| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package android.scheduling; |
| |
| import android.Manifest; |
| import android.annotation.CallbackExecutor; |
| import android.annotation.CurrentTimeMillisLong; |
| import android.annotation.NonNull; |
| import android.annotation.RequiresPermission; |
| import android.annotation.SdkConstant; |
| import android.annotation.SystemApi; |
| import android.annotation.SystemService; |
| import android.content.Context; |
| import android.os.Bundle; |
| import android.os.RemoteCallback; |
| import android.os.RemoteException; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| |
| |
| import java.util.concurrent.Executor; |
| |
| /** |
| * Gathers signals from the device to determine whether it is safe to reboot or not. |
| * |
| * <p>This service may be used by entities that are applying updates which require the device to be |
| * rebooted, to determine when the device is in an unused state and is ready to be rebooted. When |
| * an updater has notified this service that there is a pending update that requires a reboot, this |
| * service will periodically check several signals which contribute to the reboot readiness |
| * decision. When the device's reboot-readiness changes, a |
| * {@link #ACTION_REBOOT_READY} broadcast will be sent. The associated extra |
| * {@link #EXTRA_IS_READY_TO_REBOOT} will be {@code true} when the device is ready to reboot, |
| * and {@code false} when it is not ready to reboot. |
| * |
| * <p>Subsystems may register callbacks with this service. These callbacks allow subsystems to |
| * inform the reboot readiness decision in the case that they are performing important work |
| * that should not be interrupted by a reboot. An example of reboot-blocking work is tethering |
| * to another device. |
| * |
| * @hide |
| */ |
| @SystemApi |
| @SystemService(Context.REBOOT_READINESS_SERVICE) |
| public final class RebootReadinessManager { |
| private static final String TAG = "RebootReadinessManager"; |
| |
| private final IRebootReadinessManager mService; |
| private final Context mContext; |
| private final ArrayMap<RequestRebootReadinessStatusListener, |
| RebootReadinessCallbackProxy> mProxyList = new ArrayMap<>(); |
| |
| /** |
| * Broadcast Action: Indicates that the device's reboot readiness has changed. |
| * |
| * <p>This broadcast will be sent with an extra that indicates whether or not the device is |
| * ready to reboot. |
| * <p> |
| * The receiver <em>must</em> have the {@link android.Manifest.permission#REBOOT} permission. |
| * <p class="note"> |
| * This is a protected intent that can only be sent by the system. |
| * |
| * @see #EXTRA_IS_READY_TO_REBOOT |
| * @hide |
| */ |
| @SystemApi |
| @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) |
| public static final String ACTION_REBOOT_READY = "android.scheduling.action.REBOOT_READY"; |
| |
| /** |
| * A boolean extra used with {@link #ACTION_REBOOT_READY} which indicates if the |
| * device is ready to reboot. |
| * Will be {@code true} if ready to reboot, {@code false} otherwise. |
| * @hide |
| */ |
| @SystemApi |
| public static final String EXTRA_IS_READY_TO_REBOOT = |
| "android.scheduling.extra.IS_READY_TO_REBOOT"; |
| |
| /** |
| * Key used to communicate between {@link RebootReadinessManager} and the system server, |
| * indicating the reboot readiness of a component that has registered a |
| * {@link RequestRebootReadinessStatusListener}. The associated value is a boolean. |
| * |
| * @hide |
| */ |
| public static final String IS_REBOOT_READY_KEY = "IS_REBOOT_READY"; |
| |
| /** |
| * Key used to communicate between {@link RebootReadinessManager} and the system server, |
| * indicating the estimated finish time of the reboot-blocking work of a component that has |
| * registered a {@link RequestRebootReadinessStatusListener}. The associated value is a long. |
| * |
| * @hide |
| */ |
| public static final String ESTIMATED_FINISH_TIME_KEY = "ESTIMATED_FINISH_TIME"; |
| |
| /** |
| * Key used to communicate between {@link RebootReadinessManager} and the system server, |
| * indicating the identifier of a component that has registered a |
| * {@link RequestRebootReadinessStatusListener}. The associated value is a String. |
| * |
| * @hide |
| */ |
| public static final String SUBSYSTEM_NAME_KEY = "SUBSYSTEM_NAME"; |
| |
| |
| /** {@hide} */ |
| public RebootReadinessManager(Context context, IRebootReadinessManager binder) { |
| mContext = context; |
| mService = binder; |
| } |
| |
| /** |
| * An interface implemented by a system component when registering with the |
| * {@link RebootReadinessManager}. This callback may be called multiple times when |
| * the device's reboot readiness state is being periodically polled. |
| */ |
| public interface RequestRebootReadinessStatusListener { |
| |
| /** |
| * Passes a {@link RebootReadinessStatus} to the {@link RebootReadinessManager} to |
| * indicate the reboot-readiness of a component. |
| * |
| * @return a {@link RebootReadinessStatus} indicating the state of the component |
| */ |
| @NonNull RebootReadinessStatus onRequestRebootReadinessStatus(); |
| } |
| |
| |
| /** |
| * A response returned from a {@link RequestRebootReadinessStatusListener}, indicating if the |
| * subsystem is performing work that should block the reboot. If reboot-blocking work is being |
| * performed, this response may indicate the estimated completion time of this work, if that |
| * value is known. |
| * |
| * @hide |
| */ |
| @SystemApi |
| public static final class RebootReadinessStatus { |
| private final boolean mIsReadyToReboot; |
| private final long mEstimatedFinishTime; |
| private final String mLogSubsystemName; |
| |
| |
| /** |
| * Constructs a response which will be returned whenever a |
| * {@link RequestRebootReadinessStatusListener} is polled. The information in this response |
| * will be used as a signal to inform the overall reboot readiness signal. |
| * |
| * If this subsystem is performing important work that should block the reboot, it may |
| * be indicated in this response. Additionally, the subsystem may indicate the expected |
| * finish time of this reboot-blocking work, if known. The callback will be polled again |
| * when the estimated finish time is reached. |
| * |
| * A non-empty identifier which reflects the name of the entity that registered the |
| * {@link RequestRebootReadinessStatusListener} must be supplied. This identifier will be |
| * used for logging purposes. |
| * |
| * @param isReadyToReboot whether or not this subsystem is ready to reboot. |
| * @param estimatedFinishTime the time when this subsystem's reboot blocking work is |
| * estimated to be finished, if known. This value should be zero |
| * if the finish time is unknown. This value will be ignored |
| * if the subsystem is ready to reboot. |
| * @param logSubsystemName the name of the subsystem which registered the |
| * {@link RequestRebootReadinessStatusListener}. |
| */ |
| public RebootReadinessStatus(boolean isReadyToReboot, |
| @CurrentTimeMillisLong long estimatedFinishTime, |
| @NonNull String logSubsystemName) { |
| mIsReadyToReboot = isReadyToReboot; |
| mEstimatedFinishTime = estimatedFinishTime; |
| //TODO (b/161353402): Use Preconditions for this check. |
| if (TextUtils.isEmpty(logSubsystemName)) { |
| throw new IllegalArgumentException("Subsystem name should not be empty."); |
| } |
| mLogSubsystemName = logSubsystemName; |
| } |
| |
| /** |
| * Returns whether this subsystem is ready to reboot or not. |
| * |
| * @return {@code true} if this subsystem is ready to reboot, {@code false} otherwise. |
| */ |
| public boolean isReadyToReboot() { |
| return mIsReadyToReboot; |
| } |
| |
| /** |
| * Returns the time when the reboot-blocking work is estimated to finish. If this value is |
| * greater than 0, the associated {@link RequestRebootReadinessStatusListener} may not be |
| * called again until this time, since this subsystem is assumed to be performing important |
| * work until that time. This value is ignored if this subsystem is ready to reboot. |
| * |
| * @return the time when this subsystem's reboot-blocking work is estimated to finish. |
| */ |
| public @CurrentTimeMillisLong long getEstimatedFinishTime() { |
| return mEstimatedFinishTime; |
| } |
| |
| /** |
| * Returns an identifier of the subsystem that registered the callback, which will be used |
| * for logging purposes. This identifier should reflect the name of the entity that |
| * registered the callback, or the work it is performing. For example, this may be a |
| * package name or a service name. |
| * |
| * @return an identifier of the subsystem that registered the callback. |
| */ |
| public @NonNull String getLogSubsystemName() { |
| return mLogSubsystemName; |
| } |
| } |
| |
| private static class RebootReadinessCallbackProxy |
| extends IRequestRebootReadinessStatusListener.Stub { |
| private final RequestRebootReadinessStatusListener mCallback; |
| private final Executor mExecutor; |
| |
| RebootReadinessCallbackProxy(RequestRebootReadinessStatusListener callback, |
| Executor executor) { |
| mCallback = callback; |
| mExecutor = executor; |
| } |
| |
| @Override |
| public void onRequestRebootReadinessStatus(RemoteCallback callback) { |
| mExecutor.execute(() -> { |
| RebootReadinessStatus response = mCallback.onRequestRebootReadinessStatus(); |
| Bundle data = new Bundle(); |
| data.putBoolean(IS_REBOOT_READY_KEY, response.isReadyToReboot()); |
| data.putLong(ESTIMATED_FINISH_TIME_KEY, response.getEstimatedFinishTime()); |
| data.putString(SUBSYSTEM_NAME_KEY, response.getLogSubsystemName()); |
| callback.sendResult(data); |
| }); |
| } |
| } |
| |
| /** |
| * Notifies the RebootReadinessManager that there is a pending update that requires a reboot to |
| * be applied. |
| * |
| * <p>When the device's reboot-readiness changes, a {@link #ACTION_REBOOT_READY} broadcast |
| * will be sent. The associated extra {@link #EXTRA_IS_READY_TO_REBOOT} will be |
| * {@code true} when the device is ready to reboot, and {@code false} when it is not ready to |
| * reboot. |
| * |
| * <p>If the same caller calls this method twice, the second call will be a no-op. |
| * |
| * TODO(b/161353402): Document and test multi-client cases. |
| */ |
| @RequiresPermission(Manifest.permission.REBOOT) |
| public void markRebootPending() { |
| try { |
| mService.markRebootPending(mContext.getPackageName()); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Removes the caller from the set of packages that will receive reboot readiness broadcasts. |
| * If the caller is the only client that is receiving broadcasts, reboot readiness checks will |
| * be stopped. |
| */ |
| @RequiresPermission(Manifest.permission.REBOOT) |
| public void cancelPendingReboot() { |
| try { |
| mService.cancelPendingReboot(mContext.getPackageName()); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Determines whether the device is ready to be rebooted to apply an update. |
| * |
| * @return {@code true} if the device is ready to reboot, {@code false} otherwise |
| */ |
| @RequiresPermission(Manifest.permission.REBOOT) |
| public boolean isReadyToReboot() { |
| try { |
| return mService.isReadyToReboot(); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Registers a {@link RequestRebootReadinessStatusListener} with the RebootReadinessManager. |
| * |
| * @param executor the executor that the callback will be executed on |
| * @param callback the callback to be registered |
| */ |
| @RequiresPermission(Manifest.permission.SIGNAL_REBOOT_READINESS) |
| public void addRequestRebootReadinessStatusListener( |
| @NonNull @CallbackExecutor Executor executor, |
| @NonNull RequestRebootReadinessStatusListener callback) { |
| try { |
| RebootReadinessCallbackProxy proxy = |
| new RebootReadinessCallbackProxy(callback, executor); |
| mService.addRequestRebootReadinessStatusListener(proxy); |
| mProxyList.put(callback, proxy); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Unregisters a {@link RequestRebootReadinessStatusListener} from the RebootReadinessManager. |
| * |
| * @param callback the callback to unregister |
| */ |
| @RequiresPermission(Manifest.permission.SIGNAL_REBOOT_READINESS) |
| public void removeRequestRebootReadinessStatusListener( |
| @NonNull RequestRebootReadinessStatusListener callback) { |
| try { |
| RebootReadinessCallbackProxy proxy = mProxyList.get(callback); |
| if (proxy != null) { |
| mService.removeRequestRebootReadinessStatusListener(proxy); |
| mProxyList.remove(callback); |
| } |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| } |