Disable carrier apps until needed per the SIM.
Jeff Davidson [Fri, 19 Jun 2015 19:44:56 +0000 (12:44 -0700)]
Similar to how InputMethodManagerService marks IME apps as
DISABLED_UNTIL_USED except for apps being used as IMEs, we mark
carrier apps bundled with the system as DISABLED_UNTIL_USED until a
SIM that marks those apps as privileged is inserted.

Bug: 21814643
Change-Id: I24006039c18f2b0ad01019d3bb27c1344f97d53c

src/java/com/android/internal/telephony/CarrierAppUtils.java [new file with mode: 0644]
src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
tests/telephonytests/Android.mk
tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java [new file with mode: 0644]

diff --git a/src/java/com/android/internal/telephony/CarrierAppUtils.java b/src/java/com/android/internal/telephony/CarrierAppUtils.java
new file mode 100644 (file)
index 0000000..de7fc40
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 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.internal.telephony;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Utilities for handling carrier applications.
+ * @hide
+ */
+public final class CarrierAppUtils {
+    private static final String TAG = "CarrierAppUtils";
+
+    private static final boolean DEBUG = false; // STOPSHIP if true
+
+    private CarrierAppUtils() {}
+
+    /**
+     * Handle preinstalled carrier apps which should be disabled until a matching SIM is inserted.
+     *
+     * Evaluates the list of applications in config_disabledUntilUsedPreinstalledCarrierApps. We
+     * want to disable each such application which is present on the system image until the user
+     * inserts a SIM which causes that application to gain carrier privilege (indicating a "match"),
+     * without interfering with the user if they opt to enable/disable the app explicitly.
+     *
+     * So, for each such app, we either disable until used IFF the app is not carrier privileged AND
+     * in the default state (e.g. not explicitly DISABLED/DISABLED_BY_USER/ENABLED), or we enable if
+     * the app is carrier privileged and in either the default state or DISABLED_UNTIL_USED.
+     *
+     * This method is idempotent and is safe to be called at any time; it should be called once at
+     * system startup prior to any application running, as well as any time the set of carrier
+     * privileged apps may have changed.
+     */
+    public synchronized static void disableCarrierAppsUntilPrivileged(String callingPackage,
+            IPackageManager packageManager, TelephonyManager telephonyManager, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "disableCarrierAppsUntilPrivileged");
+        }
+        String[] systemCarrierAppsDisabledUntilUsed = Resources.getSystem().getStringArray(
+                com.android.internal.R.array.config_disabledUntilUsedPreinstalledCarrierApps);
+        disableCarrierAppsUntilPrivileged(callingPackage, packageManager, telephonyManager, userId,
+                systemCarrierAppsDisabledUntilUsed);
+    }
+
+    // Must be public b/c framework unit tests can't access package-private methods.
+    @VisibleForTesting
+    public static void disableCarrierAppsUntilPrivileged(String callingPackage,
+            IPackageManager packageManager, TelephonyManager telephonyManager, int userId,
+            String[] systemCarrierAppsDisabledUntilUsed) {
+        if (systemCarrierAppsDisabledUntilUsed == null
+                || systemCarrierAppsDisabledUntilUsed.length == 0) {
+            return;
+        }
+        try {
+            for (String packageName : systemCarrierAppsDisabledUntilUsed) {
+                ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
+                        PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId);
+                if (ai == null) {
+                    // No app found for packageName
+                    continue;
+                }
+                boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+                if (!isSystemPackage) {
+                    continue;
+                }
+                boolean hasPrivileges =
+                        telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) ==
+                                TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+                if (hasPrivileges
+                        && (ai.enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+                        || ai.enabledSetting ==
+                                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
+                    Slog.i(TAG, "Update state(" + packageName + "): ENABLED for user " + userId);
+                    packageManager.setApplicationEnabledSetting(packageName,
+                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userId,
+                            callingPackage);
+                } else if (!hasPrivileges
+                        && ai.enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+                    Slog.i(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED for user "
+                            + userId);
+                    packageManager.setApplicationEnabledSetting(packageName,
+                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0, userId,
+                            callingPackage);
+                }
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Could not reach PackageManager", e);
+        }
+    }
+}
index caf171e..f4398f0 100644 (file)
@@ -19,6 +19,7 @@ package com.android.internal.telephony;
 import static android.Manifest.permission.READ_PHONE_STATE;
 
 import android.app.ActivityManagerNative;
+import android.app.IUserSwitchObserver;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.ContentResolver;
@@ -26,9 +27,13 @@ import android.content.ContentValues;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.pm.IPackageManager;
 import android.os.AsyncResult;
 import android.os.Handler;
+import android.os.IRemoteCallback;
 import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.preference.PreferenceManager;
@@ -43,6 +48,7 @@ import com.android.internal.telephony.uicc.IccConstants;
 import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.IccUtils;
+
 import android.text.TextUtils;
 
 import java.util.List;
