Reevaluate some carrier-associated apps' status on SDK changes.

If a carrier-associated app is added to the system image after the
device is initially launched (e.g. Q -> R OTA), the logic to disable
apps without a corresponding SIM will not run, leaving the app in an
enabled state in more cases than there should be.

To safely account for this, we only reevaluate on SDK change for apps
whose addedInSdk value falls within the range since the last time we
evaluated carrier apps' statuses. Apps that set lower (or higher)
SDK versions are ignored. We choose *not* to use minimum or target SDK
version here because it's more likely to change with each OTA /
prebuilt, which could cause unintended disable operations (or lack
thereof).

Bug: 154872019
Test: manual, QA, atest FrameworksTelephonyTests:CarrierAppUtilsTest
Change-Id: I97ddfa03f34e7d00e66ce7cd508d6d2a5e83fbc2
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4f0a972..2193e5a 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8517,14 +8517,15 @@
         public static final int VR_DISPLAY_MODE_OFF = 1;
 
         /**
-         * Whether CarrierAppUtils#disableCarrierAppsUntilPrivileged has been executed at least
-         * once.
+         * The latest SDK version that CarrierAppUtils#disableCarrierAppsUntilPrivileged has been
+         * executed for.
          *
          * <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.
+         * <p>Will be set to {@link android.os.Build.VERSION#SDK_INT} once executed. Note that older
+         * SDK versions prior to R set 1 for this value.
          *
          * @hide
          */
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index 4606fb4..e57b030 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.CarrierAssociatedAppEntry;
 import android.os.SystemConfigManager;
 import android.os.UserHandle;
 import android.permission.PermissionManager;
@@ -79,8 +81,8 @@
         SystemConfigManager config = context.getSystemService(SystemConfigManager.class);
         Set<String> systemCarrierAppsDisabledUntilUsed =
                 config.getDisabledUntilUsedPreinstalledCarrierApps();
