diff options
Diffstat (limited to 'service/javatests')
8 files changed, 886 insertions, 0 deletions
diff --git a/service/javatests/Android.bp b/service/javatests/Android.bp new file mode 100644 index 0000000..0177890 --- /dev/null +++ b/service/javatests/Android.bp @@ -0,0 +1,59 @@ +// 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. + +filegroup { + name: "service-configinfrastructure-unitttests-bootstrap-files", + srcs: [ + "data/*", + ], +} + +android_test { + name: "ConfigInfrastructureServiceUnitTests", + min_sdk_version: "34", + sdk_version: "module_current", + manifest: "AndroidManifest.xml", + test_config: "AndroidTest.xml", + data: [ + ":service-configinfrastructure-unitttests-bootstrap-files", + ], + srcs: [ + "src/**/*.java", + ], + test_suites: [ + "mts-configinfrastructure", + "general-tests", + ], + static_libs: [ + "androidx.test.rules", + "androidx.test.runner", + "androidx.annotation_annotation", + "modules-utils-build", + "service-configinfrastructure.impl", + "frameworks-base-testutils", + "mockito-target-minus-junit4", + "truth", + "flag-junit", + ], + libs: [ + "android.test.base", + "android.test.mock", + "android.test.runner", + "framework-connectivity.stubs.module_lib", + "framework-configinfrastructure", + ], + // Test coverage system runs on different devices. Need to + // compile for all architecture. + compile_multilib: "both", +} diff --git a/service/javatests/AndroidManifest.xml b/service/javatests/AndroidManifest.xml new file mode 100644 index 0000000..663e401 --- /dev/null +++ b/service/javatests/AndroidManifest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.deviceconfig"> + + <uses-sdk android:minSdkVersion="34" android:targetSdkVersion="34" /> + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> + <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.deviceconfig" + android:label="Tests for DeviceConfig service"> + </instrumentation> + +</manifest> diff --git a/service/javatests/AndroidTest.xml b/service/javatests/AndroidTest.xml new file mode 100644 index 0000000..9c69017 --- /dev/null +++ b/service/javatests/AndroidTest.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<configuration description="Config for DeviceConfig service test cases"> + <option name="test-suite-tag" value="mts" /> + <option name="config-descriptor:metadata" key="component" value="service" /> + <option name="config-descriptor:metadata" key="parameter" value="multi_abi" /> + <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> + <option name="config-descriptor:metadata" key="parameter" value="secondary_user" /> + <option name="config-descriptor:metadata" key="mainline-param" + value="com.google.android.configinfrastructure.apex" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="ConfigInfrastructureServiceUnitTests.apk" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="settings put global device_config_sync_disabled 0" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push-file" key="bootstrap1.txt" + value="/data/local/tmp/deviceconfig/bootstrap1.txt" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.server.deviceconfig" /> + <option name="runtime-hint" value="1m" /> + </test> + <object type="module_controller" + class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> + <option name="mainline-module-package-name" + value="com.google.android.configinfrastructure" /> + </object> +</configuration>
\ No newline at end of file diff --git a/service/javatests/data/bootstrap1.txt b/service/javatests/data/bootstrap1.txt new file mode 100644 index 0000000..3349679 --- /dev/null +++ b/service/javatests/data/bootstrap1.txt @@ -0,0 +1,3 @@ +a.a.a:b.b.b=enabled +a.a.a:b.b=disabled +b.b.b:c.c=enabled
\ No newline at end of file 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); + } + } + } +} |