Add unit test for CarrierConfigLoader
Change CarrierConfigLoader a bit to make it more testable:
1. Inject SubscriptionInfoUpdater into CarrierConfigLoader to make
it both mockable and verifible.
2. Inject Looper into CarrirConfigLoader to take advantage of
TestableLooper in UT.
3. Make configs readable to test case to verify the values
(intead of verifying the behavior).
Bug: 182922049
Test: atest com.android.phone.CarrierConfigLoaderTest
Test: atest
CtsVcnTestCases: android.net.vcn.cts.VcnManagerTest -- --abi x86_64
Change-Id: Ieb4ff6954cf6baa5bf598257dd76103b1294f3b2
diff --git a/tests/src/com/android/TestContext.java b/tests/src/com/android/TestContext.java
index 9d712d3..fc5ee4c 100644
--- a/tests/src/com/android/TestContext.java
+++ b/tests/src/com/android/TestContext.java
@@ -24,6 +24,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Binder;
import android.os.Handler;
import android.os.PersistableBundle;
import android.telecom.TelecomManager;
@@ -32,16 +34,22 @@
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsManager;
import android.test.mock.MockContext;
+import android.util.Log;
import android.util.SparseArray;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
+import java.util.HashSet;
import java.util.concurrent.Executor;
public class TestContext extends MockContext {
+ private static final String TAG = "TestContext";
+ // Stub used to grant all permissions
+ public static final String STUB_PERMISSION_ENABLE_ALL = "stub_permission_enable_all";
+
@Mock CarrierConfigManager mMockCarrierConfigManager;
@Mock TelecomManager mMockTelecomManager;
@Mock TelephonyManager mMockTelephonyManager;
@@ -50,6 +58,8 @@
private SparseArray<PersistableBundle> mCarrierConfigs = new SparseArray<>();
+ private final HashSet<String> mPermissionTable = new HashSet<>();
+
public TestContext() {
MockitoAnnotations.initMocks(this);
doAnswer((Answer<PersistableBundle>) invocation -> {
@@ -161,4 +171,67 @@
}
return b;
}
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ if (checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(permission + " denied: " + message);
+ }
+ }
+
+ @Override
+ public void enforcePermission(String permission, int pid, int uid, String message) {
+ enforceCallingOrSelfPermission(permission, message);
+ }
+
+ @Override
+ public void enforceCallingPermission(String permission, String message) {
+ enforceCallingOrSelfPermission(permission, message);
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String permission) {
+ return checkPermission(permission, Binder.getCallingPid(), Binder.getCallingUid());
+ }
+
+ @Override
+ public int checkPermission(String permission, int pid, int uid) {
+ synchronized (mPermissionTable) {
+ if (mPermissionTable.contains(permission)
+ || mPermissionTable.contains(STUB_PERMISSION_ENABLE_ALL)) {
+ logd("checkCallingOrSelfPermission: " + permission + " return GRANTED");
+ return PackageManager.PERMISSION_GRANTED;
+ } else {
+ logd("checkCallingOrSelfPermission: " + permission + " return DENIED");
+ return PackageManager.PERMISSION_DENIED;
+ }
+ }
+ }
+
+ public void grantPermission(String permission) {
+ synchronized (mPermissionTable) {
+ if (mPermissionTable != null && permission != null) {
+ mPermissionTable.remove(STUB_PERMISSION_ENABLE_ALL);
+ mPermissionTable.add(permission);
+ }
+ }
+ }
+
+ public void revokePermission(String permission) {
+ synchronized (mPermissionTable) {
+ if (mPermissionTable != null && permission != null) {
+ mPermissionTable.remove(permission);
+ }
+ }
+ }
+
+ public void revokeAllPermissions() {
+ synchronized (mPermissionTable) {
+ mPermissionTable.clear();
+ }
+ }
+
+ private static void logd(String s) {
+ Log.d(TAG, s);
+ }
}
diff --git a/tests/src/com/android/phone/CarrierConfigLoaderTest.java b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
new file mode 100644
index 0000000..b7d1d70
--- /dev/null
+++ b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2021 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.phone;
+
+import static com.android.TestContext.STUB_PERMISSION_ENABLE_ALL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.service.carrier.CarrierIdentifier;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.testing.TestableLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.SubscriptionInfoUpdater;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+
+/**
+ * Unit Test for CarrierConfigLoader.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarrierConfigLoaderTest extends TelephonyTestBase {
+
+ private static final int DEFAULT_PHONE_ID = 0;
+ private static final int DEFAULT_SUB_ID = SubscriptionManager.getDefaultSubscriptionId();
+ private static final String PLATFORM_CARRIER_CONFIG_PACKAGE = "com.android.carrierconfig";
+ private static final long PLATFORM_CARRIER_CONFIG_PACKAGE_VERSION_CODE = 1;
+ private static final String CARRIER_CONFIG_EXAMPLE_KEY =
+ CarrierConfigManager.KEY_CARRIER_USSD_METHOD_INT;
+ private static final int CARRIER_CONFIG_EXAMPLE_VALUE =
+ CarrierConfigManager.USSD_OVER_CS_PREFERRED;
+
+ @Mock Resources mResources;
+ @Mock PackageManager mPackageManager;
+ @Mock PackageInfo mPackageInfo;
+ @Mock SubscriptionInfoUpdater mSubscriptionInfoUpdater;
+ @Mock SharedPreferences mSharedPreferences;
+
+ private TelephonyManager mTelephonyManager;
+ private CarrierConfigLoader mCarrierConfigLoader;
+ private Handler mHandler;
+ private HandlerThread mHandlerThread;
+ private TestableLooper mTestableLooper;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ doReturn(mSharedPreferences).when(mContext).getSharedPreferences(anyString(), anyInt());
+ doReturn(Build.FINGERPRINT).when(mSharedPreferences).getString(eq("build_fingerprint"),
+ any());
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ doReturn(mResources).when(mContext).getResources();
+ doReturn(InstrumentationRegistry.getTargetContext().getFilesDir()).when(
+ mContext).getFilesDir();
+ doReturn(PLATFORM_CARRIER_CONFIG_PACKAGE).when(mResources).getString(
+ eq(R.string.platform_carrier_config_package));
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ doReturn(1).when(mTelephonyManager).getSupportedModemCount();
+ doReturn(1).when(mTelephonyManager).getActiveModemCount();
+ doReturn("spn").when(mTelephonyManager).getSimOperatorNameForPhone(anyInt());
+ doReturn("310260").when(mTelephonyManager).getSimOperatorNumericForPhone(anyInt());
+ doReturn(mPackageInfo).when(mPackageManager).getPackageInfo(
+ eq(PLATFORM_CARRIER_CONFIG_PACKAGE), eq(0) /*flags*/);
+ doReturn(PLATFORM_CARRIER_CONFIG_PACKAGE_VERSION_CODE).when(
+ mPackageInfo).getLongVersionCode();
+
+ mHandlerThread = new HandlerThread("CarrierConfigLoaderTest");
+ mHandlerThread.start();
+
+ mTestableLooper = new TestableLooper(mHandlerThread.getLooper());
+ mCarrierConfigLoader = new CarrierConfigLoader(mContext, mSubscriptionInfoUpdater,
+ mTestableLooper.getLooper());
+ mHandler = mCarrierConfigLoader.getHandler();
+
+ // Clear all configs to have the same starting point.
+ mCarrierConfigLoader.clearConfigForPhone(DEFAULT_PHONE_ID, false);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mContext.revokeAllPermissions();
+ mTestableLooper.destroy();
+ super.tearDown();
+ }
+
+ /**
+ * Verifies that SecurityException should throw when call #updateConfigForPhoneId() without
+ * MODIFY_PHONE_STATE permission.
+ */
+ @Test
+ public void testUpdateConfigForPhoneId_noPermission() throws Exception {
+ assertThrows(SecurityException.class,
+ () -> mCarrierConfigLoader.updateConfigForPhoneId(DEFAULT_PHONE_ID,
+ IccCardConstants.INTENT_VALUE_ICC_ABSENT));
+ }
+
+ /**
+ * Verifies that when call #updateConfigForPhoneId() with SIM absence, both carrier config from
+ * default app and carrier should be cleared but no-sim config should be loaded.
+ */
+ @Test
+ public void testUpdateConfigForPhoneId_simAbsent() throws Exception {
+ // Bypass case if default subId is not supported by device to reduce flakiness
+ if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
+ return;
+ }
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
+
+ // Prepare a cached config to fetch from xml
+ PersistableBundle config = getTestConfig();
+ mCarrierConfigLoader.saveNoSimConfigToXml(PLATFORM_CARRIER_CONFIG_PACKAGE, config);
+ mCarrierConfigLoader.updateConfigForPhoneId(DEFAULT_PHONE_ID,
+ IccCardConstants.INTENT_VALUE_ICC_ABSENT);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mCarrierConfigLoader.getConfigFromDefaultApp(DEFAULT_PHONE_ID)).isNull();
+ assertThat(mCarrierConfigLoader.getConfigFromCarrierApp(DEFAULT_PHONE_ID)).isNull();
+ assertThat(mCarrierConfigLoader.getNoSimConfig().getInt(CARRIER_CONFIG_EXAMPLE_KEY))
+ .isEqualTo(CARRIER_CONFIG_EXAMPLE_VALUE);
+ verify(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
+ }
+
+ /**
+ * Verifies that with cached config in XML, calling #updateConfigForPhoneId() with SIM loaded
+ * will return the right config in the XML.
+ */
+ @Test
+ public void testUpdateConfigForPhoneId_simLoaded_withCachedConfigInXml() throws Exception {
+ // Bypass case if default subId is not supported by device to reduce flakiness
+ if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
+ return;
+ }
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+
+ // Prepare to make sure we can save the config into the XML file which used as cache
+ List<String> carrierPackages = List.of(PLATFORM_CARRIER_CONFIG_PACKAGE);
+ doReturn(carrierPackages).when(mTelephonyManager).getCarrierPackageNamesForIntentAndPhone(
+ nullable(Intent.class), anyInt());
+
+ // Save the sample config into the XML file
+ PersistableBundle config = getTestConfig();
+ CarrierIdentifier carrierId = mCarrierConfigLoader.getCarrierIdentifierForPhoneId(
+ DEFAULT_PHONE_ID);
+ mCarrierConfigLoader.saveConfigToXml(PLATFORM_CARRIER_CONFIG_PACKAGE, "",
+ DEFAULT_PHONE_ID, carrierId, config);
+ mCarrierConfigLoader.updateConfigForPhoneId(DEFAULT_PHONE_ID,
+ IccCardConstants.INTENT_VALUE_ICC_LOADED);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mCarrierConfigLoader.getConfigFromDefaultApp(DEFAULT_PHONE_ID).getInt(
+ CARRIER_CONFIG_EXAMPLE_KEY)).isEqualTo(CARRIER_CONFIG_EXAMPLE_VALUE);
+
+ }
+
+ /**
+ * Verifies that SecurityException should throw if call #overrideConfig() without
+ * MODIFY_PHONE_STATE permission.
+ */
+ @Test
+ public void testOverrideConfig_noPermission() throws Exception {
+ assertThrows(SecurityException.class,
+ () -> mCarrierConfigLoader.overrideConfig(DEFAULT_SUB_ID, PersistableBundle.EMPTY,
+ false));
+ }
+
+ /**
+ * Verifies that override config is not null when calling #overrideConfig with null bundle.
+ */
+ @Test
+ public void testOverrideConfig_withNullBundle() throws Exception {
+ // Bypass case if default subId is not supported by device to reduce flakiness
+ if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
+ return;
+ }
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+
+ mCarrierConfigLoader.overrideConfig(DEFAULT_SUB_ID, null /*overrides*/,
+ false/*persistent*/);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mCarrierConfigLoader.getOverrideConfig(DEFAULT_PHONE_ID).isEmpty()).isTrue();
+ verify(mSubscriptionInfoUpdater).updateSubscriptionByCarrierConfigAndNotifyComplete(
+ eq(DEFAULT_PHONE_ID), eq(PLATFORM_CARRIER_CONFIG_PACKAGE),
+ any(PersistableBundle.class), any(Message.class));
+ }
+
+ /**
+ * Verifies that override config is not null when calling #overrideConfig with non-null bundle.
+ */
+ @Test
+ public void testOverrideConfig_withNonNullBundle() throws Exception {
+ // Bypass case if default subId is not supported by device to reduce flakiness
+ if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
+ return;
+ }
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+
+ PersistableBundle config = getTestConfig();
+ mCarrierConfigLoader.overrideConfig(DEFAULT_SUB_ID, config /*overrides*/,
+ false/*persistent*/);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mCarrierConfigLoader.getOverrideConfig(DEFAULT_PHONE_ID).getInt(
+ CARRIER_CONFIG_EXAMPLE_KEY)).isEqualTo(CARRIER_CONFIG_EXAMPLE_VALUE);
+ verify(mSubscriptionInfoUpdater).updateSubscriptionByCarrierConfigAndNotifyComplete(
+ eq(DEFAULT_PHONE_ID), eq(PLATFORM_CARRIER_CONFIG_PACKAGE),
+ any(PersistableBundle.class), any(Message.class));
+ }
+
+ /**
+ * Verifies that calling #notifyConfigChangedForSubId() with invalid subId will be ignored.
+ */
+ @Test
+ public void testNotifyConfigChangedForSubId_invalidSubId() throws Exception {
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+
+ mCarrierConfigLoader.notifyConfigChangedForSubId(
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+ // verifying the behavior following the permission check is not actually performed.
+ // It against "verify value instead of behavior", but we don't have other alternatives.
+ verify(mPackageManager, never()).getNameForUid(anyInt());
+ verify(mContext, never()).checkCallingOrSelfPermission(
+ eq(android.Manifest.permission.MODIFY_PHONE_STATE));
+ }
+
+ // TODO(b/184040111): Enable test case when support disabling carrier privilege
+ // Phone/System UID always has carrier privilege (TelephonyPermission#getCarrierPrivilegeStatus)
+ // when running the test here.
+ /**
+ * Verifies that SecurityException should throw when calling notifyConfigChangedForSubId without
+ * MODIFY_PHONE_STATE permission.
+ */
+ @Ignore
+ public void testNotifyConfigChangedForSubId_noPermission() throws Exception {
+ setCarrierPrivilegesForSubId(false, DEFAULT_SUB_ID);
+
+ assertThrows(SecurityException.class,
+ () -> mCarrierConfigLoader.notifyConfigChangedForSubId(DEFAULT_SUB_ID));
+ }
+
+ /**
+ * Verifies that SecurityException should throw when calling getDefaultCarrierServicePackageName
+ * without READ_PRIVILEGED_PHONE_STATE permission.
+ */
+ @Test
+ public void testGetDefaultCarrierServicePackageName_noPermission() {
+ assertThrows(SecurityException.class,
+ () -> mCarrierConfigLoader.getDefaultCarrierServicePackageName());
+ }
+
+ /**
+ * Verifies that the right default carrier service package name is return when calling
+ * getDefaultCarrierServicePackageName with permission.
+ */
+ @Test
+ public void testGetDefaultCarrierServicePackageName_withPermission() {
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+
+ assertThat(mCarrierConfigLoader.getDefaultCarrierServicePackageName())
+ .isEqualTo(PLATFORM_CARRIER_CONFIG_PACKAGE);
+ }
+
+ // TODO(b/184040111): Enable test case when support disabling carrier privilege
+ // Phone/System UID always has carrier privilege (TelephonyPermission#getCarrierPrivilegeStatus)
+ // when running the test here.
+ /**
+ * Verifies that without permission, #getConfigForSubId will return an empty PersistableBundle.
+ */
+ @Ignore
+ public void testGetConfigForSubId_noPermission() {
+ // Bypass case if default subId is not supported by device to reduce flakiness
+ if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
+ return;
+ }
+ setCarrierPrivilegesForSubId(false, DEFAULT_SUB_ID);
+
+ assertThat(mCarrierConfigLoader.getConfigForSubId(DEFAULT_SUB_ID,
+ PLATFORM_CARRIER_CONFIG_PACKAGE)).isEqualTo(PersistableBundle.EMPTY);
+ }
+
+ /**
+ * Verifies that when have no DUMP permission, the #dump() method shows permission denial.
+ */
+ @Test
+ public void testDump_noPermission() {
+ StringWriter stringWriter = new StringWriter();
+ mCarrierConfigLoader.dump(new FileDescriptor(), new PrintWriter(stringWriter),
+ new String[0]);
+ stringWriter.flush();
+
+ assertThat(stringWriter.toString()).contains("Permission Denial:");
+ }
+
+ /**
+ * Verifies that when have DUMP permission, the #dump() method can dump the CarrierConfigLoader.
+ */
+ @Test
+ public void testDump_withPermission() {
+ mContext.grantPermission(android.Manifest.permission.DUMP);
+
+ StringWriter stringWriter = new StringWriter();
+ mCarrierConfigLoader.dump(new FileDescriptor(), new PrintWriter(stringWriter),
+ new String[0]);
+ stringWriter.flush();
+
+ String dumpContent = stringWriter.toString();
+ assertThat(dumpContent).contains("CarrierConfigLoader:");
+ assertThat(dumpContent).doesNotContain("Permission Denial:");
+ }
+
+ private static PersistableBundle getTestConfig() {
+ PersistableBundle config = new PersistableBundle();
+ config.putInt(CARRIER_CONFIG_EXAMPLE_KEY, CARRIER_CONFIG_EXAMPLE_VALUE);
+ return config;
+ }
+
+ private void setCarrierPrivilegesForSubId(boolean hasCarrierPrivileges, int subId) {
+ TelephonyManager mockTelephonyManager = Mockito.mock(TelephonyManager.class);
+ doReturn(mockTelephonyManager).when(mTelephonyManager).createForSubscriptionId(subId);
+ doReturn(hasCarrierPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
+ : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS).when(
+ mockTelephonyManager).getCarrierPrivilegeStatus(anyInt());
+ }
+}