Support unattended reboot with sim pin replay

Bug: 305269414
Test: atest ConfigInfrastructureServiceUnitTests[com.google.android.configinfrastructure.apex]
Change-Id: If60893c973b751a5a927a5076a2f82b512779793
diff --git a/service/Android.bp b/service/Android.bp
index 664505d..7fbec4a 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -26,7 +26,6 @@
     permitted_packages: [
         "android.provider",
         "com.android.server.deviceconfig",
-        //"com.google.protobuf",
     ],
     apex_available: [
         "com.android.configinfrastructure",
@@ -35,6 +34,7 @@
         "modules-utils-build",
         "modules-utils-shell-command-handler",
         "device_config_reboot_flags_java_lib",
+        "guava",
         "libaconfig_java_proto_lite"
     ],
     libs: [
diff --git a/service/flags.aconfig b/service/flags.aconfig
index a9ca293..e1396a0 100644
--- a/service/flags.aconfig
+++ b/service/flags.aconfig
@@ -12,3 +12,10 @@
   description: "This flag controls enabling the unattended reboot feature for applying flags."
   bug: "297502146"
 }
+
+flag {
+  name: "enable_sim_pin_replay"
+  namespace: "core_experiments_team_internal"
+  description: "This flag controls enabling sim pin replay for unattended reboot."
+  bug: "305269414"
+}
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index ebaebab..c284d6e 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -1,3 +1,6 @@
 rule com.android.modules.utils.** com.android.server.deviceconfig.internal.modules.utils.@1
 rule com.google.protobuf.** com.android.server.deviceconfig.internal.protobuf.@1
+rule com.google.common.** com.android.server.deviceconfig.internal.common.@1
+rule javax.annotation.** com.android.server.deviceconfig.javax.annotation.@1
+rule com.google.thirdparty.publicsuffix.** com.android.server.deviceconfig.publicsuffix.@1
 rule android.aconfig.** com.android.server.deviceconfig.internal.aconfig.@1
