Add support for carrier "associated" apps.

The platform currently supports the notion of default carrier apps.
These apps are set to DISABLED_UNTIL_USED until a SIM is inserted
which grants them carrier privileges, at which point they are enabled.
Apps are not touched if they have been updated from the version on
/system or if their state has been modified externally (e.g. by the
user).

This CL extends this notion to associated apps, which may not have
carrier privileges themselves, but should be enabled/disabled
alongside a particular carrier app. This should include helper apps
that should not be visible to users who don't use the given carrier
unless the user explicitly enables the app.

As additional protection, we add a check to ensure that we never
disable apps after the first time we've run. Since we need to store
this information in secure settings, we also move the call site from
PackageManagerService#main() to PackageManagerService#systemReady(),
which enables use of secure settings but still occurs before
third-party apps can be started.

Bug: 30141427
Change-Id: Iee72ba4e70e5ca97999c9147a65af82c670a23e8
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7242043..77dac14 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6214,6 +6214,20 @@
         public static final int VR_DISPLAY_MODE_OFF = 1;
 
         /**
+         * Whether CarrierAppUtils#disableCarrierAppsUntilPrivileged has been executed at least
+         * once.
+         *
+         * <p>This is used to ensure that we only take one pass which will disable apps that are not
+         * privileged (if any). From then on, we only want to enable apps (when a matching SIM is
+         * inserted), to avoid disabling an app that the user might actively be using.
+         *
+         * <p>Will be set to 1 once executed.
+         *
+         * @hide
+         */
+        public static final String CARRIER_APPS_HANDLED = "carrier_apps_handled";
+
+        /**
          * Whether parent user can access remote contact in managed profile.
          *
          * @hide
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 886265a..429131b 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -42,6 +42,8 @@
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Loads global system configuration info.
@@ -122,6 +124,11 @@
     // These are the permitted backup transport service components
     final ArraySet<ComponentName> mBackupTransportWhitelist = new ArraySet<>();
 
+    // These are the packages of carrier-associated apps which should be disabled until used until
+    // a SIM is inserted which grants carrier privileges to that carrier app.
+    final ArrayMap<String, List<String>> mDisabledUntilUsedPreinstalledCarrierAssociatedApps =
+            new ArrayMap<>();
+
     public static SystemConfig getInstance() {
         synchronized (SystemConfig.class) {
             if (sInstance == null) {
@@ -183,6 +190,10 @@
         return mBackupTransportWhitelist;
     }
 
+    public ArrayMap<String, List<String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps() {
+        return mDisabledUntilUsedPreinstalledCarrierAssociatedApps;
+    }
+
     SystemConfig() {
         // Read configuration from system
         readPermissions(Environment.buildPath(
@@ -476,6 +487,26 @@
                         }
                     }
                     XmlUtils.skipCurrentTag(parser);
+                } else if ("disabled-until-used-preinstalled-carrier-associated-app".equals(name)
+                        && allowAppConfigs) {
+                    String pkgname = parser.getAttributeValue(null, "package");
+                    String carrierPkgname = parser.getAttributeValue(null, "carrierAppPackage");
+                    if (pkgname == null || carrierPkgname == null) {
+                        Slog.w(TAG, "<disabled-until-used-preinstalled-carrier-associated-app"
+                                + " without package or carrierAppPackage in " + permFile + " at "
+                                + parser.getPositionDescription());
+                    } else {
+                        List<String> associatedPkgs =
+                                mDisabledUntilUsedPreinstalledCarrierAssociatedApps.get(
+                                        carrierPkgname);
+                        if (associatedPkgs == null) {
+                            associatedPkgs = new ArrayList<>();
+                            mDisabledUntilUsedPreinstalledCarrierAssociatedApps.put(
+                                    carrierPkgname, associatedPkgs);
+                        }
+                        associatedPkgs.add(pkgname);
+                    }
+                    XmlUtils.skipCurrentTag(parser);
                 } else {
                     XmlUtils.skipCurrentTag(parser);
                     continue;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b907be0..c1d4784 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1963,10 +1963,6 @@
         PackageManagerService m = new PackageManagerService(context, installer,
                 factoryTest, onlyCore);
         m.enableSystemUserPackages();
-        // Disable any carrier apps. We do this very early in boot to prevent the apps from being
-        // disabled after already being started.
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(context.getOpPackageName(), m,
-                UserHandle.USER_SYSTEM);
         ServiceManager.addService("package", m);
         return m;
     }
@@ -17872,6 +17868,11 @@
     public void systemReady() {
         mSystemReady = true;
 
+        // Disable any carrier apps. We do this very early in boot to prevent the apps from being
+        // disabled after already being started.
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this,
+                mContext.getContentResolver(), UserHandle.USER_SYSTEM);
+
         // Read the compatibilty setting when the system is ready.
         boolean compatibilityModeEnabled = android.provider.Settings.Global.getInt(
                 mContext.getContentResolver(),
diff --git a/telephony/java/com/android/internal/telephony/CarrierAppUtils.java b/telephony/java/com/android/internal/telephony/CarrierAppUtils.java
index ca7354f..8b81b0d 100644
--- a/telephony/java/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/java/com/android/internal/telephony/CarrierAppUtils.java
@@ -17,18 +17,23 @@
 package com.android.internal.telephony;
 
 import android.annotation.Nullable;
+import android.content.ContentResolver;
 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.provider.Settings;
 import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemConfig;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Utilities for handling carrier applications.
@@ -53,6 +58,11 @@
      * 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.
      *
+     * In addition, there is a list of carrier-associated applications in
+     * {@link SystemConfig#getDisabledUntilUsedPreinstalledCarrierAssociatedApps}. Each app in this
+     * list is associated with a carrier app. When the given carrier app is enabled/disabled per the
+     * above, the associated applications are enabled/disabled to match.
+     *
      * When enabling a carrier app we also grant it default permissions.
      *
      * This method is idempotent and is safe to be called at any time; it should be called once at
@@ -60,19 +70,24 @@
      * privileged apps may have changed.
      */
     public synchronized static void disableCarrierAppsUntilPrivileged(String callingPackage,
-            IPackageManager packageManager, TelephonyManager telephonyManager, int userId) {
+            IPackageManager packageManager, TelephonyManager telephonyManager,
+            ContentResolver contentResolver, int userId) {
         if (DEBUG) {
             Slog.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
+        SystemConfig config = SystemConfig.getInstance();
         String[] systemCarrierAppsDisabledUntilUsed = Resources.getSystem().getStringArray(
                 com.android.internal.R.array.config_disabledUntilUsedPreinstalledCarrierApps);
-        disableCarrierAppsUntilPrivileged(callingPackage, packageManager, telephonyManager, userId,
-                systemCarrierAppsDisabledUntilUsed);
+        ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
+                config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        disableCarrierAppsUntilPrivileged(callingPackage, packageManager, telephonyManager,
+                contentResolver, userId, systemCarrierAppsDisabledUntilUsed,
+                systemCarrierAssociatedAppsDisabledUntilUsed);
     }
 
     /**
      * Like {@link #disableCarrierAppsUntilPrivileged(String, IPackageManager, TelephonyManager,
-     * int)}, but assumes that no carrier apps have carrier privileges.
+     * ContentResolver, int)}, but assumes that no carrier apps have carrier privileges.
      *
      * This prevents a potential race condition on first boot - since the app's default state is
      * enabled, we will initially disable it when the telephony stack is first initialized as it has
@@ -82,29 +97,43 @@
      * Manager can kill it, and this can lead to crashes as the app is in an unexpected state.
      */
     public synchronized static void disableCarrierAppsUntilPrivileged(String callingPackage,
-            IPackageManager packageManager, int userId) {
+            IPackageManager packageManager, ContentResolver contentResolver, int userId) {
         if (DEBUG) {
             Slog.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
+        SystemConfig config = SystemConfig.getInstance();
         String[] systemCarrierAppsDisabledUntilUsed = Resources.getSystem().getStringArray(
                 com.android.internal.R.array.config_disabledUntilUsedPreinstalledCarrierApps);
+        ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
+                config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
         disableCarrierAppsUntilPrivileged(callingPackage, packageManager,
-                null /* telephonyManager */, userId, systemCarrierAppsDisabledUntilUsed);
+                null /* telephonyManager */, contentResolver, userId,
+                systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed);
     }
 
     // Must be public b/c framework unit tests can't access package-private methods.
     @VisibleForTesting
     public static void disableCarrierAppsUntilPrivileged(String callingPackage,
-            IPackageManager packageManager, @Nullable TelephonyManager telephonyManager, int userId,
-            String[] systemCarrierAppsDisabledUntilUsed) {
+            IPackageManager packageManager, @Nullable TelephonyManager telephonyManager,
+            ContentResolver contentResolver, int userId,
+            String[] systemCarrierAppsDisabledUntilUsed,
+            ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) {
         List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper(packageManager,
                 userId, systemCarrierAppsDisabledUntilUsed);
         if (candidates == null || candidates.isEmpty()) {
             return;
         }
 
+        Map<String, List<ApplicationInfo>> associatedApps = getDefaultCarrierAssociatedAppsHelper(
+                packageManager,
+                userId,
+                systemCarrierAssociatedAppsDisabledUntilUsed);
+
         List<String> enabledCarrierPackages = new ArrayList<>();
 
+        boolean hasRunOnce = Settings.Secure.getIntForUser(
+                contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1;
+
         try {
             for (ApplicationInfo ai : candidates) {
                 String packageName = ai.packageName;
@@ -112,33 +141,92 @@
                         telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) ==
                                 TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
 
-                // Only update enabled state for the app on /system. Once it has been updated we
-                // shouldn't touch it.
-                if (!ai.isUpdatedSystemApp()) {
-                    if (hasPrivileges
-                            && (ai.enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+                if (hasPrivileges) {
+                    // Only update enabled state for the app on /system. Once it has been
+                    // updated we shouldn't touch it.
+                    if (!ai.isUpdatedSystemApp()
+                            && (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.setApplicationEnabledSetting(
+                                packageName,
                                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
-                                PackageManager.DONT_KILL_APP, userId, callingPackage);
-                    } else if (!hasPrivileges
+                                PackageManager.DONT_KILL_APP,
+                                userId,
+                                callingPackage);
+                    }
+
+                    // Also enable any associated apps for this carrier app.
+                    List<ApplicationInfo> associatedAppList = associatedApps.get(packageName);
+                    if (associatedAppList != null) {
+                        for (ApplicationInfo associatedApp : associatedAppList) {
+                            if (associatedApp.enabledSetting ==
+                                    PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+                                    || associatedApp.enabledSetting ==
+                                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+                                Slog.i(TAG, "Update associated state(" + associatedApp.packageName
+                                        + "): ENABLED for user " + userId);
+                                packageManager.setApplicationEnabledSetting(
+                                        associatedApp.packageName,
+                                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                                        PackageManager.DONT_KILL_APP,
+                                        userId,
+                                        callingPackage);
+                            }
+                        }
+                    }
+
+                    // Always re-grant default permissions to carrier apps w/ privileges.
+                    enabledCarrierPackages.add(ai.packageName);
+                } else {  // No carrier privileges
+                    // Only update enabled state for the app on /system. Once it has been
+                    // updated we shouldn't touch it.
+                    if (!ai.isUpdatedSystemApp()
                             && 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);
+                        packageManager.setApplicationEnabledSetting(
+                                packageName,
+                                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+                                0,
+                                userId,
+                                callingPackage);
+                    }
+
+                    // Also disable any associated apps for this carrier app if this is the first
+                    // run. We avoid doing this a second time because it is brittle to rely on the
+                    // distinction between "default" and "enabled".
+                    if (!hasRunOnce) {
+                        List<ApplicationInfo> associatedAppList = associatedApps.get(packageName);
+                        if (associatedAppList != null) {
+                            for (ApplicationInfo associatedApp : associatedAppList) {
+                                if (associatedApp.enabledSetting
+                                        == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+                                    Slog.i(TAG,
+                                            "Update associated state(" + associatedApp.packageName
+                                                    + "): DISABLED_UNTIL_USED for user " + userId);
+                                    packageManager.setApplicationEnabledSetting(
+                                            associatedApp.packageName,
+                                            PackageManager
+                                                    .COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+                                            0,
+                                            userId,
+                                            callingPackage);
+                                }
+                            }
+                        }
                     }
                 }
+            }
 
-                // Always re-grant default permissions to carrier apps w/ privileges.
-                if (hasPrivileges) {
-                    enabledCarrierPackages.add(ai.packageName);
-                }
+            // Mark the execution so we do not disable apps again.
+            if (!hasRunOnce) {
+                Settings.Secure.putIntForUser(
+                        contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId);
             }
 
             if (!enabledCarrierPackages.isEmpty()) {
@@ -190,8 +278,8 @@
      *
      * These are the apps subject to the hiding/showing logic in
      * {@link CarrierAppUtils#disableCarrierAppsUntilPrivileged(String, IPackageManager,
-     * TelephonyManager, int)}, as well as the apps which should have default permissions granted,
-     * when a matching SIM is inserted.
+     * TelephonyManager, ContentResolver, int)}, as well as the apps which should have default
+     * permissions granted, when a matching SIM is inserted.
      *
      * Whether or not the app is actually considered a default app depends on whether the app has
      * carrier privileges as determined by the SIMs in the device.
@@ -205,30 +293,68 @@
     }
 
     private static List<ApplicationInfo> getDefaultCarrierAppCandidatesHelper(
-            IPackageManager packageManager, int userId,
+            IPackageManager packageManager,
+            int userId,
             String[] systemCarrierAppsDisabledUntilUsed) {
         if (systemCarrierAppsDisabledUntilUsed == null
                 || systemCarrierAppsDisabledUntilUsed.length == 0) {
             return null;
         }
-        List<ApplicationInfo> apps = null;
-        try {
-            apps = new ArrayList<>(systemCarrierAppsDisabledUntilUsed.length);
-            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;
-                }
-                if (!ai.isSystemApp()) {
-                    continue;
-                }
+        List<ApplicationInfo> apps = new ArrayList<>(systemCarrierAppsDisabledUntilUsed.length);
+        for (int i = 0; i < systemCarrierAppsDisabledUntilUsed.length; i++) {
+            String packageName = systemCarrierAppsDisabledUntilUsed[i];
+            ApplicationInfo ai =
+                    getApplicationInfoIfSystemApp(packageManager, userId, packageName);
+            if (ai != null) {
                 apps.add(ai);
             }
+        }
+        return apps;
+    }
+
+    private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper(
+            IPackageManager packageManager,
+            int userId,
+            ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) {
+        int size = systemCarrierAssociatedAppsDisabledUntilUsed.size();
+        Map<String, List<ApplicationInfo>> associatedApps = new ArrayMap<>(size);
+        for (int i = 0; i < size; i++) {
+            String carrierAppPackage = systemCarrierAssociatedAppsDisabledUntilUsed.keyAt(i);
+            List<String> associatedAppPackages =
+                    systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i);
+            for (int j = 0; j < associatedAppPackages.size(); j++) {
+                ApplicationInfo ai =
+                        getApplicationInfoIfSystemApp(
+                                packageManager, userId, associatedAppPackages.get(j));
+                // Only update enabled state for the app on /system. Once it has been updated we
+                // shouldn't touch it.
+                if (ai != null && !ai.isUpdatedSystemApp()) {
+                    List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage);
+                    if (appList == null) {
+                        appList = new ArrayList<>();
+                        associatedApps.put(carrierAppPackage, appList);
+                    }
+                    appList.add(ai);
+                }
+            }
+        }
+        return associatedApps;
+    }
+
+    @Nullable
+    private static ApplicationInfo getApplicationInfoIfSystemApp(
+            IPackageManager packageManager,
+            int userId,
+            String packageName) {
+        try {
+            ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
+                    PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS, userId);
+            if (ai != null && ai.isSystemApp()) {
+                return ai;
+            }
         } catch (RemoteException e) {
             Slog.w(TAG, "Could not reach PackageManager", e);
         }
-        return apps;
+        return null;
     }
 }