| /* |
| * Copyright (C) 2020 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.annotation.Nullable; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.res.Resources; |
| import android.os.SystemConfigManager; |
| import android.os.UserHandle; |
| import android.permission.PermissionManager; |
| import android.provider.Settings; |
| import android.telephony.TelephonyManager; |
| import android.util.ArrayMap; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.util.ArrayUtils; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * 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 |
| * {@link SystemConfigManager#getDisabledUntilUsedPreinstalledCarrierApps()}. 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. |
| * |
| * In addition, there is a list of carrier-associated applications in |
| * {@link SystemConfigManager#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 |
| * system startup prior to any application running, as well as any time the set of carrier |
| * privileged apps may have changed. |
| */ |
| public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, |
| TelephonyManager telephonyManager, int userId, Context context) { |
| if (DEBUG) { |
| Log.d(TAG, "disableCarrierAppsUntilPrivileged"); |
| } |
| SystemConfigManager config = context.getSystemService(SystemConfigManager.class); |
| Set<String> systemCarrierAppsDisabledUntilUsed = |
| config.getDisabledUntilUsedPreinstalledCarrierApps(); |
| Map<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed = |
| config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); |
| ContentResolver contentResolver = getContentResolverForUser(context, userId); |
| disableCarrierAppsUntilPrivileged(callingPackage, telephonyManager, contentResolver, |
| userId, systemCarrierAppsDisabledUntilUsed, |
| systemCarrierAssociatedAppsDisabledUntilUsed, context); |
| } |
| |
| /** |
| * Like {@link #disableCarrierAppsUntilPrivileged(String, TelephonyManager, int, Context)}, |
| * 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 |
| * not yet read the carrier privilege rules. However, since telephony is initialized later on |
| * late in boot, the app being disabled may have already been started in response to certain |
| * broadcasts. The app will continue to run (briefly) after being disabled, before the Package |
| * Manager can kill it, and this can lead to crashes as the app is in an unexpected state. |
| */ |
| public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, |
| int userId, Context context) { |
| if (DEBUG) { |
| Log.d(TAG, "disableCarrierAppsUntilPrivileged"); |
| } |
| SystemConfigManager config = context.getSystemService(SystemConfigManager.class); |
| Set<String> systemCarrierAppsDisabledUntilUsed = |
| config.getDisabledUntilUsedPreinstalledCarrierApps(); |
| |
| Map<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed = |
| config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); |
| ContentResolver contentResolver = getContentResolverForUser(context, userId); |
| disableCarrierAppsUntilPrivileged(callingPackage, null /* telephonyManager */, |
| contentResolver, userId, systemCarrierAppsDisabledUntilUsed, |
| systemCarrierAssociatedAppsDisabledUntilUsed, context); |
| } |
| |
| private static ContentResolver getContentResolverForUser(Context context, int userId) { |
| Context userContext = context.createContextAsUser(UserHandle.getUserHandleForUid(userId), |
| 0); |
| return userContext.getContentResolver(); |
| } |
| |
| private static boolean isUpdatedSystemApp(ApplicationInfo ai) { |
| if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Disable carrier apps until they are privileged |
| * Must be public b/c framework unit tests can't access package-private methods. |
| */ |
| // Must be public b/c framework unit tests can't access package-private methods. |
| @VisibleForTesting |
| public static void disableCarrierAppsUntilPrivileged(String callingPackage, |
| @Nullable TelephonyManager telephonyManager, ContentResolver contentResolver, |
| int userId, Set<String> systemCarrierAppsDisabledUntilUsed, |
| Map<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed, |
| Context context) { |
| PackageManager packageManager = context.getPackageManager(); |
| PermissionManager permissionManager = |
| (PermissionManager) context.getSystemService(Context.PERMISSION_SERVICE); |
| List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper( |
| userId, systemCarrierAppsDisabledUntilUsed, context); |
| if (candidates == null || candidates.isEmpty()) { |
| return; |
| } |
| |
| Map<String, List<ApplicationInfo>> associatedApps = getDefaultCarrierAssociatedAppsHelper( |
| userId, systemCarrierAssociatedAppsDisabledUntilUsed, context); |
| |
| List<String> enabledCarrierPackages = new ArrayList<>(); |
| boolean hasRunOnce = Settings.Secure.getInt(contentResolver, |
| Settings.Secure.CARRIER_APPS_HANDLED, 0) == 1; |
| |
| try { |
| for (ApplicationInfo ai : candidates) { |
| String packageName = ai.packageName; |
| String[] restrictedCarrierApps = Resources.getSystem().getStringArray( |
| android.R.array.config_restrictedPreinstalledCarrierApps); |
| boolean hasPrivileges = telephonyManager != null |
| && telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) |
| == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS |
| && !ArrayUtils.contains(restrictedCarrierApps, packageName); |
| |
| // 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); |
| if (associatedAppList != null) { |
| for (ApplicationInfo associatedApp : associatedAppList) { |
| packageManager.setSystemAppState(associatedApp.packageName, |
| PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN); |
| } |
| } |
| |
| int enabledSetting = context.createContextAsUser(UserHandle.of(userId), 0) |
| .getPackageManager().getApplicationEnabledSetting(packageName); |
| if (hasPrivileges) { |
| // Only update enabled state for the app on /system. Once it has been |
| // updated we shouldn't touch it. |
| if (!isUpdatedSystemApp(ai) && enabledSetting |
| == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT |
| || enabledSetting |
| == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED |
| || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { |
| Log.i(TAG, "Update state(" + packageName + "): ENABLED for user " |
| + userId); |
| context.createContextAsUser(UserHandle.of(userId), 0) |
| .getPackageManager() |
| .setSystemAppState( |
| packageName, PackageManager.SYSTEM_APP_STATE_INSTALLED); |
| context.createPackageContextAsUser(callingPackage, 0, UserHandle.of(userId)) |
| .getPackageManager() |
| .setApplicationEnabledSetting( |
| packageName, |
| PackageManager.COMPONENT_ENABLED_STATE_ENABLED, |
| PackageManager.DONT_KILL_APP); |
| } |
| |
| // Also enable any associated apps for this carrier app. |
| if (associatedAppList != null) { |
| for (ApplicationInfo associatedApp : associatedAppList) { |
| int associatedAppEnabledSetting = context |
| .createContextAsUser(UserHandle.of(userId), 0) |
| .getPackageManager() |
| .getApplicationEnabledSetting(associatedApp.packageName); |
| 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); |
| context.createContextAsUser(UserHandle.of(userId), 0) |
| .getPackageManager() |
| .setSystemAppState(associatedApp.packageName, |
| PackageManager.SYSTEM_APP_STATE_INSTALLED); |
| context.createPackageContextAsUser( |
| callingPackage, 0, UserHandle.of(userId)) |
| .getPackageManager() |
| .setApplicationEnabledSetting( |
| associatedApp.packageName, |
| PackageManager.COMPONENT_ENABLED_STATE_ENABLED, |
| PackageManager.DONT_KILL_APP); |
| } |
| } |
| } |
| |
| // 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 (!isUpdatedSystemApp(ai) && enabledSetting |
| == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT |
| && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { |
| Log.i(TAG, "Update state(" + packageName |
| + "): DISABLED_UNTIL_USED for user " + userId); |
| context.createContextAsUser(UserHandle.of(userId), 0) |
| .getPackageManager() |
| .setSystemAppState( |
| 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) |
| .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); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // Mark the execution so we do not disable apps again. |
| if (!hasRunOnce) { |
| Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1); |
| } |
| |
| if (!enabledCarrierPackages.isEmpty()) { |
| // Since we enabled at least one app, ensure we grant default permissions to those |
| // apps. |
| String[] packageNames = new String[enabledCarrierPackages.size()]; |
| enabledCarrierPackages.toArray(packageNames); |
| permissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, |
| UserHandle.of(userId), Runnable::run, isSuccess -> { }); |
| } |
| } catch (PackageManager.NameNotFoundException e) { |
| Log.w(TAG, "Could not reach PackageManager", e); |
| } |
| } |
| |
| /** |
| * Returns the list of "default" carrier apps. |
| * |
| * This is the subset of apps returned by |
| * {@link #getDefaultCarrierAppCandidates(int, Context)} which currently have carrier |
| * privileges per the SIM(s) inserted in the device. |
| */ |
| public static List<ApplicationInfo> getDefaultCarrierApps( |
| TelephonyManager telephonyManager, int userId, Context context) { |
| // Get all system apps from the default list. |
| List<ApplicationInfo> candidates = getDefaultCarrierAppCandidates(userId, context); |
| if (candidates == null || candidates.isEmpty()) { |
| return null; |
| } |
| |
| // Filter out apps without carrier privileges. |
| // Iterate from the end to avoid creating an Iterator object and because we will be removing |
| // elements from the list as we pass through it. |
| for (int i = candidates.size() - 1; i >= 0; i--) { |
| ApplicationInfo ai = candidates.get(i); |
| String packageName = ai.packageName; |
| boolean hasPrivileges = |
| telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) |
| == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; |
| if (!hasPrivileges) { |
| candidates.remove(i); |
| } |
| } |
| |
| return candidates; |
| } |
| |
| /** |
| * Returns the list of "default" carrier app candidates. |
| * |
| * These are the apps subject to the hiding/showing logic in |
| * {@link CarrierAppUtils#disableCarrierAppsUntilPrivileged(String, TelephonyManager, int, |
| * Context)}, 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. |
| */ |
| public static List<ApplicationInfo> getDefaultCarrierAppCandidates( |
| int userId, Context context) { |
| Set<String> systemCarrierAppsDisabledUntilUsed = |
| context.getSystemService(SystemConfigManager.class) |
| .getDisabledUntilUsedPreinstalledCarrierApps(); |
| return getDefaultCarrierAppCandidatesHelper(userId, systemCarrierAppsDisabledUntilUsed, |
| context); |
| } |
| |
| private static List<ApplicationInfo> getDefaultCarrierAppCandidatesHelper( |
| int userId, Set<String> systemCarrierAppsDisabledUntilUsed, Context context) { |
| if (systemCarrierAppsDisabledUntilUsed == null |
| || systemCarrierAppsDisabledUntilUsed.isEmpty()) { |
| return null; |
| } |
| |
| List<ApplicationInfo> apps = new ArrayList<>(systemCarrierAppsDisabledUntilUsed.size()); |
| for (String packageName : systemCarrierAppsDisabledUntilUsed) { |
| ApplicationInfo ai = |
| getApplicationInfoIfSystemApp(userId, packageName, context); |
| if (ai != null) { |
| apps.add(ai); |
| } |
| } |
| return apps; |
| } |
| |
| private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper( |
| int userId, Map<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed, |
| Context context) { |
| int size = systemCarrierAssociatedAppsDisabledUntilUsed.size(); |
| Map<String, List<ApplicationInfo>> associatedApps = new ArrayMap<>(size); |
| for (Map.Entry<String, List<String>> entry |
| : systemCarrierAssociatedAppsDisabledUntilUsed.entrySet()) { |
| String carrierAppPackage = entry.getKey(); |
| List<String> associatedAppPackages = entry.getValue(); |
| for (int j = 0; j < associatedAppPackages.size(); j++) { |
| ApplicationInfo ai = |
| getApplicationInfoIfSystemApp( |
| userId, associatedAppPackages.get(j), 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); |
| if (appList == null) { |
| appList = new ArrayList<>(); |
| associatedApps.put(carrierAppPackage, appList); |
| } |
| appList.add(ai); |
| } |
| } |
| } |
| return associatedApps; |
| } |
| |
| @Nullable |
| private static ApplicationInfo getApplicationInfoIfSystemApp( |
| int userId, String packageName, Context context) { |
| try { |
| ApplicationInfo ai = context.createContextAsUser(UserHandle.of(userId), 0) |
| .getPackageManager() |
| .getApplicationInfo(packageName, |
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS |
| | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS |
| | PackageManager.MATCH_SYSTEM_ONLY); |
| if (ai != null) { |
| return ai; |
| } |
| } catch (PackageManager.NameNotFoundException e) { |
| Log.w(TAG, "Could not reach PackageManager", e); |
| } |
| return null; |
| } |
| } |