diff --git a/service/java/com/android/server/deviceconfig/SimPinReplayManager.java b/service/java/com/android/server/deviceconfig/SimPinReplayManager.java
new file mode 100644
index 0000000..93c3f5f
--- /dev/null
+++ b/service/java/com/android/server/deviceconfig/SimPinReplayManager.java
@@ -0,0 +1,144 @@
+package com.android.server.deviceconfig;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * If device contains a SIM PIN, must prepare <a
+ * href="https://source.android.com/docs/core/ota/resume-on-reboot#sim-pin-replay">Sim Pin
+ * Replay</a> to unlock the device post reboot.
+ *
+ * @hide
+ */
+public class SimPinReplayManager {
+
+  private static final String TAG = "UnattendedRebootManager";
+
+  // The identifier of the system resource value that determines whether auto-sim-unlock feature is
+  // enabled/disabled for the device.
+  private static final String SYSTEM_ENABLE_SIM_PIN_STORAGE_KEY =
+      "config_allow_pin_storage_for_unattended_reboot";
+  // This is a copy of the hidden field
+  // CarrierConfigManager#KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL. Phonesky uses this key to
+  // read the boolean value in carrier configs specifying whether to enable/disable auto-sim-unlock.
+  private static final String CARRIER_ENABLE_SIM_PIN_STORAGE_KEY =
+      "store_sim_pin_for_unattended_reboot_bool";
+
+  private Context mContext;
+
+  SimPinReplayManager(Context context) {
+    mContext = context;
+  }
+
+  /** Returns true, if no SIM PIN present or prepared SIM PIN Replay. */
+  public boolean prepareSimPinReplay() {
+    // Is SIM Pin present?
+    ImmutableList<Integer> pinLockedSubscriptionIds = getPinLockedSubscriptionIds(mContext);
+    if (pinLockedSubscriptionIds.isEmpty()) {
+      return true;
+    }
+
+    if (!isSimPinStorageEnabled(mContext, pinLockedSubscriptionIds)) {
+      Log.w(TAG, "SIM PIN storage is disabled");
+      return false;
+    }
+
+    TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+    if (telephonyManager == null) {
+      Log.e(TAG, "Failed to prepare SIM PIN Replay, TelephonyManager is null");
+      return false;
+    }
+
+    int prepareUnattendedRebootResult = telephonyManager.prepareForUnattendedReboot();
+    if (prepareUnattendedRebootResult == TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS) {
+      Log.i(TAG, "SIM PIN replay prepared");
+      return true;
+    }
+    Log.w(TAG, "Failed to prepare SIM PIN Replay, " + prepareUnattendedRebootResult);
+    return false;
+  }
+
+  /** Returns a list of telephony subscription IDs (SIM IDs) locked by PIN. */
+  private static ImmutableList<Integer> getPinLockedSubscriptionIds(Context context) {
+    SubscriptionManager subscriptionManager = context.getSystemService(SubscriptionManager.class);
+    int[] subscriptionIds = subscriptionManager.getActiveSubscriptionIdList();
+    if (subscriptionIds.length == 0) {
+      return ImmutableList.of();
+    }
+
+    TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+    ImmutableList.Builder<Integer> pinLockedSubscriptionIdsBuilder = ImmutableList.builder();
+    for (int subscriptionId : subscriptionIds) {
+      if (telephonyManager.createForSubscriptionId(subscriptionId).isIccLockEnabled()) {
+        pinLockedSubscriptionIdsBuilder.add(subscriptionId);
+      }
+    }
+    return pinLockedSubscriptionIdsBuilder.build();
+  }
+
+  /**
+   * Returns true, if SIM PIN storage is enabled.
+   *
+   * <p>The SIM PIN storage might be disabled by OEM or by carrier, subscription (SIM) Id is
+   * required when checking if the corresponding SIM PIN storage is disabled by the carrier.
+   *
+   * <p>Both the OEM and carrier enable SIM PIN storage by default. If fails to read the OEM/carrier
+   * configs, it assume SIM PIN storage is enabled.
+   */
+  private static boolean isSimPinStorageEnabled(
+      Context context, ImmutableList<Integer> pinLockedSubscriptionIds) {
+    if (!isSystemEnableSimPin()) {
+      return false;
+    }
+
+    // If the carrier enables SIM PIN.
+    CarrierConfigManager carrierConfigManager =
+        context.getSystemService(CarrierConfigManager.class);
+    if (carrierConfigManager == null) {
+      Log.w(TAG, "CarrierConfigManager is null");
+      return true;
+    }
+    for (int pinLockedSubscriptionId : pinLockedSubscriptionIds) {
+      PersistableBundle subscriptionConfig =
+          carrierConfigManager.getConfigForSubId(
+              pinLockedSubscriptionId, CARRIER_ENABLE_SIM_PIN_STORAGE_KEY);
+      // Only disable if carrier explicitly disables sim pin storage.
+      if (!subscriptionConfig.isEmpty()
+          && !subscriptionConfig.getBoolean(
+              CARRIER_ENABLE_SIM_PIN_STORAGE_KEY, /* defaultValue= */ true)) {
+        Log.w(
+            TAG,
+            "The carrier disables SIM PIN storage on subscription ID " + pinLockedSubscriptionId);
+        return false;
+      }
+    }
+    Log.v(TAG, "SIM PIN Storage is enabled");
+    return true;
+  }
+
+  private static boolean isSystemEnableSimPin() {
+    try {
+      boolean value =
+          Resources.getSystem()
+              .getBoolean(
+                  Resources.getSystem()
+                      .getIdentifier(
+                          SYSTEM_ENABLE_SIM_PIN_STORAGE_KEY,
+                          /* defType= */ "bool",
+                          /* defPackage= */ "android"));
+      Log.i(TAG, SYSTEM_ENABLE_SIM_PIN_STORAGE_KEY + " = " + value);
+      return value;
+    } catch (Resources.NotFoundException e) {
+      Log.e(TAG, "Could not read system resource value ," + SYSTEM_ENABLE_SIM_PIN_STORAGE_KEY);
+      // When not explicitly disabled, assume SIM PIN storage functions properly.
+      return true;
+    }
+  }
+}
diff --git a/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java b/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java
index 8429d0a..2823b66 100644
--- a/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java
+++ b/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java
@@ -1,5 +1,7 @@
 package com.android.server.deviceconfig;
 