@@ -94,6 +100,9 @@ public class SubscriptionInfoUpdater extends Handler {
     private static String mIccId[] = new String[PROJECT_SIM_NUM];
     private static int[] mInsertSimState = new int[PROJECT_SIM_NUM];
     private SubscriptionManager mSubscriptionManager = null;
+    private IPackageManager mPackageManager;
+    // The current foreground user ID.
+    private int mCurrentlyActiveUserId;
 
     public SubscriptionInfoUpdater(Context context, Phone[] phoneProxy, CommandsInterface[] ci) {
         logd("Constructor invoked");
@@ -101,10 +110,55 @@ public class SubscriptionInfoUpdater extends Handler {
         mContext = context;
         mPhone = phoneProxy;
         mSubscriptionManager = SubscriptionManager.from(mContext);
+        mPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
 
         IntentFilter intentFilter = new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         intentFilter.addAction(IccCardProxy.ACTION_INTERNAL_SIM_STATE_CHANGED);
         mContext.registerReceiver(sReceiver, intentFilter);
+
+        initializeCarrierApps();
+    }
+
+    private void initializeCarrierApps() {
+        // Initialize carrier apps:
+        // -Now (on system startup)
+        // -Whenever new carrier privilege rules might change (new SIM is loaded)
+        // -Whenever we switch to a new user
+        mCurrentlyActiveUserId = 0;
+        try {
+            ActivityManagerNative.getDefault().registerUserSwitchObserver(
+                    new IUserSwitchObserver.Stub() {
+                @Override
+                public void onUserSwitching(int newUserId, IRemoteCallback reply)
+                        throws RemoteException {
+                    mCurrentlyActiveUserId = newUserId;
+                    CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
+                            mPackageManager, TelephonyManager.getDefault(), mCurrentlyActiveUserId);
+
+                    if (reply != null) {
+                        try {
+                            reply.sendResult(null);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                }
+
+                @Override
+                public void onUserSwitchComplete(int newUserId) {
+                    // Ignore.
+                }
+
+                @Override
+                public void onForegroundProfileSwitch(int newProfileId) throws RemoteException {
+                    // Ignore.
+                }
+            });
+            mCurrentlyActiveUserId = ActivityManagerNative.getDefault().getCurrentUser().id;
+        } catch (RemoteException e) {
+            logd("Couldn't get current user ID; guessing it's 0: " + e.getMessage());
+        }
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
+                mPackageManager, TelephonyManager.getDefault(), mCurrentlyActiveUserId);
     }
 
     private final BroadcastReceiver sReceiver = new  BroadcastReceiver() {
@@ -392,6 +446,10 @@ public class SubscriptionInfoUpdater extends Handler {
             logd("Invalid subId, could not update ContentResolver");
         }
 
+        // Update set of enabled carrier apps now that the privilege rules may have changed.
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
+                mPackageManager, TelephonyManager.getDefault(), mCurrentlyActiveUserId);
+
         broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_LOADED, null);
         updateCarrierConfig(slotId, IccCardConstants.INTENT_VALUE_ICC_LOADED);
     }