-        Map<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
-                config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        Map<String, List<CarrierAssociatedAppEntry>> systemCarrierAssociatedAppsDisabledUntilUsed =
+                config.getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries();
         ContentResolver contentResolver = getContentResolverForUser(context, userId);
         disableCarrierAppsUntilPrivileged(callingPackage, telephonyManager, contentResolver,
                 userId, systemCarrierAppsDisabledUntilUsed,
@@ -107,8 +109,8 @@
         Set<String> systemCarrierAppsDisabledUntilUsed =
                 config.getDisabledUntilUsedPreinstalledCarrierApps();
 
-        Map<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
-                config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        Map<String, List<CarrierAssociatedAppEntry>> systemCarrierAssociatedAppsDisabledUntilUsed =
+                config.getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries();
         ContentResolver contentResolver = getContentResolverForUser(context, userId);
         disableCarrierAppsUntilPrivileged(callingPackage, null /* telephonyManager */,
                 contentResolver, userId, systemCarrierAppsDisabledUntilUsed,
@@ -138,8 +140,8 @@
     public static void disableCarrierAppsUntilPrivileged(String callingPackage,
             @Nullable TelephonyManager telephonyManager, ContentResolver contentResolver,
             int userId, Set<String> systemCarrierAppsDisabledUntilUsed,
-            Map<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed,
-            Context context) {
+            Map<String, List<CarrierAssociatedAppEntry>>
+            systemCarrierAssociatedAppsDisabledUntilUsed, Context context) {
         PackageManager packageManager = context.getPackageManager();
         PermissionManager permissionManager =
                 (PermissionManager) context.getSystemService(Context.PERMISSION_SERVICE);
@@ -149,12 +151,17 @@
             return;
         }
 
-        Map<String, List<ApplicationInfo>> associatedApps = getDefaultCarrierAssociatedAppsHelper(
+        Map<String, List<AssociatedAppInfo>> associatedApps = getDefaultCarrierAssociatedAppsHelper(
                 userId, systemCarrierAssociatedAppsDisabledUntilUsed, context);
 
         List<String> enabledCarrierPackages = new ArrayList<>();
-        boolean hasRunOnce = Settings.Secure.getInt(contentResolver,
-                Settings.Secure.CARRIER_APPS_HANDLED, 0) == 1;
+        int carrierAppsHandledSdk =
+                Settings.Secure.getInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0);
+        if (DEBUG) {
+            Log.i(TAG, "Last execution SDK: " + carrierAppsHandledSdk);
+        }
+        boolean hasRunEver = carrierAppsHandledSdk != 0; // SDKs < R used to just set 1 here
+        boolean hasRunForSdk = carrierAppsHandledSdk == Build.VERSION.SDK_INT;
 
         try {
             for (ApplicationInfo ai : candidates) {
@@ -166,10 +173,10 @@
                 // add hiddenUntilInstalled flag for carrier apps and associated apps
                 packageManager.setSystemAppState(
                         packageName, PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
-                List<ApplicationInfo> associatedAppList = associatedApps.get(packageName);
+                List<AssociatedAppInfo> associatedAppList = associatedApps.get(packageName);
                 if (associatedAppList != null) {
-                    for (ApplicationInfo associatedApp : associatedAppList) {
-                        packageManager.setSystemAppState(associatedApp.packageName,
+                    for (AssociatedAppInfo associatedApp : associatedAppList) {
+                        packageManager.setSystemAppState(associatedApp.appInfo.packageName,
                                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
                     }
                 }
@@ -184,7 +191,7 @@
                             || enabledSetting
                             == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
                             || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
-                        Log.i(TAG, "Update state(" + packageName + "): ENABLED for user "
+                        Log.i(TAG, "Update state (" + packageName + "): ENABLED for user "
                                 + userId);
                         context.createContextAsUser(UserHandle.of(userId), 0)
                                 .getPackageManager()
@@ -200,28 +207,37 @@
 
                     // Also enable any associated apps for this carrier app.
                     if (associatedAppList != null) {
-                        for (ApplicationInfo associatedApp : associatedAppList) {
+                        for (AssociatedAppInfo associatedApp : associatedAppList) {
                             int associatedAppEnabledSetting = context
                                     .createContextAsUser(UserHandle.of(userId), 0)
                                     .getPackageManager()
-                                    .getApplicationEnabledSetting(associatedApp.packageName);
+                                    .getApplicationEnabledSetting(
+                                            associatedApp.appInfo.packageName);
+                            boolean associatedAppInstalled = (associatedApp.appInfo.flags
+                                    & ApplicationInfo.FLAG_INSTALLED) != 0;
+                            if (DEBUG) {
+                                Log.i(TAG, "(hasPrivileges) associated app "
+                                        + associatedApp.appInfo.packageName + ", enabled = "
+                                        + associatedAppEnabledSetting + ", installed = "
+                                        + associatedAppInstalled);
+                            }
                             if (associatedAppEnabledSetting
                                     == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                                     || associatedAppEnabledSetting
                                     == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
-                                    || (associatedApp.flags
-                                    & ApplicationInfo.FLAG_INSTALLED) == 0) {
-                                Log.i(TAG, "Update associated state(" + associatedApp.packageName
-                                        + "): ENABLED for user " + userId);
+                                    || !associatedAppInstalled) {
+                                Log.i(TAG, "Update associated state ("
+                                        + associatedApp.appInfo.packageName + "): ENABLED for user "
+                                        + userId);
                                 context.createContextAsUser(UserHandle.of(userId), 0)
                                         .getPackageManager()
-                                        .setSystemAppState(associatedApp.packageName,
+                                        .setSystemAppState(associatedApp.appInfo.packageName,
                                                 PackageManager.SYSTEM_APP_STATE_INSTALLED);
                                 context.createPackageContextAsUser(
                                         callingPackage, 0, UserHandle.of(userId))
                                         .getPackageManager()
                                         .setApplicationEnabledSetting(
-                                                associatedApp.packageName,
+                                                associatedApp.appInfo.packageName,
                                                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                                                 PackageManager.DONT_KILL_APP);
                             }
@@ -236,7 +252,7 @@
                     if (!isUpdatedSystemApp(ai) && enabledSetting
                             == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                             && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
-                        Log.i(TAG, "Update state(" + packageName
+                        Log.i(TAG, "Update state (" + packageName
                                 + "): DISABLED_UNTIL_USED for user " + userId);
                         context.createContextAsUser(UserHandle.of(userId), 0)
                                 .getPackageManager()
@@ -244,37 +260,56 @@
                                         packageName, PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
                     }
 
-                    // 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) {
-                        if (associatedAppList != null) {
-                            for (ApplicationInfo associatedApp : associatedAppList) {
-                                int associatedAppEnabledSetting = context
-                                        .createContextAsUser(UserHandle.of(userId), 0)
+                    // Associated apps are more brittle, because we can't rely on the distinction
+                    // between "default" and "enabled". To account for this, we have two cases:
+                    // 1. We've never run before, so we're fine to disable all associated apps.
+                    // 2. We've run before, but not on this SDK version, so we will only operate on
+                    //    apps with addedInSdk in the range (lastHandledSdk, currentSdk].
+                    // Otherwise, don't touch the associated apps.
+                    if (associatedAppList != null) {
+                        for (AssociatedAppInfo associatedApp : associatedAppList) {
+                            boolean allowDisable = !hasRunEver || (!hasRunForSdk
+                                    && associatedApp.addedInSdk
+                                    != CarrierAssociatedAppEntry.SDK_UNSPECIFIED
+                                    && associatedApp.addedInSdk > carrierAppsHandledSdk
+                                    && associatedApp.addedInSdk <= Build.VERSION.SDK_INT);
+                            int associatedAppEnabledSetting = context
+                                    .createContextAsUser(UserHandle.of(userId), 0)
+                                    .getPackageManager()
+                                    .getApplicationEnabledSetting(
+                                            associatedApp.appInfo.packageName);
+                            boolean associatedAppInstalled = (associatedApp.appInfo.flags
+                                    & ApplicationInfo.FLAG_INSTALLED) != 0;
+                            if (DEBUG) {
+                                Log.i(TAG, "(!hasPrivileges) associated app "
+                                        + associatedApp.appInfo.packageName + ", allowDisable = "
+                                        + allowDisable + ", addedInSdk = "
+                                        + associatedApp.addedInSdk + ", enabled = "
+                                        + associatedAppEnabledSetting + ", installed = "
+                                        + associatedAppInstalled);
+                            }
+                            if (allowDisable
+                                    && associatedAppEnabledSetting
+                                    == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+                                    && associatedAppInstalled) {
+                                Log.i(TAG,
+                                        "Update associated state ("
+                                        + associatedApp.appInfo.packageName
+                                        + "): DISABLED_UNTIL_USED for user " + userId);
+                                context.createContextAsUser(UserHandle.of(userId), 0)
                                         .getPackageManager()
-                                        .getApplicationEnabledSetting(associatedApp.packageName);
-                                if (associatedAppEnabledSetting
-                                        == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
-                                        && (associatedApp.flags
-                                        & ApplicationInfo.FLAG_INSTALLED) != 0) {
-                                    Log.i(TAG,
-                                            "Update associated state(" + associatedApp.packageName
-                                                    + "): DISABLED_UNTIL_USED for user " + userId);
-                                    context.createContextAsUser(UserHandle.of(userId), 0)
-                                            .getPackageManager()
-                                            .setSystemAppState(associatedApp.packageName,
-                                                    PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
-                                }
+                                        .setSystemAppState(associatedApp.appInfo.packageName,
+                                                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
                             }
                         }
                     }
                 }
             }
 
-            // Mark the execution so we do not disable apps again.
-            if (!hasRunOnce) {
-                Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1);
+            // Mark the execution so we do not disable apps again on this SDK version.
+            if (!hasRunEver || !hasRunForSdk) {
+                Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED,
+                        Build.VERSION.SDK_INT);
             }
 
             if (!enabledCarrierPackages.isEmpty()) {
@@ -360,28 +395,28 @@
         return apps;
     }
 
-    private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper(
-            int userId, Map<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed,
-            Context context) {
+    private static Map<String, List<AssociatedAppInfo>> getDefaultCarrierAssociatedAppsHelper(
+            int userId, Map<String, List<CarrierAssociatedAppEntry>>
+            systemCarrierAssociatedAppsDisabledUntilUsed, Context context) {
         int size = systemCarrierAssociatedAppsDisabledUntilUsed.size();
-        Map<String, List<ApplicationInfo>> associatedApps = new ArrayMap<>(size);
-        for (Map.Entry<String, List<String>> entry
+        Map<String, List<AssociatedAppInfo>> associatedApps = new ArrayMap<>(size);
+        for (Map.Entry<String, List<CarrierAssociatedAppEntry>> entry
                 : systemCarrierAssociatedAppsDisabledUntilUsed.entrySet()) {
             String carrierAppPackage = entry.getKey();
-            List<String> associatedAppPackages = entry.getValue();
+            List<CarrierAssociatedAppEntry> associatedAppPackages = entry.getValue();
             for (int j = 0; j < associatedAppPackages.size(); j++) {
+                CarrierAssociatedAppEntry associatedApp = associatedAppPackages.get(j);
                 ApplicationInfo ai =
-                        getApplicationInfoIfSystemApp(
-                                userId, associatedAppPackages.get(j), context);
+                        getApplicationInfoIfSystemApp(userId, associatedApp.packageName, context);
                 // Only update enabled state for the app on /system. Once it has been updated we
                 // shouldn't touch it.
                 if (ai != null && !isUpdatedSystemApp(ai)) {
-                    List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage);
+                    List<AssociatedAppInfo> appList = associatedApps.get(carrierAppPackage);
                     if (appList == null) {
                         appList = new ArrayList<>();
                         associatedApps.put(carrierAppPackage, appList);
                     }
-                    appList.add(ai);
+                    appList.add(new AssociatedAppInfo(ai, associatedApp.addedInSdk));
                 }
             }
         }
@@ -406,4 +441,15 @@
         }
         return null;
     }
+
+    private static final class AssociatedAppInfo {
+        public final ApplicationInfo appInfo;
+        // Might be CarrierAssociatedAppEntry.SDK_UNSPECIFIED.
+        public final int addedInSdk;
+
+        AssociatedAppInfo(ApplicationInfo appInfo, int addedInSdk) {
+            this.appInfo = appInfo;
+            this.addedInSdk = addedInSdk;
+        }
+    }
 }