+import static com.android.server.deviceconfig.Flags.enableSimPinReplay;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlarmManager;
@@ -19,6 +21,7 @@
 import android.os.SystemClock;
 import android.util.Log;
 import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.IOException;
 import java.time.Instant;
 import java.time.LocalDateTime;
@@ -57,6 +60,8 @@
 
   private final UnattendedRebootManagerInjector mInjector;
 
+  private final SimPinReplayManager mSimPinReplayManager;
+
   private static class InjectorImpl implements UnattendedRebootManagerInjector {
     InjectorImpl() {
       /*no op*/
@@ -133,9 +138,13 @@
   }
 
   @VisibleForTesting
-  UnattendedRebootManager(Context context, UnattendedRebootManagerInjector injector) {
+  UnattendedRebootManager(
+      Context context,
+      UnattendedRebootManagerInjector injector,
+      SimPinReplayManager simPinReplayManager) {
     mContext = context;
     mInjector = injector;
+    mSimPinReplayManager = simPinReplayManager;
 
     mContext.registerReceiver(
         new BroadcastReceiver() {
@@ -160,7 +169,7 @@
   }
 
   UnattendedRebootManager(Context context) {
-    this(context, new InjectorImpl());
+    this(context, new InjectorImpl(), new SimPinReplayManager(context));
   }
 
   public void prepareUnattendedReboot() {
@@ -213,7 +222,7 @@
         < mInjector.getRebootFrequency()) {
       Log.v(
           TAG,
-          "Device has already been rebooted in that last"
+          "Device has already been rebooted in that last "
               + mInjector.getRebootFrequency()
               + " days.");
       scheduleReboot();
@@ -235,7 +244,7 @@
     // Is network connected?
     // TODO(b/305259443): Use after-boot network connectivity projection
     if (!isNetworkConnected(mContext)) {
-      Log.i(TAG, "Network is not connected, schedule reboot for another time.");
+      Log.i(TAG, "Network is not connected, reschedule reboot.");
       mInjector.triggerRebootOnNetworkAvailable(mContext);
       return;
     }
@@ -247,19 +256,24 @@
             .getHour();
     if (currentHour < mInjector.getRebootStartTime()
         || currentHour >= mInjector.getRebootEndTime()) {
-      Log.v(TAG, "Reboot requested outside of reboot window, reschedule.");
+      Log.v(TAG, "Reboot requested outside of reboot window, reschedule reboot.");
       prepareUnattendedReboot();
       scheduleReboot();
       return;
     }
+    // Is preparing for SIM PIN replay successful?
+    if (enableSimPinReplay() && !mSimPinReplayManager.prepareSimPinReplay()) {
+      Log.w(TAG, "Sim Pin Replay failed, reschedule reboot");
+      scheduleReboot();
+    }
 
     // Proceed with RoR.
-    Log.v(TAG, "Rebooting device...");
+    Log.v(TAG, "Rebooting device to apply device config flags.");
     try {
       int success = mInjector.rebootAndApply(mContext, REBOOT_REASON, /* slotSwitch= */ false);
       if (success != 0) {
         // If reboot is not successful, reschedule.
-        Log.w(TAG, "Unattended reboot failed, reschedule.");
+        Log.w(TAG, "Unattended reboot failed, reschedule reboot.");
         scheduleReboot();
       }
     } catch (IOException e) {
diff --git a/service/javatests/Android.bp b/service/javatests/Android.bp
index d9617d8..0177890 100644
--- a/service/javatests/Android.bp
+++ b/service/javatests/Android.bp
@@ -44,6 +44,7 @@
         "frameworks-base-testutils",
         "mockito-target-minus-junit4",
         "truth",
+        "flag-junit",
     ],
     libs: [
         "android.test.base",
diff --git a/service/javatests/src/com/android/server/deviceconfig/SimPinReplayManagerTest.java b/service/javatests/src/com/android/server/deviceconfig/SimPinReplayManagerTest.java
new file mode 100644
index 0000000..f56a6dd
--- /dev/null
+++ b/service/javatests/src/com/android/server/deviceconfig/SimPinReplayManagerTest.java
@@ -0,0 +1,177 @@
+package com.android.server.deviceconfig;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class SimPinReplayManagerTest {
+
+  private static final String TAG = "SimPinReplayManagerTest";
+
+  // A copy of the hidden field CarrierConfigManager#KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL.
+  public static final String CARRIER_ENABLE_SIM_PIN_STORAGE_KEY =
+      "store_sim_pin_for_unattended_reboot_bool";
+
+  SimPinReplayManager mSimPinReplayManager;
+  SubscriptionManager mSubscriptionManager;
+  TelephonyManager mTelephonyManager;
+  CarrierConfigManager mCarrierConfigManager;
+
+  private Context mContext;
+
+  @Before
+  public void setUp() {
+
+    mSubscriptionManager = mock(SubscriptionManager.class);
+    mTelephonyManager = mock(TelephonyManager.class);
+    mCarrierConfigManager = mock(CarrierConfigManager.class);
+
+    mContext =
+        new ContextWrapper(getInstrumentation().getTargetContext()) {
+          @Override
+          public Object getSystemService(String name) {
+            if (name.equals(Context.TELEPHONY_SUBSCRIPTION_SERVICE)) {
+              return mSubscriptionManager;
+            } else if (name.equals(Context.TELEPHONY_SERVICE)) {
+              return mTelephonyManager;
+            } else if (name.equals(Context.CARRIER_CONFIG_SERVICE)) {
+              return mCarrierConfigManager;
+            }
+            return super.getSystemService(name);
+          }
+        };
+
+    mSimPinReplayManager = new SimPinReplayManager(mContext);
+  }
+
+  @Test
+  public void prepareSimPinReplay_success() {
+    Log.i(TAG, "prepareSimPinReplay_success");
+    when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[] {1}); // has sim
+    TelephonyManager subIdManager = mock(TelephonyManager.class);
+    when(mTelephonyManager.createForSubscriptionId(1)).thenReturn(subIdManager);
+    when(subIdManager.isIccLockEnabled()).thenReturn(true); // has pin
+    PersistableBundle config = new PersistableBundle(); // empty carrier config
+    when(mCarrierConfigManager.getConfigForSubId(1, CARRIER_ENABLE_SIM_PIN_STORAGE_KEY))
+        .thenReturn(config);
+    when(mTelephonyManager.prepareForUnattendedReboot())
+        .thenReturn(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
+
+    boolean isPrepared = mSimPinReplayManager.prepareSimPinReplay();
+
+    assertTrue(isPrepared);
+  }
+
+  @Test
+  public void prepareSimPinReplay_noSim() {
+    Log.i(TAG, "prepareSimPinReplay_noSim");
+    when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[] {}); // no sim
+
+    boolean isPrepared = mSimPinReplayManager.prepareSimPinReplay();
+
+    assertTrue(isPrepared);
+  }
+
+  @Test
+  public void prepareSimPinReplay_noSimPin() {
+    Log.i(TAG, "prepareSimPinReplay_noSimPin");
+    when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[] {1}); // has sim
+    TelephonyManager subIdManager = mock(TelephonyManager.class);
+    when(mTelephonyManager.createForSubscriptionId(1)).thenReturn(subIdManager);
+    when(subIdManager.isIccLockEnabled()).thenReturn(false); // no pin
+
+    boolean isPrepared = mSimPinReplayManager.prepareSimPinReplay();
+
+    assertTrue(isPrepared);
+  }
+
+  @Test
+  public void prepareSimPinReplay_carrierDisableSimPin() {
+    Log.i(TAG, "prepareSimPinReplay_carrierDisableSimPin");
+    when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[] {1}); // has sim
+    TelephonyManager subIdManager = mock(TelephonyManager.class);
+    when(mTelephonyManager.createForSubscriptionId(1)).thenReturn(subIdManager);
+    when(subIdManager.isIccLockEnabled()).thenReturn(true); // has pin
+    PersistableBundle config = new PersistableBundle();
+    config.putBoolean(CARRIER_ENABLE_SIM_PIN_STORAGE_KEY, false); // carrier disabled
+    when(mCarrierConfigManager.getConfigForSubId(1, CARRIER_ENABLE_SIM_PIN_STORAGE_KEY))
+        .thenReturn(config);
+
+    boolean isPrepared = mSimPinReplayManager.prepareSimPinReplay();
+
+    assertFalse(isPrepared);
+  }
+
+  @Test
+  public void prepareSimPinReplay_carrierEnabled() {
+    Log.i(TAG, "prepareSimPinReplay_carrierEnabled");
+    when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[] {1}); // has sim
+    TelephonyManager subIdManager = mock(TelephonyManager.class);
+    when(mTelephonyManager.createForSubscriptionId(1)).thenReturn(subIdManager);
+    when(subIdManager.isIccLockEnabled()).thenReturn(true); // has pin
+    PersistableBundle config = new PersistableBundle();
+    config.putBoolean(CARRIER_ENABLE_SIM_PIN_STORAGE_KEY, true); // carrier enabled
+    when(mCarrierConfigManager.getConfigForSubId(1, CARRIER_ENABLE_SIM_PIN_STORAGE_KEY))
+        .thenReturn(config);
+    when(mTelephonyManager.prepareForUnattendedReboot())
+        .thenReturn(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
+
+    boolean isPrepared = mSimPinReplayManager.prepareSimPinReplay();
+
+    assertTrue(isPrepared);
+  }
+
+  @Test
+  public void prepareSimPinReplay_prepareError() {
+    Log.i(TAG, "prepareSimPinReplay_prepareError");
+    when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[] {1}); // has sim
+    TelephonyManager subIdManager = mock(TelephonyManager.class);
+    when(mTelephonyManager.createForSubscriptionId(1)).thenReturn(subIdManager);
+    when(subIdManager.isIccLockEnabled()).thenReturn(true); // has pin
+    PersistableBundle config = new PersistableBundle();
+    config.putBoolean(CARRIER_ENABLE_SIM_PIN_STORAGE_KEY, true); // carrier enabled
+    when(mCarrierConfigManager.getConfigForSubId(1, CARRIER_ENABLE_SIM_PIN_STORAGE_KEY))
+        .thenReturn(config);
+    when(mTelephonyManager.prepareForUnattendedReboot())
+        .thenReturn(TelephonyManager.PREPARE_UNATTENDED_REBOOT_ERROR);
+
+    boolean isPrepared = mSimPinReplayManager.prepareSimPinReplay();
+
+    assertFalse(isPrepared);
+  }
+
+  @Test
+  public void prepareSimPinReplay_preparePinRequired() {
+    Log.i(TAG, "prepareSimPinReplay_preparePinRequired");
+    when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[] {1}); // has sim
+    TelephonyManager subIdManager = mock(TelephonyManager.class);
+    when(mTelephonyManager.createForSubscriptionId(1)).thenReturn(subIdManager);
+    when(subIdManager.isIccLockEnabled()).thenReturn(true); // has pin
+    PersistableBundle config = new PersistableBundle();
+    config.putBoolean(CARRIER_ENABLE_SIM_PIN_STORAGE_KEY, true); // carrier enabled
+    when(mCarrierConfigManager.getConfigForSubId(1, CARRIER_ENABLE_SIM_PIN_STORAGE_KEY))
+        .thenReturn(config);
+    when(mTelephonyManager.prepareForUnattendedReboot())
+        .thenReturn(TelephonyManager.PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED);
+
+    boolean isPrepared = mSimPinReplayManager.prepareSimPinReplay();
+
+    assertFalse(isPrepared);
+  }
+}
diff --git a/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java b/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java
index 7d822b6..a0579f1 100644
--- a/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java
+++ b/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java
@@ -1,6 +1,8 @@
 package com.android.server.deviceconfig;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.server.deviceconfig.Flags.FLAG_ENABLE_SIM_PIN_REPLAY;
+
 import static com.android.server.deviceconfig.UnattendedRebootManager.ACTION_RESUME_ON_REBOOT_LSKF_CAPTURED;
 import static com.android.server.deviceconfig.UnattendedRebootManager.ACTION_TRIGGER_REBOOT;
 import static com.google.common.truth.Truth.assertThat;
@@ -10,6 +12,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.platform.test.flag.junit.SetFlagsRule;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import android.app.KeyguardManager;
@@ -29,6 +32,7 @@
 import java.util.concurrent.TimeUnit;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
 @SmallTest
@@ -53,9 +57,17 @@
   private ConnectivityManager mConnectivityManager;
   private FakeInjector mFakeInjector;
   private UnattendedRebootManager mRebootManager;
+  private SimPinReplayManager mSimPinReplayManager;
+
+  @Rule
+  public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
 
   @Before
   public void setUp() throws Exception {
+    mSetFlagsRule.enableFlags(FLAG_ENABLE_SIM_PIN_REPLAY);
+
+    mSimPinReplayManager = mock(SimPinReplayManager.class);
     mKeyguardManager = mock(KeyguardManager.class);
     mConnectivityManager = mock(ConnectivityManager.class);
 
@@ -73,7 +85,7 @@
         };
 
     mFakeInjector = new FakeInjector();
-    mRebootManager = new UnattendedRebootManager(mContext, mFakeInjector);
+    mRebootManager = new UnattendedRebootManager(mContext, mFakeInjector, mSimPinReplayManager);
 
     // Need to register receiver in tests so that the test doesn't trigger reboot requested by
     // deviceconfig.
@@ -100,6 +112,7 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
                 .build());