index 13363dc..96c5a9d 100644 (file)
@@ -8,6 +8,7 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files)
 #LOCAL_STATIC_JAVA_LIBRARIES := librilproto-java
 
 LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target
 
 LOCAL_PACKAGE_NAME := FrameworksTelephonyTests
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
new file mode 100644 (file)
index 0000000..330fb51
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2015 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.internal.telephony;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.telephony.TelephonyManager;
+import android.test.InstrumentationTestCase;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+public class CarrierAppUtilsTest extends InstrumentationTestCase {
+    private static final String CARRIER_APP = "com.example.carrier";
+    private static final String[] CARRIER_APPS = new String[] { CARRIER_APP };
+    private static final int USER_ID = 12345;
+    private static final String CALLING_PACKAGE = "phone";
+
+    @Mock private IPackageManager mPackageManager;
+    @Mock private TelephonyManager mTelephonyManager;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        System.setProperty("dexmaker.dexcache",
+                getInstrumentation().getTargetContext().getCacheDir().getPath());
+        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /** No apps configured - should do nothing. */
+    public void testDisableCarrierAppsUntilPrivileged_EmptyList() {
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                mTelephonyManager, USER_ID, new String[0]);
+        Mockito.verifyNoMoreInteractions(mPackageManager, mTelephonyManager);
+    }
+
+    /** Configured app is missing - should do nothing. */
+    public void testDisableCarrierAppsUntilPrivileged_MissingApp() throws Exception {
+        Mockito.when(mPackageManager.getApplicationInfo("com.example.missing.app",
+                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, USER_ID)).thenReturn(null);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                mTelephonyManager, USER_ID, new String[] { "com.example.missing.app" });
+        Mockito.verify(mPackageManager, Mockito.never()).setApplicationEnabledSetting(
+                Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt(),
+                Mockito.anyString());
+        Mockito.verifyNoMoreInteractions(mTelephonyManager);
+    }
+
+    /** Configured app is not bundled with the system - should do nothing. */
+    public void testDisableCarrierAppsUntilPrivileged_NonSystemApp() throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, USER_ID)).thenReturn(appInfo);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                mTelephonyManager, USER_ID, CARRIER_APPS);
+        Mockito.verify(mPackageManager, Mockito.never()).setApplicationEnabledSetting(
+                Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt(),
+                Mockito.anyString());
+        Mockito.verifyNoMoreInteractions(mTelephonyManager);
+    }
+
+    /** Configured app has privileges, but was disabled by the user - should do nothing. */
+    public void testDisableCarrierAppsUntilPrivileged_HasPrivileges_DisabledUser()
+            throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, USER_ID)).thenReturn(appInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                mTelephonyManager, USER_ID, CARRIER_APPS);
+        Mockito.verify(mPackageManager, Mockito.never()).setApplicationEnabledSetting(
+                Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt(),
+                Mockito.anyString());
+    }
+
+    /** Configured app has privileges, but was disabled - should do nothing. */
+    public void testDisableCarrierAppsUntilPrivileged_HasPrivileges_Disabled() throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, USER_ID)).thenReturn(appInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                mTelephonyManager, USER_ID, CARRIER_APPS);
+        Mockito.verify(mPackageManager, Mockito.never()).setApplicationEnabledSetting(
+                Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt(),
+                Mockito.anyString());
+    }
+
+    /** Configured app has privileges, and is already enabled - should do nothing. */
+    public void testDisableCarrierAppsUntilPrivileged_HasPrivileges_Enabled() throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, USER_ID)).thenReturn(appInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                mTelephonyManager, USER_ID, CARRIER_APPS);
+        Mockito.verify(mPackageManager, Mockito.never()).setApplicationEnabledSetting(
+                Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt(),
+                Mockito.anyString());
+    }
+
+    /** Configured app has privileges, and is in the default state - should enable. */
+    public void testDisableCarrierAppsUntilPrivileged_HasPrivileges_Default() throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, USER_ID)).thenReturn(appInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                mTelephonyManager, USER_ID, CARRIER_APPS);
+        Mockito.verify(mPackageManager).setApplicationEnabledSetting(
+                CARRIER_APP, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, USER_ID,
+                CALLING_PACKAGE);
+    }
+
+    /** Configured app has privileges, and is disabled until used - should enable. */
+    public void testDisableCarrierAppsUntilPrivileged_HasPrivileges_DisabledUntilUsed()
+            throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, USER_ID)).thenReturn(appInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                mTelephonyManager, USER_ID, CARRIER_APPS);
+        Mockito.verify(mPackageManager).setApplicationEnabledSetting(
+                CARRIER_APP, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, USER_ID,
+                CALLING_PACKAGE);
+    }
+
+    /** Configured app has no privileges, and was disabled by the user - should do nothing. */
+    public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_DisabledUser() throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, USER_ID)).thenReturn(appInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                mTelephonyManager, USER_ID, CARRIER_APPS);
+        Mockito.verify(mPackageManager, Mockito.never()).setApplicationEnabledSetting(
+                Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt(),
+                Mockito.anyString());
+    }
+
+    /** Configured app has no privileges, and was disabled - should do nothing. */
+    public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_Disabled() throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, USER_ID)).thenReturn(appInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                mTelephonyManager, USER_ID, CARRIER_APPS);
+        Mockito.verify(mPackageManager, Mockito.never()).setApplicationEnabledSetting(
+                Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt(),
+                Mockito.anyString());
+    }
+
+    /** Configured app has no privileges, and is explicitly enabled - should do nothing. */
+    public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_Enabled() throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, USER_ID)).thenReturn(appInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                mTelephonyManager, USER_ID, CARRIER_APPS);
+        Mockito.verify(mPackageManager, Mockito.never()).setApplicationEnabledSetting(
+                Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt(),
+                Mockito.anyString());
+    }
+
+    /** Configured app has no privileges, and is in the default state - should disable until use. */
+    public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_Default() throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, USER_ID)).thenReturn(appInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                mTelephonyManager, USER_ID, CARRIER_APPS);
+        Mockito.verify(mPackageManager).setApplicationEnabledSetting(
+                CARRIER_APP, PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0, USER_ID,
+                CALLING_PACKAGE);
+    }
+
+    /** Configured app has no privileges, and is disabled until used - should do nothing. */
+    public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_DisabledUntilUsed()
+            throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, USER_ID)).thenReturn(appInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                mTelephonyManager, USER_ID, CARRIER_APPS);
+        Mockito.verify(mPackageManager, Mockito.never()).setApplicationEnabledSetting(
+                Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt(),
+                Mockito.anyString());
+    }
+}
+