diff options
Diffstat (limited to 'service/javatests/src')
4 files changed, 744 insertions, 0 deletions
diff --git a/service/javatests/src/com/android/server/deviceconfig/BootNotificationCreatorTest.java b/service/javatests/src/com/android/server/deviceconfig/BootNotificationCreatorTest.java new file mode 100644 index 0000000..40e807a --- /dev/null +++ b/service/javatests/src/com/android/server/deviceconfig/BootNotificationCreatorTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.deviceconfig; + +import android.content.Context; +import android.app.AlarmManager; +import android.content.ContextWrapper; +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.Properties; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.junit.Test; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; + +@RunWith(AndroidJUnit4.class) +public class BootNotificationCreatorTest { + Context mockContext; + AlarmManager mockAlarmManager; + + BootNotificationCreator bootNotificationCreator; + + @Before + public void setUp() { + mockAlarmManager = mock(AlarmManager.class); + mockContext = new ContextWrapper(getInstrumentation().getTargetContext()) { + @Override + public Object getSystemService(String name) { + if (name.equals(Context.ALARM_SERVICE)) { + return mockAlarmManager; + } else { + return super.getSystemService(name); + } + } + }; + + Map<String, Set<String>> testAconfigFlags = new HashMap<>(); + testAconfigFlags.put("test", new HashSet<>()); + testAconfigFlags.get("test").add("flag"); + + bootNotificationCreator = new BootNotificationCreator(mockContext, testAconfigFlags); + } + + @Test + public void testNotificationScheduledWhenAconfigFlagStaged() { + HashMap<String, String> flags = new HashMap(); + flags.put("test*flag", "value"); + Properties properties = new Properties("staged", flags); + + bootNotificationCreator.onPropertiesChanged(properties); + + Mockito.verify(mockAlarmManager).setExact(anyInt(), anyLong(), any()); + } + + @Test + public void testNotificationNotScheduledForNonAconfigFlag() { + HashMap<String, String> flags = new HashMap(); + flags.put("not_aconfig*flag", "value"); + Properties properties = new Properties("staged", flags); + + bootNotificationCreator.onPropertiesChanged(properties); + + Mockito.verify(mockAlarmManager, times(0)).setExact(anyInt(), anyLong(), any()); + } +} diff --git a/service/javatests/src/com/android/server/deviceconfig/DeviceConfigBootstrapValuesTest.java b/service/javatests/src/com/android/server/deviceconfig/DeviceConfigBootstrapValuesTest.java new file mode 100644 index 0000000..9d77e8c --- /dev/null +++ b/service/javatests/src/com/android/server/deviceconfig/DeviceConfigBootstrapValuesTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.deviceconfig; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; + +import android.provider.DeviceConfig; + +import com.android.modules.utils.build.SdkLevel; + +import com.android.server.deviceconfig.DeviceConfigBootstrapValues; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import java.io.IOException; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class DeviceConfigBootstrapValuesTest { + private static final String WRITE_DEVICE_CONFIG_PERMISSION = + "android.permission.WRITE_DEVICE_CONFIG"; + + private static final String READ_DEVICE_CONFIG_PERMISSION = + "android.permission.READ_DEVICE_CONFIG"; + + private static final String PATH_1 = "file:///data/local/tmp/deviceconfig/bootstrap1.txt"; + + @Test + public void assertParsesFiles() throws IOException { + assumeTrue(SdkLevel.isAtLeastV()); + InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( + WRITE_DEVICE_CONFIG_PERMISSION, READ_DEVICE_CONFIG_PERMISSION); + + DeviceConfigBootstrapValues values = new DeviceConfigBootstrapValues(PATH_1); + values.applyValuesIfNeeded(); + + assertTrue(DeviceConfig.getBoolean("a.a.a", "b.b.b", false)); + assertFalse(DeviceConfig.getBoolean("a.a.a", "b.b", true)); + assertTrue(DeviceConfig.getBoolean("b.b.b", "c.c", false)); + assertEquals(2, DeviceConfig.getProperties("a.a.a").getKeyset().size()); + assertEquals(1, DeviceConfig.getProperties("b.b.b").getKeyset().size()); + } +} 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 new file mode 100644 index 0000000..0ddbc64 --- /dev/null +++ b/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java @@ -0,0 +1,408 @@ +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; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +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; +import android.content.BroadcastReceiver; +import android.content.ContextWrapper; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; +import android.util.Log; + +import androidx.test.filters.SmallTest; +import java.time.ZoneId; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +@SmallTest +public class UnattendedRebootManagerTest { + + private static final String TAG = "UnattendedRebootManagerTest"; + + private static final int REBOOT_FREQUENCY = 1; + private static final int REBOOT_START_HOUR = 2; + private static final int REBOOT_END_HOUR = 3; + + private static final long CURRENT_TIME = 1696452549304L; // 2023-10-04T13:49:09.304 + private static final long REBOOT_TIME = 1696497120000L; // 2023-10-05T02:12:00 + private static final long RESCHEDULED_REBOOT_TIME = 1696583520000L; // 2023-10-06T02:12:00 + private static final long OUTSIDE_WINDOW_REBOOT_TIME = 1696587000000L; // 2023-10-06T03:10:00 + private static final long RESCHEDULED_OUTSIDE_WINDOW_REBOOT_TIME = + 1696669920000L; // 2023-10-07T02:12:00 + private static final long ELAPSED_REALTIME_1_DAY = 86400000L; + + private Context mContext; + private KeyguardManager mKeyguardManager; + 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); + + mContext = + new ContextWrapper(getInstrumentation().getTargetContext()) { + @Override + public Object getSystemService(String name) { + if (name.equals(Context.KEYGUARD_SERVICE)) { + return mKeyguardManager; + } else if (name.equals(Context.CONNECTIVITY_SERVICE)) { + return mConnectivityManager; + } + return super.getSystemService(name); + } + }; + + mFakeInjector = new FakeInjector(); + mRebootManager = new UnattendedRebootManager(mContext, mFakeInjector, mSimPinReplayManager); + + // Need to register receiver in tests so that the test doesn't trigger reboot requested by + // deviceconfig. + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mRebootManager.tryRebootOrSchedule(); + } + }, + new IntentFilter(ACTION_TRIGGER_REBOOT), + Context.RECEIVER_EXPORTED); + + mFakeInjector.setElapsedRealtime(ELAPSED_REALTIME_1_DAY); + } + + @Test + public void scheduleReboot() { + 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(true); + + mRebootManager.prepareUnattendedReboot(); + mRebootManager.scheduleReboot(); + + assertTrue(mFakeInjector.isRebootAndApplied()); + assertFalse(mFakeInjector.isRegularRebooted()); + assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME); + } + + @Test + public void scheduleReboot_noPinLock() { + Log.i(TAG, "scheduleReboot_noPinLock"); + when(mKeyguardManager.isDeviceSecure()).thenReturn(false); + when(mConnectivityManager.getNetworkCapabilities(any())) + .thenReturn( + new NetworkCapabilities.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + .build()); + when(mSimPinReplayManager.prepareSimPinReplay()).thenReturn(true); + + mRebootManager.prepareUnattendedReboot(); + mRebootManager.scheduleReboot(); + + assertFalse(mFakeInjector.isRebootAndApplied()); + assertTrue(mFakeInjector.isRegularRebooted()); + assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME); + } + + @Test + public void scheduleReboot_noPreparation() { + Log.i(TAG, "scheduleReboot_noPreparation"); + 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(true); + + mRebootManager.scheduleReboot(); + + assertFalse(mFakeInjector.isRebootAndApplied()); + assertFalse(mFakeInjector.isRegularRebooted()); + assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(RESCHEDULED_REBOOT_TIME); + } + + @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(); + + assertFalse(mFakeInjector.isRebootAndApplied()); + assertFalse(mFakeInjector.isRegularRebooted()); + assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME); + assertTrue(mFakeInjector.isRequestedNetwork()); + } + + @Test + public void scheduleReboot_noInternetValidation() { + Log.i(TAG, "scheduleReboot_noInternetValidation"); + when(mKeyguardManager.isDeviceSecure()).thenReturn(true); + when(mConnectivityManager.getNetworkCapabilities(any())) + .thenReturn( + new NetworkCapabilities.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build()); + when(mSimPinReplayManager.prepareSimPinReplay()).thenReturn(true); + + mRebootManager.prepareUnattendedReboot(); + mRebootManager.scheduleReboot(); + + assertFalse(mFakeInjector.isRebootAndApplied()); + assertFalse(mFakeInjector.isRegularRebooted()); + assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME); + assertTrue(mFakeInjector.isRequestedNetwork()); + } + + @Test + public void scheduleReboot_elapsedRealtimeLessThanFrequency() { + Log.i(TAG, "scheduleReboot_elapsedRealtimeLessThanFrequency"); + 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(true); + mFakeInjector.setElapsedRealtime(82800000); // 23 hours + + mRebootManager.prepareUnattendedReboot(); + mRebootManager.scheduleReboot(); + + assertFalse(mFakeInjector.isRebootAndApplied()); + assertFalse(mFakeInjector.isRegularRebooted()); + assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(RESCHEDULED_REBOOT_TIME); + } + + @Test + public void tryRebootOrSchedule_outsideRebootWindow() { + Log.i(TAG, "scheduleReboot_internetOutsideRebootWindow"); + 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(true); + mFakeInjector.setNow(OUTSIDE_WINDOW_REBOOT_TIME); + + mRebootManager.prepareUnattendedReboot(); + // Simulating case when reboot is tried after network connection is established outside the + // reboot window. + mRebootManager.tryRebootOrSchedule(); + + assertTrue(mFakeInjector.isRebootAndApplied()); + assertFalse(mFakeInjector.isRegularRebooted()); + assertThat(mFakeInjector.getActualRebootTime()) + .isEqualTo(RESCHEDULED_OUTSIDE_WINDOW_REBOOT_TIME); + } + + static class FakeInjector implements UnattendedRebootManagerInjector { + + private boolean isPreparedForUnattendedReboot; + private boolean rebootAndApplied; + private boolean regularRebooted; + private boolean requestedNetwork; + private long actualRebootTime; + private boolean scheduledReboot; + + private long nowMillis; + + private long elapsedRealtimeMillis; + + FakeInjector() { + nowMillis = CURRENT_TIME; + } + + @Override + public void prepareForUnattendedUpdate( + @NonNull Context context, + @NonNull String updateToken, + @Nullable IntentSender intentSender) { + context.sendBroadcast(new Intent(ACTION_RESUME_ON_REBOOT_LSKF_CAPTURED)); + isPreparedForUnattendedReboot = true; + } + + @Override + public boolean isPreparedForUnattendedUpdate(@NonNull Context context) { + return isPreparedForUnattendedReboot; + } + + @Override + public int rebootAndApply( + @NonNull Context context, @NonNull String reason, boolean slotSwitch) { + rebootAndApplied = true; + return 0; // No error. + } + + @Override + public int getRebootFrequency() { + return REBOOT_FREQUENCY; + } + + @Override + public void setRebootAlarm(Context context, long rebootTimeMillis) { + // To prevent infinite loop, do not simulate another reboot if reboot was already scheduled. + if (scheduledReboot) { + actualRebootTime = rebootTimeMillis; + return; + } + // Advance now to reboot time and reboot immediately. + scheduledReboot = true; + actualRebootTime = rebootTimeMillis; + setNow(rebootTimeMillis); + + LatchingBroadcastReceiver rebootReceiver = new LatchingBroadcastReceiver(); + + // Wait for reboot broadcast to be sent. + context.sendOrderedBroadcast( + new Intent(ACTION_TRIGGER_REBOOT), null, rebootReceiver, null, 0, null, null); + + rebootReceiver.await(20, TimeUnit.SECONDS); + } + + @Override + public void triggerRebootOnNetworkAvailable(Context context) { + requestedNetwork = true; + } + + public boolean isRequestedNetwork() { + return requestedNetwork; + } + + @Override + public int getRebootStartTime() { + return REBOOT_START_HOUR; + } + + @Override + public int getRebootEndTime() { + return REBOOT_END_HOUR; + } + + @Override + public long now() { + return nowMillis; + } + + public void setNow(long nowMillis) { + this.nowMillis = nowMillis; + } + + @Override + public ZoneId zoneId() { + return ZoneId.of("America/Los_Angeles"); + } + + @Override + public long elapsedRealtime() { + return elapsedRealtimeMillis; + } + + public void setElapsedRealtime(long elapsedRealtimeMillis) { + this.elapsedRealtimeMillis = elapsedRealtimeMillis; + } + + @Override + public void regularReboot(Context context) { + regularRebooted = true; + } + + boolean isRebootAndApplied() { + return rebootAndApplied; + } + + boolean isRegularRebooted() { + return regularRebooted; + } + + public long getActualRebootTime() { + return actualRebootTime; + } + } + + /** + * A {@link BroadcastReceiver} with an internal latch that unblocks once any intent is received. + */ + private static class LatchingBroadcastReceiver extends BroadcastReceiver { + private CountDownLatch latch = new CountDownLatch(1); + + @Override + public void onReceive(Context context, Intent intent) { + latch.countDown(); + } + + public boolean await(long timeoutInMs, TimeUnit timeUnit) { + try { + return latch.await(timeoutInMs, timeUnit); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } +} |