1 /*
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  */
16 package android.scheduling;
17 
18 import android.Manifest;
19 import android.annotation.CallbackExecutor;
20 import android.annotation.CurrentTimeMillisLong;
21 import android.annotation.NonNull;
22 import android.annotation.RequiresPermission;
23 import android.annotation.SdkConstant;
24 import android.annotation.SystemApi;
25 import android.annotation.SystemService;
26 import android.content.Context;
27 import android.os.Bundle;
28 import android.os.RemoteCallback;
29 import android.os.RemoteException;
30 import android.text.TextUtils;
31 import android.util.ArrayMap;
32 
33 
34 import java.util.concurrent.Executor;
35 
36 /**
37  * 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
43  * decision. When the device's reboot-readiness changes, a
44  * {@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,
46  * and {@code false} when it is not ready to reboot.
47  *
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  *
53  * @hide
54  */
55 @SystemApi
56 @SystemService(Context.REBOOT_READINESS_SERVICE)
57 public final class RebootReadinessManager {
58     private static final String TAG = "RebootReadinessManager";
59 
60     private final IRebootReadinessManager mService;
61     private final Context mContext;
62     private final ArrayMap<RequestRebootReadinessStatusListener,
63             RebootReadinessCallbackProxy> mProxyList = new ArrayMap<>();
64 
65     /**
66      * 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     /**
93      * Key used to communicate between {@link RebootReadinessManager} and the system server,
94      * indicating the reboot readiness of a component that has registered a
95      * {@link RequestRebootReadinessStatusListener}. The associated value is a boolean.
96      *
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
104      * registered a {@link RequestRebootReadinessStatusListener}. The associated value is a long.
105      *
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
113      * {@link RequestRebootReadinessStatusListener}. The associated value is a String.
114      *
115      * @hide
116      */
117     public static final String SUBSYSTEM_NAME_KEY = "SUBSYSTEM_NAME";
118 
119 
120     /** {@hide} */
RebootReadinessManager(Context context, IRebootReadinessManager binder)121     public RebootReadinessManager(Context context, IRebootReadinessManager binder) {
122         mContext = context;
123         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      */
131     public interface RequestRebootReadinessStatusListener {
132 
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          */
onRequestRebootReadinessStatus()139         @NonNull RebootReadinessStatus onRequestRebootReadinessStatus();
140     }
141 
142 
143     /**
144      * 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.
148      *
149      * @hide
150      */
151     @SystemApi
152     public static final class RebootReadinessStatus {
153         private final boolean mIsReadyToReboot;
154         private final long mEstimatedFinishTime;
155         private final String mLogSubsystemName;
156 
157 
158         /**
159          * 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.
162          *
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
169          * {@link RequestRebootReadinessStatusListener} must be supplied. This identifier will be
170          * used for logging purposes.
171          *
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
178          *                         {@link RequestRebootReadinessStatusListener}.
179          */
RebootReadinessStatus(boolean isReadyToReboot, @CurrentTimeMillisLong long estimatedFinishTime, @NonNull String logSubsystemName)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.
196          */
isReadyToReboot()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
203          * 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.
206          *
207          * @return the time when this subsystem's reboot-blocking work is estimated to finish.
208          */
getEstimatedFinishTime()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.
220          */
getLogSubsystemName()221         public @NonNull String getLogSubsystemName() {
222             return mLogSubsystemName;
223         }
224     }
225 
226     private static class RebootReadinessCallbackProxy
227             extends IRequestRebootReadinessStatusListener.Stub {
228         private final RequestRebootReadinessStatusListener mCallback;
229         private final Executor mExecutor;
230 
RebootReadinessCallbackProxy(RequestRebootReadinessStatusListener callback, Executor executor)231         RebootReadinessCallbackProxy(RequestRebootReadinessStatusListener callback,
232                 Executor executor) {
233             mCallback = callback;
234             mExecutor = executor;
235         }
236 
237         @Override
onRequestRebootReadinessStatus(RemoteCallback callback)238         public void onRequestRebootReadinessStatus(RemoteCallback callback) {
239             mExecutor.execute(() -> {
240                 RebootReadinessStatus response = mCallback.onRequestRebootReadinessStatus();
241                 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
252      * be applied.
253      *
254      * <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
256      * {@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.
262      */
263     @RequiresPermission(Manifest.permission.REBOOT)
markRebootPending()264     public void markRebootPending() {
265         try {
266             mService.markRebootPending(mContext.getPackageName());
267         } 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.
276      */
277     @RequiresPermission(Manifest.permission.REBOOT)
cancelPendingReboot()278     public void cancelPendingReboot() {
279         try {
280             mService.cancelPendingReboot(mContext.getPackageName());
281         } 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)
isReadyToReboot()292     public boolean isReadyToReboot() {
293         try {
294             return mService.isReadyToReboot();
295         } catch (RemoteException e) {
296             throw e.rethrowFromSystemServer();
297         }
298     }
299 
300     /**
301      * Registers a {@link RequestRebootReadinessStatusListener} with the RebootReadinessManager.
302      *
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)
addRequestRebootReadinessStatusListener( @onNull @allbackExecutor Executor executor, @NonNull RequestRebootReadinessStatusListener callback)307     public void addRequestRebootReadinessStatusListener(
308             @NonNull @CallbackExecutor Executor executor,
309             @NonNull RequestRebootReadinessStatusListener callback) {
310         try {
311             RebootReadinessCallbackProxy proxy =
312                     new RebootReadinessCallbackProxy(callback, executor);
313             mService.addRequestRebootReadinessStatusListener(proxy);
314             mProxyList.put(callback, proxy);
315         } catch (RemoteException e) {
316             throw e.rethrowFromSystemServer();
317         }
318     }
319 
320     /**
321      * Unregisters a {@link RequestRebootReadinessStatusListener} from the RebootReadinessManager.
322      *
323      * @param callback the callback to unregister
324      */
325     @RequiresPermission(Manifest.permission.SIGNAL_REBOOT_READINESS)
removeRequestRebootReadinessStatusListener( @onNull RequestRebootReadinessStatusListener callback)326     public void removeRequestRebootReadinessStatusListener(
327             @NonNull RequestRebootReadinessStatusListener callback) {
328         try {
329             RebootReadinessCallbackProxy proxy = mProxyList.get(callback);
330             if (proxy != null) {
331                 mService.removeRequestRebootReadinessStatusListener(proxy);
332                 mProxyList.remove(callback);
333             }
334         } catch (RemoteException e) {
335             throw e.rethrowFromSystemServer();
336         }
337     }
338 
339 }
340