• 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