• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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