1 package com.android.server.deviceconfig; 2 3 import static com.android.server.deviceconfig.Flags.enableChargerDependencyForReboot; 4 import static com.android.server.deviceconfig.Flags.enableCustomRebootTimeConfigurations; 5 import static com.android.server.deviceconfig.Flags.enableSimPinReplay; 6 7 import android.annotation.NonNull; 8 import android.annotation.Nullable; 9 import android.app.AlarmManager; 10 import android.app.KeyguardManager; 11 import android.app.PendingIntent; 12 import android.content.BroadcastReceiver; 13 import android.content.Context; 14 import android.content.Intent; 15 import android.content.IntentFilter; 16 import android.content.IntentSender; 17 import android.content.pm.PackageManager.NameNotFoundException; 18 import android.net.ConnectivityManager; 19 import android.net.Network; 20 import android.net.NetworkCapabilities; 21 import android.net.NetworkRequest; 22 import android.os.BatteryManager; 23 import android.os.PowerManager; 24 import android.os.RecoverySystem; 25 import android.os.SystemClock; 26 import android.util.Log; 27 import android.util.Pair; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.server.deviceconfig.resources.R; 31 32 import java.io.IOException; 33 import java.time.Instant; 34 import java.time.LocalDateTime; 35 import java.time.ZoneId; 36 import java.util.Optional; 37 import java.util.concurrent.TimeUnit; 38 39 /** 40 * Reboot scheduler for applying aconfig flags. 41 * 42 * <p>If device is password protected, uses <a 43 * href="https://source.android.com/docs/core/ota/resume-on-reboot">Resume on Reboot</a> to reboot 44 * the device, otherwise proceeds with regular reboot. 45 * 46 * @hide 47 */ 48 final class UnattendedRebootManager { 49 private static final int DEFAULT_REBOOT_WINDOW_START_TIME_HOUR = 3; 50 private static final int DEFAULT_REBOOT_WINDOW_END_TIME_HOUR = 5; 51 52 private static final int DEFAULT_REBOOT_FREQUENCY_DAYS = 2; 53 54 // Same as time RoR token is valid for. 55 private static final int DEFAULT_PREPARATION_FALLBACK_DELAY_MINUTES = 10; 56 57 private static final String TAG = "UnattendedRebootManager"; 58 59 static final String REBOOT_REASON = "unattended,flaginfra"; 60 61 @VisibleForTesting 62 static final String ACTION_RESUME_ON_REBOOT_LSKF_CAPTURED = 63 "com.android.server.deviceconfig.RESUME_ON_REBOOOT_LSKF_CAPTURED"; 64 65 @VisibleForTesting 66 static final String ACTION_TRIGGER_REBOOT = "com.android.server.deviceconfig.TRIGGER_REBOOT"; 67 68 @VisibleForTesting 69 static final String ACTION_TRIGGER_PREPARATION_FALLBACK = 70 "com.android.server.deviceconfig.TRIGGER_PREPERATION_FALLBACK"; 71 72 private final Context mContext; 73 74 @Nullable private final RebootTimingConfiguration mRebootTimingConfiguration; 75 76 private boolean mLskfCaptured; 77 78 private final UnattendedRebootManagerInjector mInjector; 79 80 private final SimPinReplayManager mSimPinReplayManager; 81 82 private boolean mChargingReceiverRegistered; 83 84 private final BroadcastReceiver mChargingReceiver = 85 new BroadcastReceiver() { 86 @Override 87 public void onReceive(Context context, Intent intent) { 88 mChargingReceiverRegistered = false; 89 mContext.unregisterReceiver(mChargingReceiver); 90 tryRebootOrSchedule(); 91 } 92 }; 93 94 private static class InjectorImpl implements UnattendedRebootManagerInjector { InjectorImpl()95 InjectorImpl() { 96 /*no op*/ 97 } 98 now()99 public long now() { 100 return System.currentTimeMillis(); 101 } 102 zoneId()103 public ZoneId zoneId() { 104 return ZoneId.systemDefault(); 105 } 106 107 @Override elapsedRealtime()108 public long elapsedRealtime() { 109 return SystemClock.elapsedRealtime(); 110 } 111 getRebootStartTime()112 public int getRebootStartTime() { 113 return DEFAULT_REBOOT_WINDOW_START_TIME_HOUR; 114 } 115 getRebootEndTime()116 public int getRebootEndTime() { 117 return DEFAULT_REBOOT_WINDOW_END_TIME_HOUR; 118 } 119 getRebootFrequency()120 public int getRebootFrequency() { 121 return DEFAULT_REBOOT_FREQUENCY_DAYS; 122 } 123 setRebootAlarm(Context context, long rebootTimeMillis)124 public void setRebootAlarm(Context context, long rebootTimeMillis) { 125 AlarmManager alarmManager = context.getSystemService(AlarmManager.class); 126 alarmManager.setExact( 127 AlarmManager.RTC_WAKEUP, 128 rebootTimeMillis, 129 createTriggerActionPendingIntent(context, ACTION_TRIGGER_REBOOT)); 130 } 131 132 @Override setPrepareForUnattendedRebootFallbackAlarm(Context context, long delayMillis)133 public void setPrepareForUnattendedRebootFallbackAlarm(Context context, long delayMillis) { 134 long alarmTime = now() + delayMillis; 135 AlarmManager alarmManager = context.getSystemService(AlarmManager.class); 136 alarmManager.set( 137 AlarmManager.RTC_WAKEUP, 138 alarmTime, 139 createTriggerActionPendingIntent(context, ACTION_TRIGGER_PREPARATION_FALLBACK)); 140 } 141 142 @Override cancelPrepareForUnattendedRebootFallbackAlarm(Context context)143 public void cancelPrepareForUnattendedRebootFallbackAlarm(Context context) { 144 AlarmManager alarmManager = context.getSystemService(AlarmManager.class); 145 alarmManager.cancel( 146 createTriggerActionPendingIntent(context, ACTION_TRIGGER_PREPARATION_FALLBACK)); 147 } 148 triggerRebootOnNetworkAvailable(Context context)149 public void triggerRebootOnNetworkAvailable(Context context) { 150 final ConnectivityManager connectivityManager = 151 context.getSystemService(ConnectivityManager.class); 152 NetworkRequest request = 153 new NetworkRequest.Builder() 154 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 155 .build(); 156 connectivityManager.requestNetwork( 157 request, createTriggerActionPendingIntent(context, ACTION_TRIGGER_REBOOT)); 158 } 159 rebootAndApply(@onNull Context context, @NonNull String reason, boolean slotSwitch)160 public int rebootAndApply(@NonNull Context context, @NonNull String reason, boolean slotSwitch) 161 throws IOException { 162 return RecoverySystem.rebootAndApply(context, reason, slotSwitch); 163 } 164 prepareForUnattendedUpdate( @onNull Context context, @NonNull String updateToken, @Nullable IntentSender intentSender)165 public void prepareForUnattendedUpdate( 166 @NonNull Context context, @NonNull String updateToken, @Nullable IntentSender intentSender) 167 throws IOException { 168 RecoverySystem.prepareForUnattendedUpdate(context, updateToken, intentSender); 169 } 170 isPreparedForUnattendedUpdate(@onNull Context context)171 public boolean isPreparedForUnattendedUpdate(@NonNull Context context) throws IOException { 172 return RecoverySystem.isPreparedForUnattendedUpdate(context); 173 } 174 175 @Override requiresChargingForReboot(Context context)176 public boolean requiresChargingForReboot(Context context) { 177 ServiceResourcesHelper resourcesHelper = ServiceResourcesHelper.get(context); 178 Optional<String> resourcesPackageName = resourcesHelper.getResourcesPackageName(); 179 if (!resourcesPackageName.isPresent()) { 180 Log.w(TAG, "requiresChargingForReboot: unable to find resources package name"); 181 return false; 182 } 183 184 Context resourcesContext; 185 try { 186 resourcesContext = context.createPackageContext(resourcesPackageName.get(), 0); 187 } catch (NameNotFoundException e) { 188 Log.e(TAG, "requiresChargingForReboot: Error in creating resources package context.", e); 189 return false; 190 } 191 if (resourcesContext == null) { 192 Log.w(TAG, "requiresChargingForReboot: unable to create resources context"); 193 return false; 194 } 195 196 return resourcesContext 197 .getResources() 198 .getBoolean(R.bool.config_requireChargingForUnattendedReboot); 199 } 200 regularReboot(Context context)201 public void regularReboot(Context context) { 202 PowerManager powerManager = context.getSystemService(PowerManager.class); 203 powerManager.reboot(REBOOT_REASON); 204 } 205 createTriggerActionPendingIntent(Context context, String action)206 private static PendingIntent createTriggerActionPendingIntent(Context context, String action) { 207 return PendingIntent.getBroadcast( 208 context, 209 /* requestCode= */ 0, 210 new Intent(action), 211 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); 212 } 213 } 214 215 @VisibleForTesting UnattendedRebootManager( Context context, UnattendedRebootManagerInjector injector, SimPinReplayManager simPinReplayManager, @Nullable RebootTimingConfiguration rebootTimingConfiguration)216 UnattendedRebootManager( 217 Context context, 218 UnattendedRebootManagerInjector injector, 219 SimPinReplayManager simPinReplayManager, 220 @Nullable RebootTimingConfiguration rebootTimingConfiguration) { 221 mContext = context; 222 mInjector = injector; 223 mSimPinReplayManager = simPinReplayManager; 224 mRebootTimingConfiguration = rebootTimingConfiguration; 225 226 mContext.registerReceiver( 227 new BroadcastReceiver() { 228 @Override 229 public void onReceive(Context context, Intent intent) { 230 mLskfCaptured = true; 231 } 232 }, 233 new IntentFilter(ACTION_RESUME_ON_REBOOT_LSKF_CAPTURED), 234 Context.RECEIVER_EXPORTED); 235 236 // Do not export receiver so that tests don't trigger reboot. 237 mContext.registerReceiver( 238 new BroadcastReceiver() { 239 @Override 240 public void onReceive(Context context, Intent intent) { 241 tryRebootOrSchedule(); 242 } 243 }, 244 new IntentFilter(ACTION_TRIGGER_REBOOT), 245 Context.RECEIVER_NOT_EXPORTED); 246 mContext.registerReceiver( 247 new BroadcastReceiver() { 248 @Override 249 public void onReceive(Context context, Intent intent) { 250 prepareUnattendedReboot(); 251 } 252 }, 253 new IntentFilter(ACTION_TRIGGER_PREPARATION_FALLBACK), 254 Context.RECEIVER_NOT_EXPORTED); 255 } 256 UnattendedRebootManager(Context context)257 UnattendedRebootManager(Context context) { 258 this( 259 context, 260 new InjectorImpl(), 261 new SimPinReplayManager(context), 262 enableCustomRebootTimeConfigurations() ? new RebootTimingConfiguration(context) : null); 263 } 264 maybePrepareUnattendedReboot()265 public void maybePrepareUnattendedReboot() { 266 Log.d(TAG, "Setting timeout for preparing unattended reboot."); 267 // RoR only supported on devices with screen lock. 268 if (!isDeviceSecure(mContext)) { 269 return; 270 } 271 272 // In the case of RoR failure or reboot without RoR, the device will stay in 273 // LOCKED_BOOT_COMPLETED state until primary auth. 274 // Since preparing for RoR can clear RoR state, wait sufficient time for RoR to finish 275 // before sending fallback preparation during LOCKED_BOOT_STATE. 276 mInjector.setPrepareForUnattendedRebootFallbackAlarm( 277 mContext, TimeUnit.MINUTES.toMillis(DEFAULT_PREPARATION_FALLBACK_DELAY_MINUTES)); 278 } 279 prepareUnattendedReboot()280 public void prepareUnattendedReboot() { 281 Log.i(TAG, "Preparing for Unattended Reboot"); 282 // RoR only supported on devices with screen lock. 283 if (!isDeviceSecure(mContext)) { 284 return; 285 } 286 if (isPreparedForUnattendedReboot()) { 287 Log.d(TAG, "Unattended reboot has already been prepared, skip"); 288 return; 289 } 290 PendingIntent pendingIntent = 291 PendingIntent.getBroadcast( 292 mContext, 293 /* requestCode= */ 0, 294 new Intent(ACTION_RESUME_ON_REBOOT_LSKF_CAPTURED), 295 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); 296 297 try { 298 mInjector.prepareForUnattendedUpdate( 299 mContext, /* updateToken= */ "", pendingIntent.getIntentSender()); 300 } catch (IOException e) { 301 Log.i(TAG, "prepareForUnattendedReboot failed with exception" + e.getLocalizedMessage()); 302 } 303 mInjector.cancelPrepareForUnattendedRebootFallbackAlarm(mContext); 304 } 305 scheduleReboot()306 public void scheduleReboot() { 307 // Reboot the next day at the reboot start time. 308 final int rebootHour; 309 if (enableCustomRebootTimeConfigurations()) { 310 Optional<Pair<Integer, Integer>> rebootWindowStartEndHour = 311 mRebootTimingConfiguration.getRebootWindowStartEndHour(); 312 rebootHour = rebootWindowStartEndHour.isEmpty() ? 0 : rebootWindowStartEndHour.get().first; 313 } else { 314 rebootHour = mInjector.getRebootStartTime(); 315 } 316 LocalDateTime timeToReboot = 317 Instant.ofEpochMilli(mInjector.now()) 318 .atZone(mInjector.zoneId()) 319 .toLocalDate() 320 .plusDays(getRebootFrequencyDays()) 321 .atTime(rebootHour, /* minute= */ 12); 322 long rebootTimeMillis = timeToReboot.atZone(mInjector.zoneId()).toInstant().toEpochMilli(); 323 Log.v(TAG, "Scheduling unattended reboot at time " + timeToReboot); 324 325 if (timeToReboot.isBefore( 326 LocalDateTime.ofInstant(Instant.ofEpochMilli(mInjector.now()), mInjector.zoneId()))) { 327 Log.w(TAG, "Reboot time has already passed."); 328 return; 329 } 330 331 mInjector.setRebootAlarm(mContext, rebootTimeMillis); 332 } 333 334 @VisibleForTesting tryRebootOrSchedule()335 void tryRebootOrSchedule() { 336 Log.v(TAG, "Attempting unattended reboot"); 337 338 final int rebootFrequencyDays = getRebootFrequencyDays(); 339 // Has enough time passed since reboot? 340 if (TimeUnit.MILLISECONDS.toDays(mInjector.elapsedRealtime()) < rebootFrequencyDays) { 341 Log.v(TAG, "Device has already been rebooted in that last " + rebootFrequencyDays + " days."); 342 scheduleReboot(); 343 return; 344 } 345 // Is RoR is supported? 346 if (!isDeviceSecure(mContext)) { 347 Log.v(TAG, "Device is not secure. Proceed with regular reboot"); 348 mInjector.regularReboot(mContext); 349 return; 350 } 351 // Is RoR prepared? 352 if (!isPreparedForUnattendedReboot()) { 353 Log.v(TAG, "Lskf is not captured, reschedule reboot."); 354 prepareUnattendedReboot(); 355 scheduleReboot(); 356 return; 357 } 358 // Is network connected? 359 // TODO(b/305259443): Use after-boot network connectivity projection 360 if (!isNetworkConnected(mContext)) { 361 Log.i(TAG, "Network is not connected, reschedule reboot."); 362 mInjector.triggerRebootOnNetworkAvailable(mContext); 363 return; 364 } 365 // Is current time between reboot window? 366 int currentHour = 367 Instant.ofEpochMilli(mInjector.now()) 368 .atZone(mInjector.zoneId()) 369 .toLocalDateTime() 370 .getHour(); 371 final boolean isHourWithinRebootHourWindow; 372 if (enableCustomRebootTimeConfigurations()) { 373 isHourWithinRebootHourWindow = 374 mRebootTimingConfiguration.isHourWithinRebootHourWindow(currentHour); 375 } else { 376 isHourWithinRebootHourWindow = 377 currentHour >= mInjector.getRebootStartTime() 378 && currentHour < mInjector.getRebootEndTime(); 379 } 380 if (!isHourWithinRebootHourWindow) { 381 Log.v(TAG, "Reboot requested outside of reboot window, reschedule reboot."); 382 prepareUnattendedReboot(); 383 scheduleReboot(); 384 return; 385 } 386 // Is preparing for SIM PIN replay successful? 387 if (enableSimPinReplay() && !mSimPinReplayManager.prepareSimPinReplay()) { 388 Log.w(TAG, "Sim Pin Replay failed, reschedule reboot"); 389 scheduleReboot(); 390 } 391 392 if (enableChargerDependencyForReboot() 393 && mInjector.requiresChargingForReboot(mContext) 394 && !isCharging(mContext)) { 395 triggerRebootOnCharging(); 396 return; 397 } 398 399 // Proceed with RoR. 400 Log.v(TAG, "Rebooting device to apply device config flags."); 401 try { 402 int success = mInjector.rebootAndApply(mContext, REBOOT_REASON, /* slotSwitch= */ false); 403 if (success != 0) { 404 // If reboot is not successful, reschedule. 405 Log.w(TAG, "Unattended reboot failed, reschedule reboot."); 406 scheduleReboot(); 407 } 408 } catch (IOException e) { 409 Log.e(TAG, e.getLocalizedMessage()); 410 scheduleReboot(); 411 } 412 } 413 414 private int getRebootFrequencyDays() { 415 return enableCustomRebootTimeConfigurations() 416 ? mRebootTimingConfiguration.getRebootFrequencyDays() 417 : mInjector.getRebootFrequency(); 418 } 419 420 private boolean isPreparedForUnattendedReboot() { 421 try { 422 boolean isPrepared = mInjector.isPreparedForUnattendedUpdate(mContext); 423 if (isPrepared != mLskfCaptured) { 424 Log.w(TAG, "isPrepared != mLskfCaptured. Received " + isPrepared); 425 } 426 return isPrepared; 427 } catch (IOException e) { 428 Log.w(TAG, e.getLocalizedMessage()); 429 return mLskfCaptured; 430 } 431 } 432 433 private void triggerRebootOnCharging() { 434 if (!mChargingReceiverRegistered) { 435 mChargingReceiverRegistered = true; 436 mContext.registerReceiver( 437 mChargingReceiver, 438 new IntentFilter(BatteryManager.ACTION_CHARGING), 439 Context.RECEIVER_EXPORTED); 440 } 441 } 442 443 /** Returns true if the device has screen lock. */ 444 private static boolean isDeviceSecure(Context context) { 445 KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class); 446 if (keyguardManager == null) { 447 // Unknown if device is locked, proceed with RoR anyway. 448 Log.w(TAG, "Keyguard manager is null, proceeding with RoR anyway."); 449 return true; 450 } 451 return keyguardManager.isDeviceSecure(); 452 } 453 454 private static boolean isCharging(Context context) { 455 BatteryManager batteryManager = 456 (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE); 457 return batteryManager.isCharging(); 458 } 459 460 private static boolean isNetworkConnected(Context context) { 461 final ConnectivityManager connectivityManager = 462 context.getSystemService(ConnectivityManager.class); 463 if (connectivityManager == null) { 464 Log.w(TAG, "ConnectivityManager is null"); 465 return false; 466 } 467 Network activeNetwork = connectivityManager.getActiveNetwork(); 468 NetworkCapabilities networkCapabilities = 469 connectivityManager.getNetworkCapabilities(activeNetwork); 470 return networkCapabilities != null 471 && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 472 && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); 473 } 474 } 475