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