+    when(mSimPinReplayManager.prepareSimPinReplay()).thenReturn(true);
 
     mRebootManager.prepareUnattendedReboot();
     mRebootManager.scheduleReboot();
@@ -119,6 +132,7 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
                 .build());
+    when(mSimPinReplayManager.prepareSimPinReplay()).thenReturn(true);
 
     mRebootManager.prepareUnattendedReboot();
     mRebootManager.scheduleReboot();
@@ -138,6 +152,7 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
                 .build());
+    when(mSimPinReplayManager.prepareSimPinReplay()).thenReturn(true);
 
     mRebootManager.scheduleReboot();
 
@@ -147,10 +162,31 @@
   }
 
   @Test
+  public void scheduleReboot_simPinPreparationFailed() {
+    Log.i(TAG, "scheduleReboot");
+    when(mKeyguardManager.isDeviceSecure()).thenReturn(true);
+    when(mConnectivityManager.getNetworkCapabilities(any()))
+        .thenReturn(
+            new NetworkCapabilities.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+                .build());
+    when(mSimPinReplayManager.prepareSimPinReplay()).thenReturn(false).thenReturn(true);
+
+    mRebootManager.prepareUnattendedReboot();
+    mRebootManager.scheduleReboot();
+
+    assertTrue(mFakeInjector.isRebootAndApplied());
+    assertFalse(mFakeInjector.isRegularRebooted());
+    assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(RESCHEDULED_REBOOT_TIME);
+  }
+
+  @Test
   public void scheduleReboot_noInternet() {
     Log.i(TAG, "scheduleReboot_noInternet");
     when(mKeyguardManager.isDeviceSecure()).thenReturn(true);
     when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(new NetworkCapabilities());
+    when(mSimPinReplayManager.prepareSimPinReplay()).thenReturn(true);
 
     mRebootManager.prepareUnattendedReboot();
     mRebootManager.scheduleReboot();
@@ -170,6 +206,7 @@
             new NetworkCapabilities.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build());
+    when(mSimPinReplayManager.prepareSimPinReplay()).thenReturn(true);
 
     mRebootManager.prepareUnattendedReboot();
     mRebootManager.scheduleReboot();
@@ -190,6 +227,7 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
                 .build());
+    when(mSimPinReplayManager.prepareSimPinReplay()).thenReturn(true);
     mFakeInjector.setElapsedRealtime(82800000); // 23 hours
 
     mRebootManager.prepareUnattendedReboot();
@@ -210,6 +248,7 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
                 .build());
+    when(mSimPinReplayManager.prepareSimPinReplay()).thenReturn(true);
     mFakeInjector.setNow(OUTSIDE_WINDOW_REBOOT_TIME);
 
     mRebootManager.prepareUnattendedReboot();