340 lines
14 KiB
Java
340 lines
14 KiB
Java
/*
|
|
* 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();
|
|
}
|
|
}
|
|
|
|
}
|