From d10972957fd350b4a13b0af21fdc1ca0cf4c0c2e Mon Sep 17 00:00:00 2001 From: Peter Wang Date: Mon, 6 Jan 2020 16:26:05 -0800 Subject: [Telephony Mainline] Move CarrierAppUtils and LocationAccessPolicy to telephony common Bug: 146904426 Test: Build Change-Id: I6f6d24fd0adab803899b25a47199839bcbb09eb0 Merged-In: I6f6d24fd0adab803899b25a47199839bcbb09eb0 --- .../android/telephony/LocationAccessPolicy.java | 348 ++++++++++++++++++ .../internal/telephony/CarrierAppUtils.java | 397 +++++++++++++++++++++ .../internal/telephony/util/ArrayUtils.java | 243 +++++++++++++ .../android/telephony/LocationAccessPolicy.java | 344 ------------------ .../internal/telephony/CarrierAppUtils.java | 394 -------------------- .../internal/telephony/util/ArrayUtils.java | 229 ------------ 6 files changed, 988 insertions(+), 967 deletions(-) create mode 100644 telephony/common/android/telephony/LocationAccessPolicy.java create mode 100644 telephony/common/com/android/internal/telephony/CarrierAppUtils.java create mode 100644 telephony/common/com/android/internal/telephony/util/ArrayUtils.java delete mode 100644 telephony/java/android/telephony/LocationAccessPolicy.java delete mode 100644 telephony/java/com/android/internal/telephony/CarrierAppUtils.java delete mode 100644 telephony/java/com/android/internal/telephony/util/ArrayUtils.java diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java new file mode 100644 index 000000000000..825ce2fc1a00 --- /dev/null +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -0,0 +1,348 @@ +/* + * 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 android.telephony; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.location.LocationManager; +import android.os.Binder; +import android.os.Build; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; +import android.widget.Toast; + +import com.android.internal.telephony.util.TelephonyUtils; + +import java.util.List; + +/** + * Helper for performing location access checks. + * @hide + */ +public final class LocationAccessPolicy { + private static final String TAG = "LocationAccessPolicy"; + private static final boolean DBG = false; + public static final int MAX_SDK_FOR_ANY_ENFORCEMENT = Build.VERSION_CODES.CUR_DEVELOPMENT; + + public enum LocationPermissionResult { + ALLOWED, + /** + * Indicates that the denial is due to a transient device state + * (e.g. app-ops, location master switch) + */ + DENIED_SOFT, + /** + * Indicates that the denial is due to a misconfigured app (e.g. missing entry in manifest) + */ + DENIED_HARD, + } + + /** Data structure for location permission query */ + public static class LocationPermissionQuery { + public final String callingPackage; + public final int callingUid; + public final int callingPid; + public final int minSdkVersionForCoarse; + public final int minSdkVersionForFine; + public final boolean logAsInfo; + public final String method; + + private LocationPermissionQuery(String callingPackage, int callingUid, int callingPid, + int minSdkVersionForCoarse, int minSdkVersionForFine, boolean logAsInfo, + String method) { + this.callingPackage = callingPackage; + this.callingUid = callingUid; + this.callingPid = callingPid; + this.minSdkVersionForCoarse = minSdkVersionForCoarse; + this.minSdkVersionForFine = minSdkVersionForFine; + this.logAsInfo = logAsInfo; + this.method = method; + } + + /** Builder for LocationPermissionQuery */ + public static class Builder { + private String mCallingPackage; + private int mCallingUid; + private int mCallingPid; + private int mMinSdkVersionForCoarse = Integer.MAX_VALUE; + private int mMinSdkVersionForFine = Integer.MAX_VALUE; + private boolean mLogAsInfo = false; + private String mMethod; + + /** + * Mandatory parameter, used for performing permission checks. + */ + public Builder setCallingPackage(String callingPackage) { + mCallingPackage = callingPackage; + return this; + } + + /** + * Mandatory parameter, used for performing permission checks. + */ + public Builder setCallingUid(int callingUid) { + mCallingUid = callingUid; + return this; + } + + /** + * Mandatory parameter, used for performing permission checks. + */ + public Builder setCallingPid(int callingPid) { + mCallingPid = callingPid; + return this; + } + + /** + * Apps that target at least this sdk version will be checked for coarse location + * permission. Defaults to INT_MAX (which means don't check) + */ + public Builder setMinSdkVersionForCoarse( + int minSdkVersionForCoarse) { + mMinSdkVersionForCoarse = minSdkVersionForCoarse; + return this; + } + + /** + * Apps that target at least this sdk version will be checked for fine location + * permission. Defaults to INT_MAX (which means don't check) + */ + public Builder setMinSdkVersionForFine( + int minSdkVersionForFine) { + mMinSdkVersionForFine = minSdkVersionForFine; + return this; + } + + /** + * Optional, for logging purposes only. + */ + public Builder setMethod(String method) { + mMethod = method; + return this; + } + + /** + * If called with {@code true}, log messages will only be printed at the info level. + */ + public Builder setLogAsInfo(boolean logAsInfo) { + mLogAsInfo = logAsInfo; + return this; + } + + /** build LocationPermissionQuery */ + public LocationPermissionQuery build() { + return new LocationPermissionQuery(mCallingPackage, mCallingUid, + mCallingPid, mMinSdkVersionForCoarse, mMinSdkVersionForFine, + mLogAsInfo, mMethod); + } + } + } + + private static void logError(Context context, LocationPermissionQuery query, String errorMsg) { + if (query.logAsInfo) { + Log.i(TAG, errorMsg); + return; + } + Log.e(TAG, errorMsg); + try { + if (TelephonyUtils.IS_DEBUGGABLE) { + Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show(); + } + } catch (Throwable t) { + // whatever, not important + } + } + + private static LocationPermissionResult appOpsModeToPermissionResult(int appOpsMode) { + switch (appOpsMode) { + case AppOpsManager.MODE_ALLOWED: + return LocationPermissionResult.ALLOWED; + case AppOpsManager.MODE_ERRORED: + return LocationPermissionResult.DENIED_HARD; + default: + return LocationPermissionResult.DENIED_SOFT; + } + } + + private static LocationPermissionResult checkAppLocationPermissionHelper(Context context, + LocationPermissionQuery query, String permissionToCheck) { + String locationTypeForLog = + Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck) + ? "fine" : "coarse"; + + // Do the app-ops and the manifest check without any of the allow-overrides first. + boolean hasManifestPermission = checkManifestPermission(context, query.callingPid, + query.callingUid, permissionToCheck); + + if (hasManifestPermission) { + // Only check the app op if the app has the permission. + int appOpMode = context.getSystemService(AppOpsManager.class) + .noteOpNoThrow(AppOpsManager.permissionToOpCode(permissionToCheck), + query.callingUid, query.callingPackage); + if (appOpMode == AppOpsManager.MODE_ALLOWED) { + // If the app did everything right, return without logging. + return LocationPermissionResult.ALLOWED; + } else { + // If the app has the manifest permission but not the app-op permission, it means + // that it's aware of the requirement and the user denied permission explicitly. + // If we see this, don't let any of the overrides happen. + Log.i(TAG, query.callingPackage + " is aware of " + locationTypeForLog + " but the" + + " app-ops permission is specifically denied."); + return appOpsModeToPermissionResult(appOpMode); + } + } + + int minSdkVersion = Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck) + ? query.minSdkVersionForFine : query.minSdkVersionForCoarse; + + // If the app fails for some reason, see if it should be allowed to proceed. + if (minSdkVersion > MAX_SDK_FOR_ANY_ENFORCEMENT) { + String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog + + " because we're not enforcing API " + minSdkVersion + " yet." + + " Please fix this app because it will break in the future. Called from " + + query.method; + logError(context, query, errorMsg); + return null; + } else if (!isAppAtLeastSdkVersion(context, query.callingPackage, minSdkVersion)) { + String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog + + " because it doesn't target API " + minSdkVersion + " yet." + + " Please fix this app. Called from " + query.method; + logError(context, query, errorMsg); + return null; + } else { + // If we're not allowing it due to the above two conditions, this means that the app + // did not declare the permission in their manifest. + return LocationPermissionResult.DENIED_HARD; + } + } + + /** Check if location permissions have been granted */ + public static LocationPermissionResult checkLocationPermission( + Context context, LocationPermissionQuery query) { + // Always allow the phone process and system server to access location. This avoid + // breaking legacy code that rely on public-facing APIs to access cell location, and + // it doesn't create an info leak risk because the cell location is stored in the phone + // process anyway, and the system server already has location access. + if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID + || query.callingUid == Process.ROOT_UID) { + return LocationPermissionResult.ALLOWED; + } + + // Check the system-wide requirements. If the location master switch is off or + // the app's profile isn't in foreground, return a soft denial. + if (!checkSystemLocationAccess(context, query.callingUid, query.callingPid)) { + return LocationPermissionResult.DENIED_SOFT; + } + + // Do the check for fine, then for coarse. + if (query.minSdkVersionForFine < Integer.MAX_VALUE) { + LocationPermissionResult resultForFine = checkAppLocationPermissionHelper( + context, query, Manifest.permission.ACCESS_FINE_LOCATION); + if (resultForFine != null) { + return resultForFine; + } + } + + if (query.minSdkVersionForCoarse < Integer.MAX_VALUE) { + LocationPermissionResult resultForCoarse = checkAppLocationPermissionHelper( + context, query, Manifest.permission.ACCESS_COARSE_LOCATION); + if (resultForCoarse != null) { + return resultForCoarse; + } + } + + // At this point, we're out of location checks to do. If the app bypassed all the previous + // ones due to the SDK grandfathering schemes, allow it access. + return LocationPermissionResult.ALLOWED; + } + + + private static boolean checkManifestPermission(Context context, int pid, int uid, + String permissionToCheck) { + return context.checkPermission(permissionToCheck, pid, uid) + == PackageManager.PERMISSION_GRANTED; + } + + private static boolean checkSystemLocationAccess(@NonNull Context context, int uid, int pid) { + if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))) { + if (DBG) Log.w(TAG, "Location disabled, failed, (" + uid + ")"); + return false; + } + // If the user or profile is current, permission is granted. + // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. + return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, uid, pid); + } + + private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { + LocationManager locationManager = context.getSystemService(LocationManager.class); + if (locationManager == null) { + Log.w(TAG, "Couldn't get location manager, denying location access"); + return false; + } + return locationManager.isLocationEnabledForUser(UserHandle.of(userId)); + } + + private static boolean checkInteractAcrossUsersFull( + @NonNull Context context, int pid, int uid) { + return checkManifestPermission(context, pid, uid, + Manifest.permission.INTERACT_ACROSS_USERS_FULL); + } + + private static boolean isCurrentProfile(@NonNull Context context, int uid) { + long token = Binder.clearCallingIdentity(); + try { + final int currentUser = ActivityManager.getCurrentUser(); + final int callingUserId = UserHandle.getUserId(uid); + if (callingUserId == currentUser) { + return true; + } else { + List userProfiles = context.getSystemService( + UserManager.class).getProfiles(currentUser); + for (UserInfo user : userProfiles) { + if (user.id == callingUserId) { + return true; + } + } + } + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private static boolean isAppAtLeastSdkVersion(Context context, String pkgName, int sdkVersion) { + try { + if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion + >= sdkVersion) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + // In case of exception, assume known app (more strict checking) + // Note: This case will never happen since checkPackage is + // called to verify validity before checking app's version. + } + return false; + } +} diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java new file mode 100644 index 000000000000..b5d33699a7f6 --- /dev/null +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -0,0 +1,397 @@ +/* + * 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.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.ArraySet; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.util.ArrayUtils; +import com.android.server.SystemConfig; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 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 SystemConfig#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 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 + * 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, + IPackageManager packageManager, TelephonyManager telephonyManager, + ContentResolver contentResolver, int userId) { + if (DEBUG) { + Slog.d(TAG, "disableCarrierAppsUntilPrivileged"); + } + SystemConfig config = SystemConfig.getInstance(); + ArraySet systemCarrierAppsDisabledUntilUsed = + config.getDisabledUntilUsedPreinstalledCarrierApps(); + ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed = + config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + disableCarrierAppsUntilPrivileged(callingPackage, packageManager, telephonyManager, + contentResolver, userId, systemCarrierAppsDisabledUntilUsed, + systemCarrierAssociatedAppsDisabledUntilUsed); + } + + /** + * Like {@link #disableCarrierAppsUntilPrivileged(String, IPackageManager, TelephonyManager, + * 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 + * 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, + IPackageManager packageManager, ContentResolver contentResolver, int userId) { + if (DEBUG) { + Slog.d(TAG, "disableCarrierAppsUntilPrivileged"); + } + SystemConfig config = SystemConfig.getInstance(); + ArraySet systemCarrierAppsDisabledUntilUsed = + config.getDisabledUntilUsedPreinstalledCarrierApps(); + + + ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed = + config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + disableCarrierAppsUntilPrivileged(callingPackage, packageManager, + null /* telephonyManager */, contentResolver, userId, + systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed); + } + + /** + * Disable carrier apps until they are privileged + * 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, + ContentResolver contentResolver, int userId, + ArraySet systemCarrierAppsDisabledUntilUsed, + ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed) { + List candidates = getDefaultCarrierAppCandidatesHelper(packageManager, + userId, systemCarrierAppsDisabledUntilUsed); + if (candidates == null || candidates.isEmpty()) { + return; + } + + Map> associatedApps = getDefaultCarrierAssociatedAppsHelper( + packageManager, + userId, + systemCarrierAssociatedAppsDisabledUntilUsed); + + List 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; + String[] restrictedCarrierApps = Resources.getSystem().getStringArray( + 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.setSystemAppHiddenUntilInstalled(packageName, true); + List associatedAppList = associatedApps.get(packageName); + if (associatedAppList != null) { + for (ApplicationInfo associatedApp : associatedAppList) { + packageManager.setSystemAppHiddenUntilInstalled( + associatedApp.packageName, + true + ); + } + } + + 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 + || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) { + Slog.i(TAG, "Update state(" + packageName + "): ENABLED for user " + + userId); + packageManager.setSystemAppInstallState( + packageName, + true /*installed*/, + userId); + packageManager.setApplicationEnabledSetting( + packageName, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP, + userId, + callingPackage); + } + + // Also enable any associated apps for this carrier app. + if (associatedAppList != null) { + for (ApplicationInfo associatedApp : associatedAppList) { + if (associatedApp.enabledSetting + == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT + || associatedApp.enabledSetting + == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED + || (associatedApp.flags + & ApplicationInfo.FLAG_INSTALLED) == 0) { + Slog.i(TAG, "Update associated state(" + associatedApp.packageName + + "): ENABLED for user " + userId); + packageManager.setSystemAppInstallState( + associatedApp.packageName, + true /*installed*/, + 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 + && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { + Slog.i(TAG, "Update state(" + packageName + + "): DISABLED_UNTIL_USED for user " + userId); + packageManager.setSystemAppInstallState( + packageName, + false /*installed*/, + userId); + } + + // 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) { + if (associatedApp.enabledSetting + == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT + && (associatedApp.flags + & ApplicationInfo.FLAG_INSTALLED) != 0) { + Slog.i(TAG, + "Update associated state(" + associatedApp.packageName + + "): DISABLED_UNTIL_USED for user " + userId); + packageManager.setSystemAppInstallState( + associatedApp.packageName, + false /*installed*/, + userId); + } + } + } + } + } + } + + // 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()) { + // Since we enabled at least one app, ensure we grant default permissions to those + // apps. + String[] packageNames = new String[enabledCarrierPackages.size()]; + enabledCarrierPackages.toArray(packageNames); + packageManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, userId); + } + } catch (RemoteException e) { + Slog.w(TAG, "Could not reach PackageManager", e); + } + } + + /** + * Returns the list of "default" carrier apps. + * + * This is the subset of apps returned by + * {@link #getDefaultCarrierAppCandidates(IPackageManager, int)} which currently have carrier + * privileges per the SIM(s) inserted in the device. + */ + public static List getDefaultCarrierApps(IPackageManager packageManager, + TelephonyManager telephonyManager, int userId) { + // Get all system apps from the default list. + List candidates = getDefaultCarrierAppCandidates(packageManager, userId); + 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, IPackageManager, + * 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. + */ + public static List getDefaultCarrierAppCandidates( + IPackageManager packageManager, int userId) { + ArraySet systemCarrierAppsDisabledUntilUsed = + SystemConfig.getInstance().getDisabledUntilUsedPreinstalledCarrierApps(); + return getDefaultCarrierAppCandidatesHelper(packageManager, userId, + systemCarrierAppsDisabledUntilUsed); + } + + private static List getDefaultCarrierAppCandidatesHelper( + IPackageManager packageManager, + int userId, + ArraySet systemCarrierAppsDisabledUntilUsed) { + if (systemCarrierAppsDisabledUntilUsed == null) { + return null; + } + + int size = systemCarrierAppsDisabledUntilUsed.size(); + if (size == 0) { + return null; + } + + List apps = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); + ApplicationInfo ai = + getApplicationInfoIfSystemApp(packageManager, userId, packageName); + if (ai != null) { + apps.add(ai); + } + } + return apps; + } + + private static Map> getDefaultCarrierAssociatedAppsHelper( + IPackageManager packageManager, + int userId, + ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed) { + int size = systemCarrierAssociatedAppsDisabledUntilUsed.size(); + Map> associatedApps = new ArrayMap<>(size); + for (int i = 0; i < size; i++) { + String carrierAppPackage = systemCarrierAssociatedAppsDisabledUntilUsed.keyAt(i); + List 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 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 + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId); + if (ai != null && ai.isSystemApp()) { + return ai; + } + } catch (RemoteException e) { + Slog.w(TAG, "Could not reach PackageManager", e); + } + return null; + } +} diff --git a/telephony/common/com/android/internal/telephony/util/ArrayUtils.java b/telephony/common/com/android/internal/telephony/util/ArrayUtils.java new file mode 100644 index 000000000000..28401a6a7092 --- /dev/null +++ b/telephony/common/com/android/internal/telephony/util/ArrayUtils.java @@ -0,0 +1,243 @@ +/* + * 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.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; + +/** Utility methods for array operations. */ +public final class ArrayUtils { + private ArrayUtils() { /* cannot be instantiated */ } + + /** + * Adds value to given array if not already present, providing set-like behavior. + * + * @param kind The class of the array elements. + * @param array The array to append to. + * @param element The array element to append. + * @return The array containing the appended element. + */ + @SuppressWarnings("unchecked") + @NonNull + public static T[] appendElement(Class kind, @Nullable T[] array, T element) { + return appendElement(kind, array, element, false); + } + + /** + * Adds value to given array. + * + * @param kind The class of the array elements. + * @param array The array to append to. + * @param element The array element to append. + * @param allowDuplicates Whether to allow duplicated elements in array. + * @return The array containing the appended element. + */ + @SuppressWarnings("unchecked") + @NonNull + public static T[] appendElement(Class kind, @Nullable T[] array, T element, + boolean allowDuplicates) { + final T[] result; + final int end; + if (array != null) { + if (!allowDuplicates && contains(array, element)) return array; + end = array.length; + result = (T[]) Array.newInstance(kind, end + 1); + System.arraycopy(array, 0, result, 0, end); + } else { + end = 0; + result = (T[]) Array.newInstance(kind, 1); + } + result[end] = element; + return result; + } + + /** + * Combine multiple arrays into a single array. + * + * @param kind The class of the array elements + * @param arrays The arrays to combine + * @param The class of the array elements (inferred from kind). + * @return A single array containing all the elements of the parameter arrays. + */ + @SuppressWarnings("unchecked") + @NonNull + public static T[] concatElements(Class kind, @Nullable T[]... arrays) { + if (arrays == null || arrays.length == 0) { + return createEmptyArray(kind); + } + + int totalLength = 0; + for (T[] item : arrays) { + if (item == null) { + continue; + } + + totalLength += item.length; + } + + // Optimization for entirely empty arrays. + if (totalLength == 0) { + return createEmptyArray(kind); + } + + final T[] all = (T[]) Array.newInstance(kind, totalLength); + int pos = 0; + for (T[] item : arrays) { + if (item == null || item.length == 0) { + continue; + } + System.arraycopy(item, 0, all, pos, item.length); + pos += item.length; + } + return all; + } + + private static @NonNull T[] createEmptyArray(Class kind) { + if (kind == String.class) { + return (T[]) EmptyArray.STRING; + } else if (kind == Object.class) { + return (T[]) EmptyArray.OBJECT; + } + + return (T[]) Array.newInstance(kind, 0); + } + + private static final class EmptyArray { + private EmptyArray() {} + + public static final Object[] OBJECT = new Object[0]; + public static final String[] STRING = new String[0]; + } + + /** + * Checks if {@code value} is in {@code array}. + */ + public static boolean contains(@Nullable char[] array, char value) { + if (array == null) return false; + for (char element : array) { + if (element == value) { + return true; + } + } + return false; + } + + /** + * Checks if {@code value} is in {@code array}. + */ + public static boolean contains(@Nullable Collection cur, T val) { + return (cur != null) ? cur.contains(val) : false; + } + + /** + * Checks if {@code value} is in {@code array}. + */ + public static boolean contains(@Nullable int[] array, int value) { + if (array == null) return false; + for (int element : array) { + if (element == value) { + return true; + } + } + return false; + } + + /** + * Checks if {@code value} is in {@code array}. + */ + public static boolean contains(@Nullable long[] array, long value) { + if (array == null) return false; + for (long element : array) { + if (element == value) { + return true; + } + } + return false; + } + + /** + * Checks if {@code value} is in {@code array}. + */ + public static boolean contains(@Nullable T[] array, T value) { + return indexOf(array, value) != -1; + } + + /** + * Return first index of {@code value} in {@code array}, or {@code -1} if + * not found. + */ + public static int indexOf(@Nullable T[] array, T value) { + if (array == null) return -1; + for (int i = 0; i < array.length; i++) { + if (Objects.equals(array[i], value)) return i; + } + return -1; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable Collection array) { + return array == null || array.isEmpty(); + } + + /** + * Checks if given map is null or has zero elements. + */ + public static boolean isEmpty(@Nullable Map map) { + return map == null || map.isEmpty(); + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable T[] array) { + return array == null || array.length == 0; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable int[] array) { + return array == null || array.length == 0; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable long[] array) { + return array == null || array.length == 0; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable byte[] array) { + return array == null || array.length == 0; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable boolean[] array) { + return array == null || array.length == 0; + } +} diff --git a/telephony/java/android/telephony/LocationAccessPolicy.java b/telephony/java/android/telephony/LocationAccessPolicy.java deleted file mode 100644 index d4526a48fdb1..000000000000 --- a/telephony/java/android/telephony/LocationAccessPolicy.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (C) 2017 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 android.telephony; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.UserIdInt; -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.UserInfo; -import android.location.LocationManager; -import android.os.Binder; -import android.os.Build; -import android.os.Process; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.Log; -import android.widget.Toast; - -import com.android.internal.telephony.util.TelephonyUtils; - -import java.util.List; - -/** - * Helper for performing location access checks. - * @hide - */ -public final class LocationAccessPolicy { - private static final String TAG = "LocationAccessPolicy"; - private static final boolean DBG = false; - public static final int MAX_SDK_FOR_ANY_ENFORCEMENT = Build.VERSION_CODES.CUR_DEVELOPMENT; - - public enum LocationPermissionResult { - ALLOWED, - /** - * Indicates that the denial is due to a transient device state - * (e.g. app-ops, location master switch) - */ - DENIED_SOFT, - /** - * Indicates that the denial is due to a misconfigured app (e.g. missing entry in manifest) - */ - DENIED_HARD, - } - - public static class LocationPermissionQuery { - public final String callingPackage; - public final int callingUid; - public final int callingPid; - public final int minSdkVersionForCoarse; - public final int minSdkVersionForFine; - public final boolean logAsInfo; - public final String method; - - private LocationPermissionQuery(String callingPackage, int callingUid, int callingPid, - int minSdkVersionForCoarse, int minSdkVersionForFine, boolean logAsInfo, - String method) { - this.callingPackage = callingPackage; - this.callingUid = callingUid; - this.callingPid = callingPid; - this.minSdkVersionForCoarse = minSdkVersionForCoarse; - this.minSdkVersionForFine = minSdkVersionForFine; - this.logAsInfo = logAsInfo; - this.method = method; - } - - public static class Builder { - private String mCallingPackage; - private int mCallingUid; - private int mCallingPid; - private int mMinSdkVersionForCoarse = Integer.MAX_VALUE; - private int mMinSdkVersionForFine = Integer.MAX_VALUE; - private boolean mLogAsInfo = false; - private String mMethod; - - /** - * Mandatory parameter, used for performing permission checks. - */ - public Builder setCallingPackage(String callingPackage) { - mCallingPackage = callingPackage; - return this; - } - - /** - * Mandatory parameter, used for performing permission checks. - */ - public Builder setCallingUid(int callingUid) { - mCallingUid = callingUid; - return this; - } - - /** - * Mandatory parameter, used for performing permission checks. - */ - public Builder setCallingPid(int callingPid) { - mCallingPid = callingPid; - return this; - } - - /** - * Apps that target at least this sdk version will be checked for coarse location - * permission. Defaults to INT_MAX (which means don't check) - */ - public Builder setMinSdkVersionForCoarse( - int minSdkVersionForCoarse) { - mMinSdkVersionForCoarse = minSdkVersionForCoarse; - return this; - } - - /** - * Apps that target at least this sdk version will be checked for fine location - * permission. Defaults to INT_MAX (which means don't check) - */ - public Builder setMinSdkVersionForFine( - int minSdkVersionForFine) { - mMinSdkVersionForFine = minSdkVersionForFine; - return this; - } - - /** - * Optional, for logging purposes only. - */ - public Builder setMethod(String method) { - mMethod = method; - return this; - } - - /** - * If called with {@code true}, log messages will only be printed at the info level. - */ - public Builder setLogAsInfo(boolean logAsInfo) { - mLogAsInfo = logAsInfo; - return this; - } - - public LocationPermissionQuery build() { - return new LocationPermissionQuery(mCallingPackage, mCallingUid, - mCallingPid, mMinSdkVersionForCoarse, mMinSdkVersionForFine, - mLogAsInfo, mMethod); - } - } - } - - private static void logError(Context context, LocationPermissionQuery query, String errorMsg) { - if (query.logAsInfo) { - Log.i(TAG, errorMsg); - return; - } - Log.e(TAG, errorMsg); - try { - if (TelephonyUtils.IS_DEBUGGABLE) { - Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show(); - } - } catch (Throwable t) { - // whatever, not important - } - } - - private static LocationPermissionResult appOpsModeToPermissionResult(int appOpsMode) { - switch (appOpsMode) { - case AppOpsManager.MODE_ALLOWED: - return LocationPermissionResult.ALLOWED; - case AppOpsManager.MODE_ERRORED: - return LocationPermissionResult.DENIED_HARD; - default: - return LocationPermissionResult.DENIED_SOFT; - } - } - - private static LocationPermissionResult checkAppLocationPermissionHelper(Context context, - LocationPermissionQuery query, String permissionToCheck) { - String locationTypeForLog = - Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck) - ? "fine" : "coarse"; - - // Do the app-ops and the manifest check without any of the allow-overrides first. - boolean hasManifestPermission = checkManifestPermission(context, query.callingPid, - query.callingUid, permissionToCheck); - - if (hasManifestPermission) { - // Only check the app op if the app has the permission. - int appOpMode = context.getSystemService(AppOpsManager.class) - .noteOpNoThrow(AppOpsManager.permissionToOpCode(permissionToCheck), - query.callingUid, query.callingPackage); - if (appOpMode == AppOpsManager.MODE_ALLOWED) { - // If the app did everything right, return without logging. - return LocationPermissionResult.ALLOWED; - } else { - // If the app has the manifest permission but not the app-op permission, it means - // that it's aware of the requirement and the user denied permission explicitly. - // If we see this, don't let any of the overrides happen. - Log.i(TAG, query.callingPackage + " is aware of " + locationTypeForLog + " but the" - + " app-ops permission is specifically denied."); - return appOpsModeToPermissionResult(appOpMode); - } - } - - int minSdkVersion = Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck) - ? query.minSdkVersionForFine : query.minSdkVersionForCoarse; - - // If the app fails for some reason, see if it should be allowed to proceed. - if (minSdkVersion > MAX_SDK_FOR_ANY_ENFORCEMENT) { - String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog - + " because we're not enforcing API " + minSdkVersion + " yet." - + " Please fix this app because it will break in the future. Called from " - + query.method; - logError(context, query, errorMsg); - return null; - } else if (!isAppAtLeastSdkVersion(context, query.callingPackage, minSdkVersion)) { - String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog - + " because it doesn't target API " + minSdkVersion + " yet." - + " Please fix this app. Called from " + query.method; - logError(context, query, errorMsg); - return null; - } else { - // If we're not allowing it due to the above two conditions, this means that the app - // did not declare the permission in their manifest. - return LocationPermissionResult.DENIED_HARD; - } - } - - public static LocationPermissionResult checkLocationPermission( - Context context, LocationPermissionQuery query) { - // Always allow the phone process and system server to access location. This avoid - // breaking legacy code that rely on public-facing APIs to access cell location, and - // it doesn't create an info leak risk because the cell location is stored in the phone - // process anyway, and the system server already has location access. - if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID - || query.callingUid == Process.ROOT_UID) { - return LocationPermissionResult.ALLOWED; - } - - // Check the system-wide requirements. If the location master switch is off or - // the app's profile isn't in foreground, return a soft denial. - if (!checkSystemLocationAccess(context, query.callingUid, query.callingPid)) { - return LocationPermissionResult.DENIED_SOFT; - } - - // Do the check for fine, then for coarse. - if (query.minSdkVersionForFine < Integer.MAX_VALUE) { - LocationPermissionResult resultForFine = checkAppLocationPermissionHelper( - context, query, Manifest.permission.ACCESS_FINE_LOCATION); - if (resultForFine != null) { - return resultForFine; - } - } - - if (query.minSdkVersionForCoarse < Integer.MAX_VALUE) { - LocationPermissionResult resultForCoarse = checkAppLocationPermissionHelper( - context, query, Manifest.permission.ACCESS_COARSE_LOCATION); - if (resultForCoarse != null) { - return resultForCoarse; - } - } - - // At this point, we're out of location checks to do. If the app bypassed all the previous - // ones due to the SDK grandfathering schemes, allow it access. - return LocationPermissionResult.ALLOWED; - } - - - private static boolean checkManifestPermission(Context context, int pid, int uid, - String permissionToCheck) { - return context.checkPermission(permissionToCheck, pid, uid) - == PackageManager.PERMISSION_GRANTED; - } - - private static boolean checkSystemLocationAccess(@NonNull Context context, int uid, int pid) { - if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))) { - if (DBG) Log.w(TAG, "Location disabled, failed, (" + uid + ")"); - return false; - } - // If the user or profile is current, permission is granted. - // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. - return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, uid, pid); - } - - private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { - LocationManager locationManager = context.getSystemService(LocationManager.class); - if (locationManager == null) { - Log.w(TAG, "Couldn't get location manager, denying location access"); - return false; - } - return locationManager.isLocationEnabledForUser(UserHandle.of(userId)); - } - - private static boolean checkInteractAcrossUsersFull( - @NonNull Context context, int pid, int uid) { - return checkManifestPermission(context, pid, uid, - Manifest.permission.INTERACT_ACROSS_USERS_FULL); - } - - private static boolean isCurrentProfile(@NonNull Context context, int uid) { - long token = Binder.clearCallingIdentity(); - try { - final int currentUser = ActivityManager.getCurrentUser(); - final int callingUserId = UserHandle.getUserId(uid); - if (callingUserId == currentUser) { - return true; - } else { - List userProfiles = context.getSystemService( - UserManager.class).getProfiles(currentUser); - for (UserInfo user : userProfiles) { - if (user.id == callingUserId) { - return true; - } - } - } - return false; - } finally { - Binder.restoreCallingIdentity(token); - } - } - - private static boolean isAppAtLeastSdkVersion(Context context, String pkgName, int sdkVersion) { - try { - if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion - >= sdkVersion) { - return true; - } - } catch (PackageManager.NameNotFoundException e) { - // In case of exception, assume known app (more strict checking) - // Note: This case will never happen since checkPackage is - // called to verify validity before checking app's version. - } - return false; - } -} \ No newline at end of file diff --git a/telephony/java/com/android/internal/telephony/CarrierAppUtils.java b/telephony/java/com/android/internal/telephony/CarrierAppUtils.java deleted file mode 100644 index 0630454cbf53..000000000000 --- a/telephony/java/com/android/internal/telephony/CarrierAppUtils.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * 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.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.ArraySet; -import android.util.Slog; - -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.util.ArrayUtils; -import com.android.server.SystemConfig; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * 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 SystemConfig#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 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 - * 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, - ContentResolver contentResolver, int userId) { - if (DEBUG) { - Slog.d(TAG, "disableCarrierAppsUntilPrivileged"); - } - SystemConfig config = SystemConfig.getInstance(); - ArraySet systemCarrierAppsDisabledUntilUsed = - config.getDisabledUntilUsedPreinstalledCarrierApps(); - ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed = - config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); - disableCarrierAppsUntilPrivileged(callingPackage, packageManager, telephonyManager, - contentResolver, userId, systemCarrierAppsDisabledUntilUsed, - systemCarrierAssociatedAppsDisabledUntilUsed); - } - - /** - * Like {@link #disableCarrierAppsUntilPrivileged(String, IPackageManager, TelephonyManager, - * 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 - * 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 synchronized static void disableCarrierAppsUntilPrivileged(String callingPackage, - IPackageManager packageManager, ContentResolver contentResolver, int userId) { - if (DEBUG) { - Slog.d(TAG, "disableCarrierAppsUntilPrivileged"); - } - SystemConfig config = SystemConfig.getInstance(); - ArraySet systemCarrierAppsDisabledUntilUsed = - config.getDisabledUntilUsedPreinstalledCarrierApps(); - - - ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed = - config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); - disableCarrierAppsUntilPrivileged(callingPackage, packageManager, - 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, - ContentResolver contentResolver, int userId, - ArraySet systemCarrierAppsDisabledUntilUsed, - ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed) { - List candidates = getDefaultCarrierAppCandidatesHelper(packageManager, - userId, systemCarrierAppsDisabledUntilUsed); - if (candidates == null || candidates.isEmpty()) { - return; - } - - Map> associatedApps = getDefaultCarrierAssociatedAppsHelper( - packageManager, - userId, - systemCarrierAssociatedAppsDisabledUntilUsed); - - List 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; - String[] restrictedCarrierApps = Resources.getSystem().getStringArray( - 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.setSystemAppHiddenUntilInstalled(packageName, true); - List associatedAppList = associatedApps.get(packageName); - if (associatedAppList != null) { - for (ApplicationInfo associatedApp : associatedAppList) { - packageManager.setSystemAppHiddenUntilInstalled( - associatedApp.packageName, - true - ); - } - } - - 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 - || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) { - Slog.i(TAG, "Update state(" + packageName + "): ENABLED for user " - + userId); - packageManager.setSystemAppInstallState( - packageName, - true /*installed*/, - userId); - packageManager.setApplicationEnabledSetting( - packageName, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, - PackageManager.DONT_KILL_APP, - userId, - callingPackage); - } - - // Also enable any associated apps for this carrier app. - if (associatedAppList != null) { - for (ApplicationInfo associatedApp : associatedAppList) { - if (associatedApp.enabledSetting == - PackageManager.COMPONENT_ENABLED_STATE_DEFAULT - || associatedApp.enabledSetting == - PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED - || (associatedApp.flags - & ApplicationInfo.FLAG_INSTALLED) == 0) { - Slog.i(TAG, "Update associated state(" + associatedApp.packageName - + "): ENABLED for user " + userId); - packageManager.setSystemAppInstallState( - associatedApp.packageName, - true /*installed*/, - 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 - && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { - Slog.i(TAG, "Update state(" + packageName - + "): DISABLED_UNTIL_USED for user " + userId); - packageManager.setSystemAppInstallState( - packageName, - false /*installed*/, - userId); - } - - // 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) { - if (associatedApp.enabledSetting - == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT - && (associatedApp.flags - & ApplicationInfo.FLAG_INSTALLED) != 0) { - Slog.i(TAG, - "Update associated state(" + associatedApp.packageName - + "): DISABLED_UNTIL_USED for user " + userId); - packageManager.setSystemAppInstallState( - associatedApp.packageName, - false /*installed*/, - userId); - } - } - } - } - } - } - - // 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()) { - // Since we enabled at least one app, ensure we grant default permissions to those - // apps. - String[] packageNames = new String[enabledCarrierPackages.size()]; - enabledCarrierPackages.toArray(packageNames); - packageManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, userId); - } - } catch (RemoteException e) { - Slog.w(TAG, "Could not reach PackageManager", e); - } - } - - /** - * Returns the list of "default" carrier apps. - * - * This is the subset of apps returned by - * {@link #getDefaultCarrierAppCandidates(IPackageManager, int)} which currently have carrier - * privileges per the SIM(s) inserted in the device. - */ - public static List getDefaultCarrierApps(IPackageManager packageManager, - TelephonyManager telephonyManager, int userId) { - // Get all system apps from the default list. - List candidates = getDefaultCarrierAppCandidates(packageManager, userId); - 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, IPackageManager, - * 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. - */ - public static List getDefaultCarrierAppCandidates( - IPackageManager packageManager, int userId) { - ArraySet systemCarrierAppsDisabledUntilUsed = - SystemConfig.getInstance().getDisabledUntilUsedPreinstalledCarrierApps(); - return getDefaultCarrierAppCandidatesHelper(packageManager, userId, - systemCarrierAppsDisabledUntilUsed); - } - - private static List getDefaultCarrierAppCandidatesHelper( - IPackageManager packageManager, - int userId, - ArraySet systemCarrierAppsDisabledUntilUsed) { - if (systemCarrierAppsDisabledUntilUsed == null) { - return null; - } - - int size = systemCarrierAppsDisabledUntilUsed.size(); - if (size == 0) { - return null; - } - - List apps = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); - ApplicationInfo ai = - getApplicationInfoIfSystemApp(packageManager, userId, packageName); - if (ai != null) { - apps.add(ai); - } - } - return apps; - } - - private static Map> getDefaultCarrierAssociatedAppsHelper( - IPackageManager packageManager, - int userId, - ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed) { - int size = systemCarrierAssociatedAppsDisabledUntilUsed.size(); - Map> associatedApps = new ArrayMap<>(size); - for (int i = 0; i < size; i++) { - String carrierAppPackage = systemCarrierAssociatedAppsDisabledUntilUsed.keyAt(i); - List 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 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 - | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId); - if (ai != null && ai.isSystemApp()) { - return ai; - } - } catch (RemoteException e) { - Slog.w(TAG, "Could not reach PackageManager", e); - } - return null; - } -} diff --git a/telephony/java/com/android/internal/telephony/util/ArrayUtils.java b/telephony/java/com/android/internal/telephony/util/ArrayUtils.java deleted file mode 100644 index 2905125c69cc..000000000000 --- a/telephony/java/com/android/internal/telephony/util/ArrayUtils.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2019 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.util; - -import android.annotation.NonNull; -import android.annotation.Nullable; - -import java.lang.reflect.Array; -import java.util.Collection; -import java.util.Map; -import java.util.Objects; - -/** Utility methods for array operations. */ -public final class ArrayUtils { - private ArrayUtils() { /* cannot be instantiated */ } - - /** - * Adds value to given array if not already present, providing set-like behavior. - */ - @SuppressWarnings("unchecked") - public static @NonNull T[] appendElement(Class kind, @Nullable T[] array, T element) { - return appendElement(kind, array, element, false); - } - - /** - * Adds value to given array. - */ - @SuppressWarnings("unchecked") - public static @NonNull T[] appendElement(Class kind, @Nullable T[] array, T element, - boolean allowDuplicates) { - final T[] result; - final int end; - if (array != null) { - if (!allowDuplicates && contains(array, element)) return array; - end = array.length; - result = (T[]) Array.newInstance(kind, end + 1); - System.arraycopy(array, 0, result, 0, end); - } else { - end = 0; - result = (T[]) Array.newInstance(kind, 1); - } - result[end] = element; - return result; - } - - /** - * Combine multiple arrays into a single array. - * - * @param kind The class of the array elements - * @param arrays The arrays to combine - * @param The class of the array elements (inferred from kind). - * @return A single array containing all the elements of the parameter arrays. - */ - @SuppressWarnings("unchecked") - public static @NonNull T[] concatElements(Class kind, @Nullable T[]... arrays) { - if (arrays == null || arrays.length == 0) { - return createEmptyArray(kind); - } - - int totalLength = 0; - for (T[] item : arrays) { - if (item == null) { - continue; - } - - totalLength += item.length; - } - - // Optimization for entirely empty arrays. - if (totalLength == 0) { - return createEmptyArray(kind); - } - - final T[] all = (T[]) Array.newInstance(kind, totalLength); - int pos = 0; - for (T[] item : arrays) { - if (item == null || item.length == 0) { - continue; - } - System.arraycopy(item, 0, all, pos, item.length); - pos += item.length; - } - return all; - } - - private static @NonNull T[] createEmptyArray(Class kind) { - if (kind == String.class) { - return (T[]) EmptyArray.STRING; - } else if (kind == Object.class) { - return (T[]) EmptyArray.OBJECT; - } - - return (T[]) Array.newInstance(kind, 0); - } - - private static final class EmptyArray { - private EmptyArray() {} - - public static final Object[] OBJECT = new Object[0]; - public static final String[] STRING = new String[0]; - } - - /** - * Checks if {@code value} is in {@code array}. - */ - public static boolean contains(@Nullable char[] array, char value) { - if (array == null) return false; - for (char element : array) { - if (element == value) { - return true; - } - } - return false; - } - - /** - * Checks if {@code value} is in {@code array}. - */ - public static boolean contains(@Nullable Collection cur, T val) { - return (cur != null) ? cur.contains(val) : false; - } - - /** - * Checks if {@code value} is in {@code array}. - */ - public static boolean contains(@Nullable int[] array, int value) { - if (array == null) return false; - for (int element : array) { - if (element == value) { - return true; - } - } - return false; - } - - /** - * Checks if {@code value} is in {@code array}. - */ - public static boolean contains(@Nullable long[] array, long value) { - if (array == null) return false; - for (long element : array) { - if (element == value) { - return true; - } - } - return false; - } - - /** - * Checks if {@code value} is in {@code array}. - */ - public static boolean contains(@Nullable T[] array, T value) { - return indexOf(array, value) != -1; - } - - /** - * Return first index of {@code value} in {@code array}, or {@code -1} if - * not found. - */ - public static int indexOf(@Nullable T[] array, T value) { - if (array == null) return -1; - for (int i = 0; i < array.length; i++) { - if (Objects.equals(array[i], value)) return i; - } - return -1; - } - - /** - * Checks if given array is null or has zero elements. - */ - public static boolean isEmpty(@Nullable Collection array) { - return array == null || array.isEmpty(); - } - - /** - * Checks if given map is null or has zero elements. - */ - public static boolean isEmpty(@Nullable Map map) { - return map == null || map.isEmpty(); - } - - /** - * Checks if given array is null or has zero elements. - */ - public static boolean isEmpty(@Nullable T[] array) { - return array == null || array.length == 0; - } - - /** - * Checks if given array is null or has zero elements. - */ - public static boolean isEmpty(@Nullable int[] array) { - return array == null || array.length == 0; - } - - /** - * Checks if given array is null or has zero elements. - */ - public static boolean isEmpty(@Nullable long[] array) { - return array == null || array.length == 0; - } - - /** - * Checks if given array is null or has zero elements. - */ - public static boolean isEmpty(@Nullable byte[] array) { - return array == null || array.length == 0; - } - - /** - * Checks if given array is null or has zero elements. - */ - public static boolean isEmpty(@Nullable boolean[] array) { - return array == null || array.length == 0; - } -} -- cgit v1.2.3-59-g8ed1b