blob: b25d947f2042acbf1233baed576695aa5487d702 [file] [log] [blame]
Gavin Corkeryfd4df482021-02-15 20:20:38 +00001/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Gavin Corkeryfd4df482021-02-15 20:20:38 +000016package android.scheduling;
17
Gavin Corkery2f053552021-02-15 20:20:47 +000018import android.Manifest;
19import android.annotation.CallbackExecutor;
20import android.annotation.CurrentTimeMillisLong;
21import android.annotation.NonNull;
22import android.annotation.RequiresPermission;
Gavin Corkeryb110a272021-03-30 14:24:51 +010023import android.annotation.SdkConstant;
Gavin Corkery2f053552021-02-15 20:20:47 +000024import android.annotation.SystemApi;
25import android.annotation.SystemService;
26import android.content.Context;
27import android.os.Bundle;
Gavin Corkery2f053552021-02-15 20:20:47 +000028import android.os.RemoteCallback;
29import android.os.RemoteException;
30import android.text.TextUtils;
31import android.util.ArrayMap;
32
33
34import java.util.concurrent.Executor;
35
Gavin Corkeryfd4df482021-02-15 20:20:38 +000036/**
Gavin Corkery2f053552021-02-15 20:20:47 +000037 * Gathers signals from the device to determine whether it is safe to reboot or not.
38 *
39 * <p>This service may be used by entities that are applying updates which require the device to be
40 * rebooted, to determine when the device is in an unused state and is ready to be rebooted. When
41 * an updater has notified this service that there is a pending update that requires a reboot, this
42 * service will periodically check several signals which contribute to the reboot readiness
Gavin Corkery8caaace2021-01-30 22:35:14 +000043 * decision. When the device's reboot-readiness changes, a
Gavin Corkeryb110a272021-03-30 14:24:51 +010044 * {@link #ACTION_REBOOT_READY} broadcast will be sent. The associated extra
45 * {@link #EXTRA_IS_READY_TO_REBOOT} will be {@code true} when the device is ready to reboot,
Gavin Corkery8caaace2021-01-30 22:35:14 +000046 * and {@code false} when it is not ready to reboot.
Gavin Corkery2f053552021-02-15 20:20:47 +000047 *
48 * <p>Subsystems may register callbacks with this service. These callbacks allow subsystems to
49 * inform the reboot readiness decision in the case that they are performing important work
50 * that should not be interrupted by a reboot. An example of reboot-blocking work is tethering
51 * to another device.
52 *
Gavin Corkeryfd4df482021-02-15 20:20:38 +000053 * @hide
54 */
Gavin Corkery2f053552021-02-15 20:20:47 +000055@SystemApi
56@SystemService(Context.REBOOT_READINESS_SERVICE)
57public final class RebootReadinessManager {
58 private static final String TAG = "RebootReadinessManager";
59
60 private final IRebootReadinessManager mService;
Gavin Corkeryce2ef962021-03-18 14:57:21 +000061 private final Context mContext;
62 private final ArrayMap<RequestRebootReadinessStatusListener,
63 RebootReadinessCallbackProxy> mProxyList = new ArrayMap<>();
Gavin Corkery2f053552021-02-15 20:20:47 +000064
65 /**
Gavin Corkeryb110a272021-03-30 14:24:51 +010066 * Broadcast Action: Indicates that the device's reboot readiness has changed.
67 *
68 * <p>This broadcast will be sent with an extra that indicates whether or not the device is
69 * ready to reboot.
70 * <p>
71 * The receiver <em>must</em> have the {@link android.Manifest.permission#REBOOT} permission.
72 * <p class="note">
73 * This is a protected intent that can only be sent by the system.
74 *
75 * @see #EXTRA_IS_READY_TO_REBOOT
76 * @hide
77 */
78 @SystemApi
79 @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
80 public static final String ACTION_REBOOT_READY = "android.scheduling.action.REBOOT_READY";
81
82 /**
83 * A boolean extra used with {@link #ACTION_REBOOT_READY} which indicates if the
84 * device is ready to reboot.
85 * Will be {@code true} if ready to reboot, {@code false} otherwise.
86 * @hide
87 */
88 @SystemApi
89 public static final String EXTRA_IS_READY_TO_REBOOT =
90 "android.scheduling.extra.IS_READY_TO_REBOOT";
91
92 /**
Gavin Corkery2f053552021-02-15 20:20:47 +000093 * Key used to communicate between {@link RebootReadinessManager} and the system server,
94 * indicating the reboot readiness of a component that has registered a
Gavin Corkeryce2ef962021-03-18 14:57:21 +000095 * {@link RequestRebootReadinessStatusListener}. The associated value is a boolean.
Gavin Corkery2f053552021-02-15 20:20:47 +000096 *
97 * @hide
98 */
99 public static final String IS_REBOOT_READY_KEY = "IS_REBOOT_READY";
100
101 /**
102 * Key used to communicate between {@link RebootReadinessManager} and the system server,
103 * indicating the estimated finish time of the reboot-blocking work of a component that has
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000104 * registered a {@link RequestRebootReadinessStatusListener}. The associated value is a long.
Gavin Corkery2f053552021-02-15 20:20:47 +0000105 *
106 * @hide
107 */
108 public static final String ESTIMATED_FINISH_TIME_KEY = "ESTIMATED_FINISH_TIME";
109
110 /**
111 * Key used to communicate between {@link RebootReadinessManager} and the system server,
112 * indicating the identifier of a component that has registered a
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000113 * {@link RequestRebootReadinessStatusListener}. The associated value is a String.
Gavin Corkery2f053552021-02-15 20:20:47 +0000114 *
115 * @hide
116 */
117 public static final String SUBSYSTEM_NAME_KEY = "SUBSYSTEM_NAME";
118
Gavin Corkery806b82e2021-02-01 23:31:09 +0000119
Gavin Corkery2f053552021-02-15 20:20:47 +0000120 /** {@hide} */
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000121 public RebootReadinessManager(Context context, IRebootReadinessManager binder) {
122 mContext = context;
Gavin Corkery2f053552021-02-15 20:20:47 +0000123 mService = binder;
124 }
125
126 /**
127 * An interface implemented by a system component when registering with the
128 * {@link RebootReadinessManager}. This callback may be called multiple times when
129 * the device's reboot readiness state is being periodically polled.
130 */
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000131 public interface RequestRebootReadinessStatusListener {
Gavin Corkery2f053552021-02-15 20:20:47 +0000132
133 /**
134 * Passes a {@link RebootReadinessStatus} to the {@link RebootReadinessManager} to
135 * indicate the reboot-readiness of a component.
136 *
137 * @return a {@link RebootReadinessStatus} indicating the state of the component
138 */
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000139 @NonNull RebootReadinessStatus onRequestRebootReadinessStatus();
Gavin Corkery2f053552021-02-15 20:20:47 +0000140 }
141
142
143 /**
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000144 * A response returned from a {@link RequestRebootReadinessStatusListener}, indicating if the
145 * subsystem is performing work that should block the reboot. If reboot-blocking work is being
146 * performed, this response may indicate the estimated completion time of this work, if that
147 * value is known.
Gavin Corkery95fc5aa2021-03-02 19:25:40 +0000148 *
149 * @hide
Gavin Corkery2f053552021-02-15 20:20:47 +0000150 */
Gavin Corkery95fc5aa2021-03-02 19:25:40 +0000151 @SystemApi
Gavin Corkery2f053552021-02-15 20:20:47 +0000152 public static final class RebootReadinessStatus {
153 private final boolean mIsReadyToReboot;
154 private final long mEstimatedFinishTime;
155 private final String mLogSubsystemName;
156
157
158 /**
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000159 * Constructs a response which will be returned whenever a
160 * {@link RequestRebootReadinessStatusListener} is polled. The information in this response
161 * will be used as a signal to inform the overall reboot readiness signal.
Gavin Corkery2f053552021-02-15 20:20:47 +0000162 *
163 * If this subsystem is performing important work that should block the reboot, it may
164 * be indicated in this response. Additionally, the subsystem may indicate the expected
165 * finish time of this reboot-blocking work, if known. The callback will be polled again
166 * when the estimated finish time is reached.
167 *
168 * A non-empty identifier which reflects the name of the entity that registered the
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000169 * {@link RequestRebootReadinessStatusListener} must be supplied. This identifier will be
170 * used for logging purposes.
Gavin Corkery2f053552021-02-15 20:20:47 +0000171 *
172 * @param isReadyToReboot whether or not this subsystem is ready to reboot.
173 * @param estimatedFinishTime the time when this subsystem's reboot blocking work is
174 * estimated to be finished, if known. This value should be zero
175 * if the finish time is unknown. This value will be ignored
176 * if the subsystem is ready to reboot.
177 * @param logSubsystemName the name of the subsystem which registered the
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000178 * {@link RequestRebootReadinessStatusListener}.
Gavin Corkery2f053552021-02-15 20:20:47 +0000179 */
180 public RebootReadinessStatus(boolean isReadyToReboot,
181 @CurrentTimeMillisLong long estimatedFinishTime,
182 @NonNull String logSubsystemName) {
183 mIsReadyToReboot = isReadyToReboot;
184 mEstimatedFinishTime = estimatedFinishTime;
185 //TODO (b/161353402): Use Preconditions for this check.
186 if (TextUtils.isEmpty(logSubsystemName)) {
187 throw new IllegalArgumentException("Subsystem name should not be empty.");
188 }
189 mLogSubsystemName = logSubsystemName;
190 }
191
192 /**
193 * Returns whether this subsystem is ready to reboot or not.
194 *
195 * @return {@code true} if this subsystem is ready to reboot, {@code false} otherwise.
Gavin Corkery2f053552021-02-15 20:20:47 +0000196 */
197 public boolean isReadyToReboot() {
198 return mIsReadyToReboot;
199 }
200
201 /**
202 * Returns the time when the reboot-blocking work is estimated to finish. If this value is
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000203 * greater than 0, the associated {@link RequestRebootReadinessStatusListener} may not be
204 * called again until this time, since this subsystem is assumed to be performing important
205 * work until that time. This value is ignored if this subsystem is ready to reboot.
Gavin Corkery2f053552021-02-15 20:20:47 +0000206 *
207 * @return the time when this subsystem's reboot-blocking work is estimated to finish.
Gavin Corkery2f053552021-02-15 20:20:47 +0000208 */
209 public @CurrentTimeMillisLong long getEstimatedFinishTime() {
210 return mEstimatedFinishTime;
211 }
212
213 /**
214 * Returns an identifier of the subsystem that registered the callback, which will be used
215 * for logging purposes. This identifier should reflect the name of the entity that
216 * registered the callback, or the work it is performing. For example, this may be a
217 * package name or a service name.
218 *
219 * @return an identifier of the subsystem that registered the callback.
Gavin Corkery2f053552021-02-15 20:20:47 +0000220 */
221 public @NonNull String getLogSubsystemName() {
222 return mLogSubsystemName;
223 }
224 }
225
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000226 private static class RebootReadinessCallbackProxy
227 extends IRequestRebootReadinessStatusListener.Stub {
228 private final RequestRebootReadinessStatusListener mCallback;
Gavin Corkery2f053552021-02-15 20:20:47 +0000229 private final Executor mExecutor;
230
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000231 RebootReadinessCallbackProxy(RequestRebootReadinessStatusListener callback,
232 Executor executor) {
Gavin Corkery2f053552021-02-15 20:20:47 +0000233 mCallback = callback;
234 mExecutor = executor;
235 }
236
237 @Override
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000238 public void onRequestRebootReadinessStatus(RemoteCallback callback) {
Gavin Corkery2f053552021-02-15 20:20:47 +0000239 mExecutor.execute(() -> {
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000240 RebootReadinessStatus response = mCallback.onRequestRebootReadinessStatus();
Gavin Corkery2f053552021-02-15 20:20:47 +0000241 Bundle data = new Bundle();
242 data.putBoolean(IS_REBOOT_READY_KEY, response.isReadyToReboot());
243 data.putLong(ESTIMATED_FINISH_TIME_KEY, response.getEstimatedFinishTime());
244 data.putString(SUBSYSTEM_NAME_KEY, response.getLogSubsystemName());
245 callback.sendResult(data);
246 });
247 }
248 }
249
250 /**
251 * Notifies the RebootReadinessManager that there is a pending update that requires a reboot to
Gavin Corkery8caaace2021-01-30 22:35:14 +0000252 * be applied.
Gavin Corkery2f053552021-02-15 20:20:47 +0000253 *
Gavin Corkeryb110a272021-03-30 14:24:51 +0100254 * <p>When the device's reboot-readiness changes, a {@link #ACTION_REBOOT_READY} broadcast
255 * will be sent. The associated extra {@link #EXTRA_IS_READY_TO_REBOOT} will be
Gavin Corkery8caaace2021-01-30 22:35:14 +0000256 * {@code true} when the device is ready to reboot, and {@code false} when it is not ready to
257 * reboot.
258 *
259 * <p>If the same caller calls this method twice, the second call will be a no-op.
260 *
261 * TODO(b/161353402): Document and test multi-client cases.
Gavin Corkery2f053552021-02-15 20:20:47 +0000262 */
Gavin Corkery8caaace2021-01-30 22:35:14 +0000263 @RequiresPermission(Manifest.permission.REBOOT)
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000264 public void markRebootPending() {
Gavin Corkery2f053552021-02-15 20:20:47 +0000265 try {
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000266 mService.markRebootPending(mContext.getPackageName());
Gavin Corkery8caaace2021-01-30 22:35:14 +0000267 } catch (RemoteException e) {
268 throw e.rethrowFromSystemServer();
269 }
270 }
271
272 /**
273 * Removes the caller from the set of packages that will receive reboot readiness broadcasts.
274 * If the caller is the only client that is receiving broadcasts, reboot readiness checks will
275 * be stopped.
Gavin Corkery8caaace2021-01-30 22:35:14 +0000276 */
277 @RequiresPermission(Manifest.permission.REBOOT)
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000278 public void cancelPendingReboot() {
Gavin Corkery8caaace2021-01-30 22:35:14 +0000279 try {
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000280 mService.cancelPendingReboot(mContext.getPackageName());
Gavin Corkery2f053552021-02-15 20:20:47 +0000281 } catch (RemoteException e) {
282 throw e.rethrowFromSystemServer();
283 }
284 }
285
286 /**
287 * Determines whether the device is ready to be rebooted to apply an update.
288 *
289 * @return {@code true} if the device is ready to reboot, {@code false} otherwise
290 */
291 @RequiresPermission(Manifest.permission.REBOOT)
292 public boolean isReadyToReboot() {
293 try {
294 return mService.isReadyToReboot();
295 } catch (RemoteException e) {
296 throw e.rethrowFromSystemServer();
297 }
298 }
299
300 /**
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000301 * Registers a {@link RequestRebootReadinessStatusListener} with the RebootReadinessManager.
Gavin Corkery2f053552021-02-15 20:20:47 +0000302 *
303 * @param executor the executor that the callback will be executed on
304 * @param callback the callback to be registered
305 */
306 @RequiresPermission(Manifest.permission.SIGNAL_REBOOT_READINESS)
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000307 public void addRequestRebootReadinessStatusListener(
308 @NonNull @CallbackExecutor Executor executor,
309 @NonNull RequestRebootReadinessStatusListener callback) {
Gavin Corkery2f053552021-02-15 20:20:47 +0000310 try {
311 RebootReadinessCallbackProxy proxy =
312 new RebootReadinessCallbackProxy(callback, executor);
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000313 mService.addRequestRebootReadinessStatusListener(proxy);
Gavin Corkery2f053552021-02-15 20:20:47 +0000314 mProxyList.put(callback, proxy);
315 } catch (RemoteException e) {
316 throw e.rethrowFromSystemServer();
317 }
318 }
319
320 /**
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000321 * Unregisters a {@link RequestRebootReadinessStatusListener} from the RebootReadinessManager.
Gavin Corkery2f053552021-02-15 20:20:47 +0000322 *
323 * @param callback the callback to unregister
324 */
325 @RequiresPermission(Manifest.permission.SIGNAL_REBOOT_READINESS)
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000326 public void removeRequestRebootReadinessStatusListener(
327 @NonNull RequestRebootReadinessStatusListener callback) {
Gavin Corkery2f053552021-02-15 20:20:47 +0000328 try {
329 RebootReadinessCallbackProxy proxy = mProxyList.get(callback);
330 if (proxy != null) {
Gavin Corkeryce2ef962021-03-18 14:57:21 +0000331 mService.removeRequestRebootReadinessStatusListener(proxy);
Gavin Corkery2f053552021-02-15 20:20:47 +0000332 mProxyList.remove(callback);
333 }
334 } catch (RemoteException e) {
335 throw e.rethrowFromSystemServer();
336 }
337 }
Gavin Corkery806b82e2021-02-01 23:31:09 +0000338
Gavin Corkeryfd4df482021-02-15 20:20:38 +0000339}