| /* |
| * Copyright (C) 2021 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.server.pm.permission; |
| |
| import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY; |
| import static android.Manifest.permission.POST_NOTIFICATIONS; |
| import static android.Manifest.permission.READ_EXTERNAL_STORAGE; |
| import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; |
| import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE; |
| import static android.content.pm.PackageManager.MASK_PERMISSION_FLAGS_ALL; |
| import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; |
| import static android.os.Process.INVALID_UID; |
| import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; |
| import static android.permission.PermissionManager.KILL_APP_REASON_GIDS_CHANGED; |
| import static android.permission.PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED; |
| |
| import static com.android.server.pm.ApexManager.MATCH_ACTIVE_PACKAGE; |
| import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL; |
| import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING; |
| import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS; |
| import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE; |
| import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; |
| |
| import static java.util.concurrent.TimeUnit.SECONDS; |
| |
| import android.Manifest; |
| import android.annotation.AppIdInt; |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.UserIdInt; |
| import android.app.ActivityManager; |
| import android.app.IActivityManager; |
| import android.app.admin.DevicePolicyManagerInternal; |
| import android.compat.annotation.ChangeId; |
| import android.compat.annotation.EnabledAfter; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.FeatureInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManagerInternal; |
| import android.content.pm.PermissionGroupInfo; |
| import android.content.pm.PermissionInfo; |
| import android.content.pm.SigningDetails; |
| import android.content.pm.permission.SplitPermissionInfoParcelable; |
| import android.metrics.LogMaker; |
| import android.os.AsyncTask; |
| import android.os.Binder; |
| import android.os.Build; |
| import android.os.Debug; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.Process; |
| import android.os.RemoteCallbackList; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.SystemProperties; |
| import android.os.Trace; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.os.storage.StorageManager; |
| import android.permission.IOnPermissionsChangeListener; |
| import android.permission.PermissionControllerManager; |
| import android.permission.PermissionManager; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.DebugUtils; |
| import android.util.EventLog; |
| import android.util.IntArray; |
| import android.util.Log; |
| import android.util.Pair; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| import android.util.SparseBooleanArray; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.compat.IPlatformCompat; |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.nano.MetricsProto; |
| import com.android.internal.os.RoSystemProperties; |
| import com.android.internal.util.ArrayUtils; |
| import com.android.internal.util.CollectionUtils; |
| import com.android.internal.util.DumpUtils; |
| import com.android.internal.util.IntPair; |
| import com.android.internal.util.Preconditions; |
| import com.android.internal.util.function.pooled.PooledLambda; |
| import com.android.server.FgThread; |
| import com.android.server.LocalServices; |
| import com.android.server.ServiceThread; |
| import com.android.server.SystemConfig; |
| import com.android.server.Watchdog; |
| import com.android.server.pm.ApexManager; |
| import com.android.server.pm.KnownPackages; |
| import com.android.server.pm.UserManagerInternal; |
| import com.android.server.pm.UserManagerService; |
| import com.android.server.pm.parsing.PackageInfoUtils; |
| import com.android.server.pm.parsing.pkg.AndroidPackage; |
| import com.android.server.pm.parsing.pkg.AndroidPackageUtils; |
| import com.android.server.pm.pkg.AndroidPackageApi; |
| import com.android.server.pm.pkg.PackageStateInternal; |
| import com.android.server.pm.pkg.component.ComponentMutateUtils; |
| import com.android.server.pm.pkg.component.ParsedPermission; |
| import com.android.server.pm.pkg.component.ParsedPermissionGroup; |
| import com.android.server.pm.pkg.component.ParsedPermissionUtils; |
| import com.android.server.policy.PermissionPolicyInternal; |
| import com.android.server.policy.SoftRestrictedPermissionPolicy; |
| |
| import libcore.util.EmptyArray; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| |
| /** |
| * PermissionManagerServiceImpl. |
| */ |
| public class PermissionManagerServiceImpl implements PermissionManagerServiceInterface { |
| |
| private static final String TAG = "PackageManager"; |
| private static final String LOG_TAG = PermissionManagerServiceImpl.class.getSimpleName(); |
| |
| private static final String SKIP_KILL_APP_REASON_NOTIFICATION_TEST = "skip permission revoke " |
| + "app kill for notification test"; |
| |
| |
| private static final long BACKUP_TIMEOUT_MILLIS = SECONDS.toMillis(60); |
| |
| /** Cap the size of permission trees that 3rd party apps can define; in characters of text */ |
| private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768; |
| /** Empty array to avoid allocations */ |
| private static final int[] EMPTY_INT_ARRAY = new int[0]; |
| |
| /** |
| * When these flags are set, the system should not automatically modify the permission grant |
| * state. |
| */ |
| private static final int BLOCKING_PERMISSION_FLAGS = FLAG_PERMISSION_SYSTEM_FIXED |
| | FLAG_PERMISSION_POLICY_FIXED |
| | FLAG_PERMISSION_GRANTED_BY_DEFAULT; |
| |
| /** Permission flags set by the user */ |
| private static final int USER_PERMISSION_FLAGS = FLAG_PERMISSION_USER_SET |
| | FLAG_PERMISSION_USER_FIXED; |
| |
| /** All storage permissions */ |
| private static final List<String> STORAGE_PERMISSIONS = new ArrayList<>(); |
| |
| private static final Set<String> READ_MEDIA_AURAL_PERMISSIONS = new ArraySet<>(); |
| |
| private static final Set<String> READ_MEDIA_VISUAL_PERMISSIONS = new ArraySet<>(); |
| |
| /** All nearby devices permissions */ |
| private static final List<String> NEARBY_DEVICES_PERMISSIONS = new ArrayList<>(); |
| |
| /** |
| * All notification permissions. |
| * Notification permission state is treated differently from other permissions. Notification |
| * permission get the REVIEW_REQUIRED flag set for S- apps, or for T+ apps on updating to T or |
| * restoring a pre-T backup. The permission and app op remain denied. The flag will be read by |
| * the notification system, and allow apps to send notifications, until cleared. |
| * The flag is cleared for S- apps by the system showing a permission request prompt, and the |
| * user clicking "allow" or "deny" in the dialog. For T+ apps, the flag is cleared upon the |
| * first activity launch. |
| * |
| * @see PermissionPolicyInternal#showNotificationPromptIfNeeded(String, int, int) |
| */ |
| private static final List<String> NOTIFICATION_PERMISSIONS = new ArrayList<>(); |
| |
| /** If the permission of the value is granted, so is the key */ |
| private static final Map<String, String> FULLER_PERMISSION_MAP = new HashMap<>(); |
| |
| static { |
| FULLER_PERMISSION_MAP.put(Manifest.permission.ACCESS_COARSE_LOCATION, |
| Manifest.permission.ACCESS_FINE_LOCATION); |
| FULLER_PERMISSION_MAP.put(Manifest.permission.INTERACT_ACROSS_USERS, |
| Manifest.permission.INTERACT_ACROSS_USERS_FULL); |
| STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE); |
| STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); |
| READ_MEDIA_AURAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_AUDIO); |
| READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO); |
| READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES); |
| READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION); |
| NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_ADVERTISE); |
| NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT); |
| NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN); |
| NOTIFICATION_PERMISSIONS.add(Manifest.permission.POST_NOTIFICATIONS); |
| } |
| |
| /** Set of source package names for Privileged Permission Allowlist */ |
| private final ArraySet<String> mPrivilegedPermissionAllowlistSourcePackageNames = |
| new ArraySet<>(); |
| |
| /** Lock to protect internal data access */ |
| private final Object mLock = new Object(); |
| |
| /** Internal connection to the package manager */ |
| private final PackageManagerInternal mPackageManagerInt; |
| |
| /** Internal connection to the user manager */ |
| private final UserManagerInternal mUserManagerInt; |
| |
| @GuardedBy("mLock") |
| @NonNull |
| private final DevicePermissionState mState = new DevicePermissionState(); |
| |
| /** Permission controller: User space permission management */ |
| private PermissionControllerManager mPermissionControllerManager; |
| |
| /** |
| * Built-in permissions. Read from system configuration files. Mapping is from |
| * UID to permission name. |
| */ |
| private final SparseArray<ArraySet<String>> mSystemPermissions; |
| |
| /** Built-in group IDs given to all packages. Read from system configuration files. */ |
| @NonNull |
| private final int[] mGlobalGids; |
| |
| private final HandlerThread mHandlerThread; |
| private final Handler mHandler; |
| private final Context mContext; |
| private final MetricsLogger mMetricsLogger = new MetricsLogger(); |
| private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface( |
| ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); |
| |
| /** Internal storage for permissions and related settings */ |
| @GuardedBy("mLock") |
| @NonNull |
| private final PermissionRegistry mRegistry = new PermissionRegistry(); |
| |
| @GuardedBy("mLock") |
| @Nullable |
| private ArraySet<String> mPrivappPermissionsViolations; |
| |
| @GuardedBy("mLock") |
| private boolean mSystemReady; |
| |
| @GuardedBy("mLock") |
| private PermissionPolicyInternal mPermissionPolicyInternal; |
| |
| /** |
| * A permission backup might contain apps that are not installed. In this case we delay the |
| * restoration until the app is installed. |
| * |
| * <p>This array ({@code userId -> noDelayedBackupLeft}) is {@code true} for all the users where |
| * there is <u>no more</u> delayed backup left. |
| */ |
| @GuardedBy("mLock") |
| private final SparseBooleanArray mHasNoDelayedPermBackup = new SparseBooleanArray(); |
| |
| /** Listeners for permission state (granting and flags) changes */ |
| @GuardedBy("mLock") |
| private final ArrayList<PermissionManagerServiceInternal |
| .OnRuntimePermissionStateChangedListener> |
| mRuntimePermissionStateChangedListeners = new ArrayList<>(); |
| |
| private final boolean mIsLeanback; |
| |
| @NonNull |
| private final OnPermissionChangeListeners mOnPermissionChangeListeners; |
| |
| // TODO: Take a look at the methods defined in the callback. |
| // The callback was initially created to support the split between permission |
| // manager and the package manager. However, it's started to be used for other |
| // purposes. It may make sense to keep as an abstraction, but, the methods |
| // necessary to be overridden may be different than what was initially needed |
| // for the split. |
| private final PermissionCallback mDefaultPermissionCallback = new PermissionCallback() { |
| @Override |
| public void onGidsChanged(int appId, int userId) { |
| mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED)); |
| } |
| @Override |
| public void onPermissionGranted(int uid, int userId) { |
| mOnPermissionChangeListeners.onPermissionsChanged(uid); |
| |
| // Not critical; if this is lost, the application has to request again. |
| mPackageManagerInt.writeSettings(true); |
| } |
| @Override |
| public void onInstallPermissionGranted() { |
| mPackageManagerInt.writeSettings(true); |
| } |
| @Override |
| public void onPermissionRevoked(int uid, int userId, String reason, boolean overrideKill) { |
| mOnPermissionChangeListeners.onPermissionsChanged(uid); |
| |
| // Critical; after this call the application should never have the permission |
| mPackageManagerInt.writeSettings(false); |
| if (overrideKill) { |
| return; |
| } |
| |
| final int appId = UserHandle.getAppId(uid); |
| if (reason == null) { |
| mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED)); |
| } else { |
| mHandler.post(() -> killUid(appId, userId, reason)); |
| } |
| } |
| @Override |
| public void onInstallPermissionRevoked() { |
| mPackageManagerInt.writeSettings(true); |
| } |
| @Override |
| public void onPermissionUpdated(int[] userIds, boolean sync) { |
| mPackageManagerInt.writePermissionSettings(userIds, !sync); |
| } |
| @Override |
| public void onInstallPermissionUpdated() { |
| mPackageManagerInt.writeSettings(true); |
| } |
| @Override |
| public void onPermissionRemoved() { |
| mPackageManagerInt.writeSettings(false); |
| } |
| public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync, |
| int uid) { |
| onPermissionUpdated(updatedUserIds, sync); |
| for (int i = 0; i < updatedUserIds.length; i++) { |
| int userUid = UserHandle.getUid(updatedUserIds[i], UserHandle.getAppId(uid)); |
| mOnPermissionChangeListeners.onPermissionsChanged(userUid); |
| } |
| } |
| public void onInstallPermissionUpdatedNotifyListener(int uid) { |
| onInstallPermissionUpdated(); |
| mOnPermissionChangeListeners.onPermissionsChanged(uid); |
| } |
| }; |
| |
| public PermissionManagerServiceImpl(@NonNull Context context, |
| @NonNull ArrayMap<String, FeatureInfo> availableFeatures) { |
| // The package info cache is the cache for package and permission information. |
| // Disable the package info and package permission caches locally but leave the |
| // checkPermission cache active. |
| PackageManager.invalidatePackageInfoCache(); |
| PermissionManager.disablePackageNamePermissionCache(); |
| |
| mContext = context; |
| mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class); |
| mUserManagerInt = LocalServices.getService(UserManagerInternal.class); |
| mIsLeanback = availableFeatures.containsKey(PackageManager.FEATURE_LEANBACK); |
| |
| mPrivilegedPermissionAllowlistSourcePackageNames.add(PLATFORM_PACKAGE_NAME); |
| // PackageManager.hasSystemFeature() is not used here because PackageManagerService |
| // isn't ready yet. |
| if (availableFeatures.containsKey(PackageManager.FEATURE_AUTOMOTIVE)) { |
| // The property defined in car api surface, so use the string directly. |
| String carServicePackage = SystemProperties.get("ro.android.car.carservice.package", |
| null); |
| if (carServicePackage != null) { |
| mPrivilegedPermissionAllowlistSourcePackageNames.add(carServicePackage); |
| } |
| } |
| |
| mHandlerThread = new ServiceThread(TAG, |
| Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); |
| mHandlerThread.start(); |
| mHandler = new Handler(mHandlerThread.getLooper()); |
| Watchdog.getInstance().addThread(mHandler); |
| |
| SystemConfig systemConfig = SystemConfig.getInstance(); |
| mSystemPermissions = systemConfig.getSystemPermissions(); |
| mGlobalGids = systemConfig.getGlobalGids(); |
| mOnPermissionChangeListeners = new OnPermissionChangeListeners(FgThread.get().getLooper()); |
| |
| // propagate permission configuration |
| final ArrayMap<String, SystemConfig.PermissionEntry> permConfig = |
| SystemConfig.getInstance().getPermissions(); |
| synchronized (mLock) { |
| for (int i = 0; i < permConfig.size(); i++) { |
| final SystemConfig.PermissionEntry perm = permConfig.valueAt(i); |
| Permission bp = mRegistry.getPermission(perm.name); |
| if (bp == null) { |
| bp = new Permission(perm.name, "android", Permission.TYPE_CONFIG); |
| mRegistry.addPermission(bp); |
| } |
| if (perm.gids != null) { |
| bp.setGids(perm.gids, perm.perUser); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { |
| return; |
| } |
| |
| mContext.getSystemService(PermissionControllerManager.class).dump(fd, args); |
| } |
| |
| /** |
| * This method should typically only be used when granting or revoking |
| * permissions, since the app may immediately restart after this call. |
| * <p> |
| * If you're doing surgery on app code/data, use {@link PackageFreezer} to |
| * guard your work against the app being relaunched. |
| */ |
| private static void killUid(int appId, int userId, String reason) { |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| IActivityManager am = ActivityManager.getService(); |
| if (am != null) { |
| try { |
| am.killUidForPermissionChange(appId, userId, reason); |
| } catch (RemoteException e) { |
| /* ignore - same process */ |
| } |
| } |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| @NonNull |
| private String[] getAppOpPermissionPackagesInternal(@NonNull String permissionName) { |
| synchronized (mLock) { |
| final ArraySet<String> packageNames = mRegistry.getAppOpPermissionPackages( |
| permissionName); |
| if (packageNames == null) { |
| return EmptyArray.STRING; |
| } |
| return packageNames.toArray(new String[0]); |
| } |
| } |
| |
| @Override |
| @NonNull |
| public List<PermissionGroupInfo> getAllPermissionGroups( |
| @PackageManager.PermissionGroupInfoFlags int flags) { |
| final int callingUid = Binder.getCallingUid(); |
| if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { |
| return Collections.emptyList(); |
| } |
| |
| final List<PermissionGroupInfo> out = new ArrayList<>(); |
| synchronized (mLock) { |
| for (ParsedPermissionGroup pg : mRegistry.getPermissionGroups()) { |
| out.add(PackageInfoUtils.generatePermissionGroupInfo(pg, flags)); |
| } |
| } |
| |
| final int callingUserId = UserHandle.getUserId(callingUid); |
| out.removeIf(it -> mPackageManagerInt.filterAppAccess(it.packageName, callingUid, |
| callingUserId)); |
| return out; |
| } |
| |
| @Override |
| @Nullable |
| public PermissionGroupInfo getPermissionGroupInfo(String groupName, |
| @PackageManager.PermissionGroupInfoFlags int flags) { |
| final int callingUid = Binder.getCallingUid(); |
| if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { |
| return null; |
| } |
| |
| final PermissionGroupInfo permissionGroupInfo; |
| synchronized (mLock) { |
| final ParsedPermissionGroup permissionGroup = mRegistry.getPermissionGroup(groupName); |
| if (permissionGroup == null) { |
| return null; |
| } |
| permissionGroupInfo = PackageInfoUtils.generatePermissionGroupInfo(permissionGroup, |
| flags); |
| } |
| |
| final int callingUserId = UserHandle.getUserId(callingUid); |
| if (mPackageManagerInt.filterAppAccess(permissionGroupInfo.packageName, callingUid, |
| callingUserId)) { |
| EventLog.writeEvent(0x534e4554, "186113473", callingUid, groupName); |
| return null; |
| } |
| return permissionGroupInfo; |
| } |
| |
| @Override |
| @Nullable |
| public PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName, |
| @PackageManager.PermissionInfoFlags int flags) { |
| final int callingUid = Binder.getCallingUid(); |
| if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { |
| return null; |
| } |
| |
| final AndroidPackage opPackage = mPackageManagerInt.getPackage(opPackageName); |
| final int targetSdkVersion = getPermissionInfoCallingTargetSdkVersion(opPackage, |
| callingUid); |
| final PermissionInfo permissionInfo; |
| synchronized (mLock) { |
| final Permission bp = mRegistry.getPermission(permName); |
| if (bp == null) { |
| return null; |
| } |
| permissionInfo = bp.generatePermissionInfo(flags, targetSdkVersion); |
| } |
| |
| final int callingUserId = UserHandle.getUserId(callingUid); |
| if (mPackageManagerInt.filterAppAccess(permissionInfo.packageName, callingUid, |
| callingUserId)) { |
| EventLog.writeEvent(0x534e4554, "183122164", callingUid, permName); |
| return null; |
| } |
| return permissionInfo; |
| } |
| |
| private int getPermissionInfoCallingTargetSdkVersion(@Nullable AndroidPackage pkg, int uid) { |
| final int appId = UserHandle.getAppId(uid); |
| if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID |
| || appId == Process.SHELL_UID) { |
| // System sees all flags. |
| return Build.VERSION_CODES.CUR_DEVELOPMENT; |
| } |
| if (pkg == null) { |
| return Build.VERSION_CODES.CUR_DEVELOPMENT; |
| } |
| return pkg.getTargetSdkVersion(); |
| } |
| |
| @Override |
| @Nullable |
| public List<PermissionInfo> queryPermissionsByGroup(String groupName, |
| @PackageManager.PermissionInfoFlags int flags) { |
| final int callingUid = Binder.getCallingUid(); |
| if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { |
| return null; |
| } |
| |
| final List<PermissionInfo> out = new ArrayList<>(10); |
| synchronized (mLock) { |
| if (groupName != null && mRegistry.getPermissionGroup(groupName) == null) { |
| return null; |
| } |
| for (Permission bp : mRegistry.getPermissions()) { |
| if (Objects.equals(bp.getGroup(), groupName)) { |
| out.add(bp.generatePermissionInfo(flags)); |
| } |
| } |
| } |
| |
| final int callingUserId = UserHandle.getUserId(callingUid); |
| out.removeIf(it -> mPackageManagerInt.filterAppAccess(it.packageName, callingUid, |
| callingUserId)); |
| return out; |
| } |
| |
| @Override |
| public boolean addPermission(PermissionInfo info, boolean async) { |
| final int callingUid = Binder.getCallingUid(); |
| if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { |
| throw new SecurityException("Instant apps can't add permissions"); |
| } |
| if (info.labelRes == 0 && info.nonLocalizedLabel == null) { |
| throw new SecurityException("Label must be specified in permission"); |
| } |
| final boolean added; |
| final boolean changed; |
| synchronized (mLock) { |
| final Permission tree = mRegistry.enforcePermissionTree(info.name, callingUid); |
| Permission bp = mRegistry.getPermission(info.name); |
| added = bp == null; |
| int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel); |
| if (added) { |
| enforcePermissionCapLocked(info, tree); |
| bp = new Permission(info.name, tree.getPackageName(), Permission.TYPE_DYNAMIC); |
| } else if (!bp.isDynamic()) { |
| throw new SecurityException("Not allowed to modify non-dynamic permission " |
| + info.name); |
| } |
| changed = bp.addToTree(fixedLevel, info, tree); |
| if (added) { |
| mRegistry.addPermission(bp); |
| } |
| } |
| if (changed) { |
| mPackageManagerInt.writeSettings(async); |
| } |
| return added; |
| } |
| |
| @Override |
| public void removePermission(String permName) { |
| final int callingUid = Binder.getCallingUid(); |
| if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { |
| throw new SecurityException("Instant applications don't have access to this method"); |
| } |
| synchronized (mLock) { |
| mRegistry.enforcePermissionTree(permName, callingUid); |
| final Permission bp = mRegistry.getPermission(permName); |
| if (bp == null) { |
| return; |
| } |
| if (bp.isDynamic()) { |
| // TODO: switch this back to SecurityException |
| Slog.wtf(TAG, "Not allowed to modify non-dynamic permission " |
| + permName); |
| } |
| mRegistry.removePermission(permName); |
| } |
| mPackageManagerInt.writeSettings(false); |
| } |
| |
| @Override |
| public int getPermissionFlags(String packageName, String permName, int userId) { |
| final int callingUid = Binder.getCallingUid(); |
| return getPermissionFlagsInternal(packageName, permName, callingUid, userId); |
| } |
| |
| private int getPermissionFlagsInternal( |
| String packageName, String permName, int callingUid, int userId) { |
| if (!mUserManagerInt.exists(userId)) { |
| return 0; |
| } |
| |
| enforceGrantRevokeGetRuntimePermissionPermissions("getPermissionFlags"); |
| enforceCrossUserPermission(callingUid, userId, |
| true, // requireFullPermission |
| false, // checkShell |
| "getPermissionFlags"); |
| |
| final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); |
| if (pkg == null) { |
| return 0; |
| } |
| if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { |
| return 0; |
| } |
| |
| synchronized (mLock) { |
| if (mRegistry.getPermission(permName) == null) { |
| return 0; |
| } |
| |
| final UidPermissionState uidState = getUidStateLocked(pkg, userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); |
| return 0; |
| } |
| |
| return uidState.getPermissionFlags(permName); |
| } |
| } |
| |
| @Override |
| public void updatePermissionFlags(String packageName, String permName, int flagMask, |
| int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) { |
| final int callingUid = Binder.getCallingUid(); |
| boolean overridePolicy = false; |
| |
| if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) { |
| final long callingIdentity = Binder.clearCallingIdentity(); |
| try { |
| if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0) { |
| if (checkAdjustPolicyFlagPermission) { |
| mContext.enforceCallingOrSelfPermission( |
| Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, |
| "Need " + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY |
| + " to change policy flags"); |
| } else if (mPackageManagerInt.getUidTargetSdkVersion(callingUid) |
| >= Build.VERSION_CODES.Q) { |
| throw new IllegalArgumentException( |
| Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY + " needs " |
| + " to be checked for packages targeting " |
| + Build.VERSION_CODES.Q + " or later when changing policy " |
| + "flags"); |
| } |
| overridePolicy = true; |
| } |
| } finally { |
| Binder.restoreCallingIdentity(callingIdentity); |
| } |
| } |
| |
| updatePermissionFlagsInternal( |
| packageName, permName, flagMask, flagValues, callingUid, userId, |
| overridePolicy, mDefaultPermissionCallback); |
| } |
| |
| private void updatePermissionFlagsInternal(String packageName, String permName, int flagMask, |
| int flagValues, int callingUid, int userId, boolean overridePolicy, |
| PermissionCallback callback) { |
| if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES |
| && PermissionManager.shouldTraceGrant(packageName, permName, userId)) { |
| Log.i(TAG, "System is updating flags for " + packageName + " " |
| + permName + " for user " + userId + " " |
| + DebugUtils.flagsToString( |
| PackageManager.class, "FLAG_PERMISSION_", flagMask) |
| + " := " |
| + DebugUtils.flagsToString( |
| PackageManager.class, "FLAG_PERMISSION_", flagValues) |
| + " on behalf of uid " + callingUid |
| + " " + mPackageManagerInt.getNameForUid(callingUid), |
| new RuntimeException()); |
| } |
| |
| if (!mUserManagerInt.exists(userId)) { |
| return; |
| } |
| |
| enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags"); |
| |
| enforceCrossUserPermission(callingUid, userId, |
| true, // requireFullPermission |
| true, // checkShell |
| "updatePermissionFlags"); |
| |
| if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0 && !overridePolicy) { |
| throw new SecurityException("updatePermissionFlags requires " |
| + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY); |
| } |
| |
| // Only the system can change these flags and nothing else. |
| if (callingUid != Process.SYSTEM_UID) { |
| flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; |
| flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; |
| flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; |
| flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; |
| flagValues &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; |
| flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; |
| flagValues &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; |
| flagValues &= ~PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; |
| // REVIEW_REQUIRED can be set on any permission by the shell or the root uid, or by |
| // any app for the POST_NOTIFICATIONS permission specifically. |
| if (!POST_NOTIFICATIONS.equals(permName) && callingUid != Process.SHELL_UID |
| && callingUid != Process.ROOT_UID) { |
| flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; |
| } |
| } |
| |
| final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); |
| if (pkg == null) { |
| Log.e(TAG, "Unknown package: " + packageName); |
| return; |
| } |
| if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { |
| throw new IllegalArgumentException("Unknown package: " + packageName); |
| } |
| |
| boolean isRequested = false; |
| // Fast path, the current package has requested the permission. |
| if (pkg.getRequestedPermissions().contains(permName)) { |
| isRequested = true; |
| } |
| if (!isRequested) { |
| // Slow path, go through all shared user packages. |
| String[] sharedUserPackageNames = |
| mPackageManagerInt.getSharedUserPackagesForPackage(packageName, userId); |
| for (String sharedUserPackageName : sharedUserPackageNames) { |
| AndroidPackage sharedUserPkg = mPackageManagerInt.getPackage( |
| sharedUserPackageName); |
| if (sharedUserPkg != null |
| && sharedUserPkg.getRequestedPermissions().contains(permName)) { |
| isRequested = true; |
| break; |
| } |
| } |
| } |
| |
| final boolean isRuntimePermission; |
| final boolean permissionUpdated; |
| synchronized (mLock) { |
| final Permission bp = mRegistry.getPermission(permName); |
| if (bp == null) { |
| throw new IllegalArgumentException("Unknown permission: " + permName); |
| } |
| |
| isRuntimePermission = bp.isRuntime(); |
| |
| final UidPermissionState uidState = getUidStateLocked(pkg, userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); |
| return; |
| } |
| |
| if (!uidState.hasPermissionState(permName) && !isRequested) { |
| Log.e(TAG, "Permission " + permName + " isn't requested by package " + packageName); |
| return; |
| } |
| |
| permissionUpdated = uidState.updatePermissionFlags(bp, flagMask, flagValues); |
| } |
| |
| if (permissionUpdated && isRuntimePermission) { |
| notifyRuntimePermissionStateChanged(packageName, userId); |
| } |
| if (permissionUpdated && callback != null) { |
| // Install and runtime permissions are stored in different places, |
| // so figure out what permission changed and persist the change. |
| if (!isRuntimePermission) { |
| int userUid = UserHandle.getUid(userId, pkg.getUid()); |
| callback.onInstallPermissionUpdatedNotifyListener(userUid); |
| } else { |
| callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false, pkg.getUid()); |
| } |
| } |
| } |
| |
| /** |
| * Update the permission flags for all packages and runtime permissions of a user in order |
| * to allow device or profile owner to remove POLICY_FIXED. |
| */ |
| @Override |
| public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, |
| final int userId) { |
| final int callingUid = Binder.getCallingUid(); |
| if (!mUserManagerInt.exists(userId)) { |
| return; |
| } |
| |
| enforceGrantRevokeRuntimePermissionPermissions( |
| "updatePermissionFlagsForAllApps"); |
| enforceCrossUserPermission(callingUid, userId, |
| true, // requireFullPermission |
| true, // checkShell |
| "updatePermissionFlagsForAllApps"); |
| |
| // Only the system can change system fixed flags. |
| final int effectiveFlagMask = (callingUid != Process.SYSTEM_UID) |
| ? flagMask : flagMask & ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; |
| final int effectiveFlagValues = (callingUid != Process.SYSTEM_UID) |
| ? flagValues : flagValues & ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; |
| |
| final boolean[] changed = new boolean[1]; |
| mPackageManagerInt.forEachPackage(pkg -> { |
| synchronized (mLock) { |
| final UidPermissionState uidState = getUidStateLocked(pkg, userId); |
| if (uidState == null) { |
| Slog.e(TAG, |
| "Missing permissions state for " + pkg.getPackageName() + " and user " |
| + userId); |
| return; |
| } |
| changed[0] |= uidState.updatePermissionFlagsForAllPermissions( |
| effectiveFlagMask, effectiveFlagValues); |
| } |
| mOnPermissionChangeListeners.onPermissionsChanged(pkg.getUid()); |
| }); |
| |
| if (changed[0]) { |
| mPackageManagerInt.writePermissionSettings(new int[] { userId }, true); |
| } |
| } |
| |
| @Override |
| public int checkPermission(String pkgName, String permName, int userId) { |
| if (!mUserManagerInt.exists(userId)) { |
| return PackageManager.PERMISSION_DENIED; |
| } |
| |
| final AndroidPackage pkg = mPackageManagerInt.getPackage(pkgName); |
| if (pkg == null) { |
| return PackageManager.PERMISSION_DENIED; |
| } |
| return checkPermissionInternal(pkg, true, permName, userId); |
| } |
| |
| private int checkPermissionInternal(@NonNull AndroidPackage pkg, boolean isPackageExplicit, |
| @NonNull String permissionName, @UserIdInt int userId) { |
| final int callingUid = Binder.getCallingUid(); |
| if (isPackageExplicit || pkg.getSharedUserId() == null) { |
| if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { |
| return PackageManager.PERMISSION_DENIED; |
| } |
| } else { |
| if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { |
| return PackageManager.PERMISSION_DENIED; |
| } |
| } |
| |
| final int uid = UserHandle.getUid(userId, pkg.getUid()); |
| final boolean isInstantApp = mPackageManagerInt.getInstantAppPackageName(uid) != null; |
| |
| synchronized (mLock) { |
| final UidPermissionState uidState = getUidStateLocked(pkg, userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " |
| + userId); |
| return PackageManager.PERMISSION_DENIED; |
| } |
| |
| if (checkSinglePermissionInternalLocked(uidState, permissionName, isInstantApp)) { |
| return PackageManager.PERMISSION_GRANTED; |
| } |
| |
| final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName); |
| if (fullerPermissionName != null && checkSinglePermissionInternalLocked(uidState, |
| fullerPermissionName, isInstantApp)) { |
| return PackageManager.PERMISSION_GRANTED; |
| } |
| } |
| |
| return PackageManager.PERMISSION_DENIED; |
| } |
| |
| @GuardedBy("mLock") |
| private boolean checkSinglePermissionInternalLocked(@NonNull UidPermissionState uidState, |
| @NonNull String permissionName, boolean isInstantApp) { |
| if (!uidState.isPermissionGranted(permissionName)) { |
| return false; |
| } |
| |
| if (isInstantApp) { |
| final Permission permission = mRegistry.getPermission(permissionName); |
| return permission != null && permission.isInstant(); |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public int checkUidPermission(int uid, String permName) { |
| final int userId = UserHandle.getUserId(uid); |
| if (!mUserManagerInt.exists(userId)) { |
| return PackageManager.PERMISSION_DENIED; |
| } |
| |
| final AndroidPackage pkg = mPackageManagerInt.getPackage(uid); |
| return checkUidPermissionInternal(pkg, uid, permName); |
| } |
| |
| /** |
| * Checks whether or not the given package has been granted the specified |
| * permission. If the given package is {@code null}, we instead check the |
| * system permissions for the given UID. |
| * |
| * @see SystemConfig#getSystemPermissions() |
| */ |
| private int checkUidPermissionInternal(@Nullable AndroidPackage pkg, int uid, |
| @NonNull String permissionName) { |
| if (pkg != null) { |
| final int userId = UserHandle.getUserId(uid); |
| return checkPermissionInternal(pkg, false, permissionName, userId); |
| } |
| |
| synchronized (mLock) { |
| if (checkSingleUidPermissionInternalLocked(uid, permissionName)) { |
| return PackageManager.PERMISSION_GRANTED; |
| } |
| |
| final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName); |
| if (fullerPermissionName != null |
| && checkSingleUidPermissionInternalLocked(uid, fullerPermissionName)) { |
| return PackageManager.PERMISSION_GRANTED; |
| } |
| } |
| |
| return PackageManager.PERMISSION_DENIED; |
| } |
| |
| @GuardedBy("mLock") |
| private boolean checkSingleUidPermissionInternalLocked(int uid, |
| @NonNull String permissionName) { |
| ArraySet<String> permissions = mSystemPermissions.get(uid); |
| return permissions != null && permissions.contains(permissionName); |
| } |
| |
| @Override |
| public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) { |
| mContext.enforceCallingOrSelfPermission( |
| Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, |
| "addOnPermissionsChangeListener"); |
| |
| mOnPermissionChangeListeners.addListener(listener); |
| } |
| |
| @Override |
| public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener) { |
| if (mPackageManagerInt.getInstantAppPackageName(Binder.getCallingUid()) != null) { |
| throw new SecurityException("Instant applications don't have access to this method"); |
| } |
| mOnPermissionChangeListeners.removeListener(listener); |
| } |
| |
| @Nullable |
| @Override |
| public List<String> getAllowlistedRestrictedPermissions(@NonNull String packageName, |
| @PackageManager.PermissionWhitelistFlags int flags, @UserIdInt int userId) { |
| Objects.requireNonNull(packageName); |
| Preconditions.checkFlagsArgument(flags, |
| PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE |
| | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM |
| | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER); |
| Preconditions.checkArgumentNonNegative(userId, null); |
| |
| if (UserHandle.getCallingUserId() != userId) { |
| mContext.enforceCallingOrSelfPermission( |
| android.Manifest.permission.INTERACT_ACROSS_USERS, |
| "getAllowlistedRestrictedPermissions for user " + userId); |
| } |
| |
| final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); |
| if (pkg == null) { |
| return null; |
| } |
| |
| final int callingUid = Binder.getCallingUid(); |
| if (mPackageManagerInt.filterAppAccess(pkg, callingUid, UserHandle.getCallingUserId())) { |
| return null; |
| } |
| final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission( |
| Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS) |
| == PackageManager.PERMISSION_GRANTED; |
| final boolean isCallerInstallerOnRecord = |
| mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid); |
| |
| if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 |
| && !isCallerPrivileged) { |
| throw new SecurityException("Querying system allowlist requires " |
| + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); |
| } |
| |
| if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE |
| | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) != 0) { |
| if (!isCallerPrivileged && !isCallerInstallerOnRecord) { |
| throw new SecurityException("Querying upgrade or installer allowlist" |
| + " requires being installer on record or " |
| + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); |
| } |
| } |
| |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| return getAllowlistedRestrictedPermissionsInternal(pkg, flags, userId); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| @Nullable |
| private List<String> getAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg, |
| @PackageManager.PermissionWhitelistFlags int flags, @UserIdInt int userId) { |
| synchronized (mLock) { |
| final UidPermissionState uidState = getUidStateLocked(pkg, userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " |
| + userId); |
| return null; |
| } |
| |
| int queryFlags = 0; |
| if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0) { |
| queryFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; |
| } |
| if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) { |
| queryFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; |
| } |
| if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) { |
| queryFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; |
| } |
| |
| ArrayList<String> allowlistedPermissions = null; |
| |
| final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions()); |
| for (int i = 0; i < permissionCount; i++) { |
| final String permissionName = pkg.getRequestedPermissions().get(i); |
| final int currentFlags = |
| uidState.getPermissionFlags(permissionName); |
| if ((currentFlags & queryFlags) != 0) { |
| if (allowlistedPermissions == null) { |
| allowlistedPermissions = new ArrayList<>(); |
| } |
| allowlistedPermissions.add(permissionName); |
| } |
| } |
| |
| return allowlistedPermissions; |
| } |
| } |
| |
| @Override |
| public boolean addAllowlistedRestrictedPermission(@NonNull String packageName, |
| @NonNull String permName, @PackageManager.PermissionWhitelistFlags int flags, |
| @UserIdInt int userId) { |
| // Other argument checks are done in get/setAllowlistedRestrictedPermissions |
| Objects.requireNonNull(permName); |
| |
| if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) { |
| return false; |
| } |
| |
| List<String> permissions = |
| getAllowlistedRestrictedPermissions(packageName, flags, userId); |
| if (permissions == null) { |
| permissions = new ArrayList<>(1); |
| } |
| if (permissions.indexOf(permName) < 0) { |
| permissions.add(permName); |
| return setAllowlistedRestrictedPermissions(packageName, permissions, |
| flags, userId); |
| } |
| return false; |
| } |
| |
| private boolean checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission( |
| @NonNull String permName) { |
| final String permissionPackageName; |
| final boolean isImmutablyRestrictedPermission; |
| synchronized (mLock) { |
| final Permission bp = mRegistry.getPermission(permName); |
| if (bp == null) { |
| Slog.w(TAG, "No such permissions: " + permName); |
| return false; |
| } |
| permissionPackageName = bp.getPackageName(); |
| isImmutablyRestrictedPermission = bp.isHardOrSoftRestricted() |
| && bp.isImmutablyRestricted(); |
| } |
| |
| final int callingUid = Binder.getCallingUid(); |
| final int callingUserId = UserHandle.getUserId(callingUid); |
| if (mPackageManagerInt.filterAppAccess(permissionPackageName, callingUid, callingUserId)) { |
| EventLog.writeEvent(0x534e4554, "186404356", callingUid, permName); |
| return false; |
| } |
| |
| if (isImmutablyRestrictedPermission && mContext.checkCallingOrSelfPermission( |
| Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS) |
| != PackageManager.PERMISSION_GRANTED) { |
| throw new SecurityException("Cannot modify allowlisting of an immutably " |
| + "restricted permission: " + permName); |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public boolean removeAllowlistedRestrictedPermission(@NonNull String packageName, |
| @NonNull String permName, @PackageManager.PermissionWhitelistFlags int flags, |
| @UserIdInt int userId) { |
| // Other argument checks are done in get/setAllowlistedRestrictedPermissions |
| Objects.requireNonNull(permName); |
| |
| if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) { |
| return false; |
| } |
| |
| final List<String> permissions = |
| getAllowlistedRestrictedPermissions(packageName, flags, userId); |
| if (permissions != null && permissions.remove(permName)) { |
| return setAllowlistedRestrictedPermissions(packageName, permissions, |
| flags, userId); |
| } |
| return false; |
| } |
| |
| private boolean setAllowlistedRestrictedPermissions(@NonNull String packageName, |
| @Nullable List<String> permissions, @PackageManager.PermissionWhitelistFlags int flags, |
| @UserIdInt int userId) { |
| Objects.requireNonNull(packageName); |
| Preconditions.checkFlagsArgument(flags, |
| PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE |
| | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM |
| | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER); |
| Preconditions.checkArgument(Integer.bitCount(flags) == 1); |
| Preconditions.checkArgumentNonNegative(userId, null); |
| |
| if (UserHandle.getCallingUserId() != userId) { |
| mContext.enforceCallingOrSelfPermission( |
| Manifest.permission.INTERACT_ACROSS_USERS, |
| "setAllowlistedRestrictedPermissions for user " + userId); |
| } |
| |
| final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); |
| if (pkg == null) { |
| return false; |
| } |
| |
| final int callingUid = Binder.getCallingUid(); |
| if (mPackageManagerInt.filterAppAccess(pkg, callingUid, UserHandle.getCallingUserId())) { |
| return false; |
| } |
| |
| final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission( |
| Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS) |
| == PackageManager.PERMISSION_GRANTED; |
| final boolean isCallerInstallerOnRecord = |
| mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid); |
| |
| if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 && !isCallerPrivileged) { |
| throw new SecurityException("Modifying system allowlist requires " |
| + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); |
| } |
| |
| if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) { |
| if (!isCallerPrivileged && !isCallerInstallerOnRecord) { |
| throw new SecurityException("Modifying upgrade allowlist requires" |
| + " being installer on record or " |
| + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); |
| } |
| final List<String> allowlistedPermissions = |
| getAllowlistedRestrictedPermissions(pkg.getPackageName(), flags, userId); |
| if (permissions == null || permissions.isEmpty()) { |
| if (allowlistedPermissions == null || allowlistedPermissions.isEmpty()) { |
| return true; |
| } |
| } else { |
| // Only the system can add and remove while the installer can only remove. |
| final int permissionCount = permissions.size(); |
| for (int i = 0; i < permissionCount; i++) { |
| if ((allowlistedPermissions == null |
| || !allowlistedPermissions.contains(permissions.get(i))) |
| && !isCallerPrivileged) { |
| throw new SecurityException("Adding to upgrade allowlist requires" |
| + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); |
| } |
| } |
| } |
| |
| if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) { |
| if (!isCallerPrivileged && !isCallerInstallerOnRecord) { |
| throw new SecurityException("Modifying installer allowlist requires" |
| + " being installer on record or " |
| + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); |
| } |
| } |
| } |
| |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| setAllowlistedRestrictedPermissionsInternal(pkg, permissions, flags, userId); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public void grantRuntimePermission(String packageName, String permName, final int userId) { |
| final int callingUid = Binder.getCallingUid(); |
| final boolean overridePolicy = |
| checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY) |
| == PackageManager.PERMISSION_GRANTED; |
| |
| grantRuntimePermissionInternal(packageName, permName, overridePolicy, |
| callingUid, userId, mDefaultPermissionCallback); |
| } |
| |
| private void grantRuntimePermissionInternal(String packageName, String permName, |
| boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) { |
| if (PermissionManager.DEBUG_TRACE_GRANTS |
| && PermissionManager.shouldTraceGrant(packageName, permName, userId)) { |
| Log.i(PermissionManager.LOG_TAG_TRACE_GRANTS, "System is granting " + packageName + " " |
| + permName + " for user " + userId + " on behalf of uid " + callingUid |
| + " " + mPackageManagerInt.getNameForUid(callingUid), |
| new RuntimeException()); |
| } |
| if (!mUserManagerInt.exists(userId)) { |
| Log.e(TAG, "No such user:" + userId); |
| return; |
| } |
| |
| mContext.enforceCallingOrSelfPermission( |
| android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, |
| "grantRuntimePermission"); |
| |
| enforceCrossUserPermission(callingUid, userId, |
| true, // requireFullPermission |
| true, // checkShell |
| "grantRuntimePermission"); |
| |
| final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); |
| final PackageStateInternal ps = mPackageManagerInt.getPackageStateInternal(packageName); |
| if (pkg == null || ps == null) { |
| Log.e(TAG, "Unknown package: " + packageName); |
| return; |
| } |
| if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { |
| throw new IllegalArgumentException("Unknown package: " + packageName); |
| } |
| |
| final boolean isRolePermission; |
| final boolean isSoftRestrictedPermission; |
| synchronized (mLock) { |
| final Permission permission = mRegistry.getPermission(permName); |
| if (permission == null) { |
| throw new IllegalArgumentException("Unknown permission: " + permName); |
| } |
| isRolePermission = permission.isRole(); |
| isSoftRestrictedPermission = permission.isSoftRestricted(); |
| } |
| final boolean mayGrantRolePermission = isRolePermission |
| && mayManageRolePermission(callingUid); |
| final boolean mayGrantSoftRestrictedPermission = isSoftRestrictedPermission |
| && SoftRestrictedPermissionPolicy.forPermission(mContext, |
| AndroidPackageUtils.generateAppInfoWithoutState(pkg), pkg, |
| UserHandle.of(userId), permName) |
| .mayGrantPermission(); |
| |
| final boolean isRuntimePermission; |
| final boolean permissionHasGids; |
| synchronized (mLock) { |
| final Permission bp = mRegistry.getPermission(permName); |
| if (bp == null) { |
| throw new IllegalArgumentException("Unknown permission: " + permName); |
| } |
| |
| isRuntimePermission = bp.isRuntime(); |
| permissionHasGids = bp.hasGids(); |
| if (isRuntimePermission || bp.isDevelopment()) { |
| // Good. |
| } else if (bp.isRole()) { |
| if (!mayGrantRolePermission) { |
| throw new SecurityException("Permission " + permName + " is managed by role"); |
| } |
| } else { |
| throw new SecurityException("Permission " + permName + " requested by " |
| + pkg.getPackageName() + " is not a changeable permission type"); |
| } |
| |
| final UidPermissionState uidState = getUidStateLocked(pkg, userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " |
| + userId); |
| return; |
| } |
| |
| if (!(uidState.hasPermissionState(permName) |
| || pkg.getRequestedPermissions().contains(permName))) { |
| throw new SecurityException("Package " + pkg.getPackageName() |
| + " has not requested permission " + permName); |
| } |
| |
| // If a permission review is required for legacy apps we represent |
| // their permissions as always granted runtime ones since we need |
| // to keep the review required permission flag per user while an |
| // install permission's state is shared across all users. |
| if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) { |
| return; |
| } |
| |
| final int flags = uidState.getPermissionFlags(permName); |
| if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) { |
| Log.e(TAG, "Cannot grant system fixed permission " |
| + permName + " for package " + packageName); |
| return; |
| } |
| if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) { |
| Log.e(TAG, "Cannot grant policy fixed permission " |
| + permName + " for package " + packageName); |
| return; |
| } |
| |
| if (bp.isHardRestricted() |
| && (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) { |
| Log.e(TAG, "Cannot grant hard restricted non-exempt permission " |
| + permName + " for package " + packageName); |
| return; |
| } |
| |
| if (bp.isSoftRestricted() && !mayGrantSoftRestrictedPermission) { |
| Log.e(TAG, "Cannot grant soft restricted permission " + permName + " for package " |
| + packageName); |
| return; |
| } |
| |
| if (bp.isDevelopment() || bp.isRole()) { |
| // Development permissions must be handled specially, since they are not |
| // normal runtime permissions. For now they apply to all users. |
| // TODO(zhanghai): We are breaking the behavior above by making all permission state |
| // per-user. It isn't documented behavior and relatively rarely used anyway. |
| if (!uidState.grantPermission(bp)) { |
| return; |
| } |
| } else { |
| if (ps.getUserStateOrDefault(userId).isInstantApp() && !bp.isInstant()) { |
| throw new SecurityException("Cannot grant non-ephemeral permission " + permName |
| + " for package " + packageName); |
| } |
| |
| if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) { |
| Slog.w(TAG, "Cannot grant runtime permission to a legacy app"); |
| return; |
| } |
| |
| if (!uidState.grantPermission(bp)) { |
| return; |
| } |
| } |
| } |
| |
| if (isRuntimePermission) { |
| logPermission(MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED, |
| permName, packageName); |
| } |
| |
| final int uid = UserHandle.getUid(userId, pkg.getUid()); |
| if (callback != null) { |
| if (isRuntimePermission) { |
| callback.onPermissionGranted(uid, userId); |
| } else { |
| callback.onInstallPermissionGranted(); |
| } |
| if (permissionHasGids) { |
| callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId); |
| } |
| } |
| |
| if (isRuntimePermission) { |
| notifyRuntimePermissionStateChanged(packageName, userId); |
| } |
| } |
| |
| @Override |
| public void revokeRuntimePermission(String packageName, String permName, int userId, |
| String reason) { |
| final int callingUid = Binder.getCallingUid(); |
| final boolean overridePolicy = |
| checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY) |
| == PackageManager.PERMISSION_GRANTED; |
| |
| revokeRuntimePermissionInternal(packageName, permName, overridePolicy, callingUid, userId, |
| reason, mDefaultPermissionCallback); |
| } |
| |
| @Override |
| public void revokePostNotificationPermissionWithoutKillForTest(String packageName, int userId) { |
| final int callingUid = Binder.getCallingUid(); |
| final boolean overridePolicy = |
| checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY) |
| == PackageManager.PERMISSION_GRANTED; |
| mContext.enforceCallingPermission( |
| android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL, ""); |
| revokeRuntimePermissionInternal(packageName, Manifest.permission.POST_NOTIFICATIONS, |
| overridePolicy, true, callingUid, userId, |
| SKIP_KILL_APP_REASON_NOTIFICATION_TEST, mDefaultPermissionCallback); |
| } |
| |
| private void revokeRuntimePermissionInternal(String packageName, String permName, |
| boolean overridePolicy, int callingUid, final int userId, |
| String reason, PermissionCallback callback) { |
| revokeRuntimePermissionInternal(packageName, permName, overridePolicy, false, callingUid, |
| userId, reason, callback); |
| } |
| |
| private void revokeRuntimePermissionInternal(String packageName, String permName, |
| boolean overridePolicy, boolean overrideKill, int callingUid, final int userId, |
| String reason, PermissionCallback callback) { |
| if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES |
| && PermissionManager.shouldTraceGrant(packageName, permName, userId)) { |
| Log.i(TAG, "System is revoking " + packageName + " " |
| + permName + " for user " + userId + " on behalf of uid " + callingUid |
| + " " + mPackageManagerInt.getNameForUid(callingUid), |
| new RuntimeException()); |
| } |
| if (!mUserManagerInt.exists(userId)) { |
| Log.e(TAG, "No such user:" + userId); |
| return; |
| } |
| |
| mContext.enforceCallingOrSelfPermission( |
| android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, |
| "revokeRuntimePermission"); |
| |
| enforceCrossUserPermission(callingUid, userId, |
| true, // requireFullPermission |
| true, // checkShell |
| "revokeRuntimePermission"); |
| |
| final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); |
| if (pkg == null) { |
| Log.e(TAG, "Unknown package: " + packageName); |
| return; |
| } |
| if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { |
| throw new IllegalArgumentException("Unknown package: " + packageName); |
| } |
| |
| final boolean isRolePermission; |
| synchronized (mLock) { |
| final Permission permission = mRegistry.getPermission(permName); |
| if (permission == null) { |
| throw new IllegalArgumentException("Unknown permission: " + permName); |
| } |
| isRolePermission = permission.isRole(); |
| } |
| final boolean mayRevokeRolePermission = isRolePermission |
| // Allow ourselves to revoke role permissions due to definition changes. |
| && (callingUid == Process.myUid() || mayManageRolePermission(callingUid)); |
| |
| final boolean isRuntimePermission; |
| synchronized (mLock) { |
| final Permission bp = mRegistry.getPermission(permName); |
| if (bp == null) { |
| throw new IllegalArgumentException("Unknown permission: " + permName); |
| } |
| |
| isRuntimePermission = bp.isRuntime(); |
| if (isRuntimePermission || bp.isDevelopment()) { |
| // Good. |
| } else if (bp.isRole()) { |
| if (!mayRevokeRolePermission) { |
| throw new SecurityException("Permission " + permName + " is managed by role"); |
| } |
| } else { |
| throw new SecurityException("Permission " + permName + " requested by " |
| + pkg.getPackageName() + " is not a changeable permission type"); |
| } |
| |
| final UidPermissionState uidState = getUidStateLocked(pkg, userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " |
| + userId); |
| return; |
| } |
| |
| if (!(uidState.hasPermissionState(permName) |
| || pkg.getRequestedPermissions().contains(permName))) { |
| throw new SecurityException("Package " + pkg.getPackageName() |
| + " has not requested permission " + permName); |
| } |
| |
| // If a permission review is required for legacy apps we represent |
| // their permissions as always granted runtime ones since we need |
| // to keep the review required permission flag per user while an |
| // install permission's state is shared across all users. |
| if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) { |
| return; |
| } |
| |
| final int flags = uidState.getPermissionFlags(permName); |
| // Only the system may revoke SYSTEM_FIXED permissions. |
| if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0 |
| && UserHandle.getCallingAppId() != Process.SYSTEM_UID) { |
| throw new SecurityException("Non-System UID cannot revoke system fixed permission " |
| + permName + " for package " + packageName); |
| } |
| if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) { |
| throw new SecurityException("Cannot revoke policy fixed permission " |
| + permName + " for package " + packageName); |
| } |
| |
| // Development permissions must be handled specially, since they are not |
| // normal runtime permissions. For now they apply to all users. |
| // TODO(zhanghai): We are breaking the behavior above by making all permission state |
| // per-user. It isn't documented behavior and relatively rarely used anyway. |
| if (!uidState.revokePermission(bp)) { |
| return; |
| } |
| } |
| |
| if (isRuntimePermission) { |
| logPermission(MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED, |
| permName, packageName); |
| } |
| |
| if (callback != null) { |
| if (isRuntimePermission) { |
| callback.onPermissionRevoked(UserHandle.getUid(userId, pkg.getUid()), userId, |
| reason, overrideKill); |
| } else { |
| mDefaultPermissionCallback.onInstallPermissionRevoked(); |
| } |
| } |
| |
| if (isRuntimePermission) { |
| notifyRuntimePermissionStateChanged(packageName, userId); |
| } |
| } |
| |
| private boolean mayManageRolePermission(int uid) { |
| final PackageManager packageManager = mContext.getPackageManager(); |
| final String[] packageNames = packageManager.getPackagesForUid(uid); |
| if (packageNames == null) { |
| return false; |
| } |
| final String permissionControllerPackageName = |
| packageManager.getPermissionControllerPackageName(); |
| return Arrays.asList(packageNames).contains(permissionControllerPackageName); |
| } |
| |
| /** |
| * Reverts user permission state changes (permissions and flags). |
| * |
| * @param pkg The package for which to reset. |
| * @param userId The device user for which to do a reset. |
| */ |
| private void resetRuntimePermissionsInternal(@NonNull AndroidPackage pkg, |
| @UserIdInt int userId) { |
| final String packageName = pkg.getPackageName(); |
| |
| // These are flags that can change base on user actions. |
| final int userSettableMask = FLAG_PERMISSION_USER_SET |
| | FLAG_PERMISSION_USER_FIXED |
| | FLAG_PERMISSION_REVOKED_COMPAT |
| | FLAG_PERMISSION_REVIEW_REQUIRED |
| | FLAG_PERMISSION_ONE_TIME |
| | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; |
| |
| final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED |
| | FLAG_PERMISSION_POLICY_FIXED; |
| |
| // Delay and combine non-async permission callbacks |
| final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions()); |
| final boolean[] permissionRemoved = new boolean[1]; |
| final ArraySet<Long> revokedPermissions = new ArraySet<>(); |
| final IntArray syncUpdatedUsers = new IntArray(permissionCount); |
| final IntArray asyncUpdatedUsers = new IntArray(permissionCount); |
| |
| PermissionCallback delayingPermCallback = new PermissionCallback() { |
| public void onGidsChanged(int appId, int userId) { |
| mDefaultPermissionCallback.onGidsChanged(appId, userId); |
| } |
| |
| public void onPermissionChanged() { |
| mDefaultPermissionCallback.onPermissionChanged(); |
| } |
| |
| public void onPermissionGranted(int uid, int userId) { |
| mDefaultPermissionCallback.onPermissionGranted(uid, userId); |
| } |
| |
| public void onInstallPermissionGranted() { |
| mDefaultPermissionCallback.onInstallPermissionGranted(); |
| } |
| |
| public void onPermissionRevoked(int uid, int userId, String reason) { |
| revokedPermissions.add(IntPair.of(uid, userId)); |
| |
| syncUpdatedUsers.add(userId); |
| } |
| |
| public void onInstallPermissionRevoked() { |
| mDefaultPermissionCallback.onInstallPermissionRevoked(); |
| } |
| |
| public void onPermissionUpdated(int[] updatedUserIds, boolean sync) { |
| for (int userId : updatedUserIds) { |
| if (sync) { |
| syncUpdatedUsers.add(userId); |
| asyncUpdatedUsers.remove(userId); |
| } else { |
| // Don't override sync=true by sync=false |
| if (syncUpdatedUsers.indexOf(userId) == -1) { |
| asyncUpdatedUsers.add(userId); |
| } |
| } |
| } |
| } |
| |
| public void onPermissionRemoved() { |
| permissionRemoved[0] = true; |
| } |
| |
| public void onInstallPermissionUpdated() { |
| mDefaultPermissionCallback.onInstallPermissionUpdated(); |
| } |
| |
| public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, |
| boolean sync, int uid) { |
| onPermissionUpdated(updatedUserIds, sync); |
| mOnPermissionChangeListeners.onPermissionsChanged(uid); |
| } |
| |
| public void onInstallPermissionUpdatedNotifyListener(int uid) { |
| mDefaultPermissionCallback.onInstallPermissionUpdatedNotifyListener(uid); |
| } |
| }; |
| |
| for (int i = 0; i < permissionCount; i++) { |
| final String permName = pkg.getRequestedPermissions().get(i); |
| |
| final boolean isRuntimePermission; |
| synchronized (mLock) { |
| final Permission permission = mRegistry.getPermission(permName); |
| if (permission == null) { |
| continue; |
| } |
| |
| if (permission.isRemoved()) { |
| continue; |
| } |
| isRuntimePermission = permission.isRuntime(); |
| } |
| |
| // If shared user we just reset the state to which only this app contributed. |
| final String[] pkgNames = mPackageManagerInt.getSharedUserPackagesForPackage( |
| pkg.getPackageName(), userId); |
| if (pkgNames.length > 0) { |
| boolean used = false; |
| for (String sharedPkgName : pkgNames) { |
| final AndroidPackage sharedPkg = |
| mPackageManagerInt.getPackage(sharedPkgName); |
| if (sharedPkg != null && !sharedPkg.getPackageName().equals(packageName) |
| && sharedPkg.getRequestedPermissions().contains(permName)) { |
| used = true; |
| break; |
| } |
| } |
| if (used) { |
| continue; |
| } |
| } |
| |
| final int oldFlags = |
| getPermissionFlagsInternal(packageName, permName, Process.SYSTEM_UID, userId); |
| |
| // Always clear the user settable flags. |
| // If permission review is enabled and this is a legacy app, mark the |
| // permission as requiring a review as this is the initial state. |
| final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId); |
| final int targetSdk = mPackageManagerInt.getUidTargetSdkVersion(uid); |
| final int flags = (targetSdk < Build.VERSION_CODES.M && isRuntimePermission) |
| ? FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKED_COMPAT |
| : 0; |
| |
| updatePermissionFlagsInternal( |
| packageName, permName, userSettableMask, flags, Process.SYSTEM_UID, userId, |
| false, delayingPermCallback); |
| |
| // Below is only runtime permission handling. |
| if (!isRuntimePermission) { |
| continue; |
| } |
| |
| // Never clobber system or policy. |
| if ((oldFlags & policyOrSystemFlags) != 0) { |
| continue; |
| } |
| |
| // If this permission was granted by default or role, make sure it is. |
| if ((oldFlags & FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0 |
| || (oldFlags & FLAG_PERMISSION_GRANTED_BY_ROLE) != 0) { |
| // PermissionPolicyService will handle the app op for runtime permissions later. |
| grantRuntimePermissionInternal(packageName, permName, false, |
| Process.SYSTEM_UID, userId, delayingPermCallback); |
| // In certain cases we should leave the state unchanged: |
| // -- If permission review is enabled the permissions for a legacy apps |
| // are represented as constantly granted runtime ones |
| // -- If the permission was split from a non-runtime permission |
| } else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0 |
| && !isPermissionSplitFromNonRuntime(permName, targetSdk)) { |
| // Otherwise, reset the permission. |
| revokeRuntimePermissionInternal(packageName, permName, false, Process.SYSTEM_UID, |
| userId, null, delayingPermCallback); |
| } |
| } |
| |
| // Execute delayed callbacks |
| if (permissionRemoved[0]) { |
| mDefaultPermissionCallback.onPermissionRemoved(); |
| } |
| |
| // Slight variation on the code in mPermissionCallback.onPermissionRevoked() as we cannot |
| // kill uid while holding mPackages-lock |
| if (!revokedPermissions.isEmpty()) { |
| int numRevokedPermissions = revokedPermissions.size(); |
| for (int i = 0; i < numRevokedPermissions; i++) { |
| int revocationUID = IntPair.first(revokedPermissions.valueAt(i)); |
| int revocationUserId = IntPair.second(revokedPermissions.valueAt(i)); |
| |
| mOnPermissionChangeListeners.onPermissionsChanged(revocationUID); |
| |
| // Kill app later as we are holding mPackages |
| mHandler.post(() -> killUid(UserHandle.getAppId(revocationUID), revocationUserId, |
| KILL_APP_REASON_PERMISSIONS_REVOKED)); |
| } |
| } |
| |
| mPackageManagerInt.writePermissionSettings(syncUpdatedUsers.toArray(), false); |
| mPackageManagerInt.writePermissionSettings(asyncUpdatedUsers.toArray(), true); |
| } |
| |
| /** |
| * Determine if the given permission should be treated as split from a |
| * non-runtime permission for an application targeting the given SDK level. |
| */ |
| private boolean isPermissionSplitFromNonRuntime(String permName, int targetSdk) { |
| final List<PermissionManager.SplitPermissionInfo> splitPerms = getSplitPermissionInfos(); |
| final int size = splitPerms.size(); |
| for (int i = 0; i < size; i++) { |
| final PermissionManager.SplitPermissionInfo splitPerm = splitPerms.get(i); |
| if (targetSdk < splitPerm.getTargetSdk() |
| && splitPerm.getNewPermissions().contains(permName)) { |
| synchronized (mLock) { |
| final Permission perm = |
| mRegistry.getPermission(splitPerm.getSplitPermission()); |
| return perm != null && !perm.isRuntime(); |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * This change makes it so that apps are told to show rationale for asking for background |
| * location access every time they request. |
| */ |
| @ChangeId |
| @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) |
| private static final long BACKGROUND_RATIONALE_CHANGE_ID = 147316723L; |
| |
| @Override |
| public boolean shouldShowRequestPermissionRationale(String packageName, String permName, |
| @UserIdInt int userId) { |
| final int callingUid = Binder.getCallingUid(); |
| if (UserHandle.getCallingUserId() != userId) { |
| mContext.enforceCallingPermission( |
| android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, |
| "canShowRequestPermissionRationale for user " + userId); |
| } |
| |
| final int uid = |
| mPackageManagerInt.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, userId); |
| if (UserHandle.getAppId(callingUid) != UserHandle.getAppId(uid)) { |
| return false; |
| } |
| |
| if (checkPermission(packageName, permName, userId) == PackageManager.PERMISSION_GRANTED) { |
| return false; |
| } |
| |
| final int flags; |
| |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| flags = getPermissionFlagsInternal(packageName, permName, callingUid, userId); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| |
| final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED |
| | PackageManager.FLAG_PERMISSION_POLICY_FIXED |
| | PackageManager.FLAG_PERMISSION_USER_FIXED; |
| |
| if ((flags & fixedFlags) != 0) { |
| return false; |
| } |
| |
| synchronized (mLock) { |
| final Permission permission = mRegistry.getPermission(permName); |
| if (permission == null) { |
| return false; |
| } |
| if (permission.isHardRestricted() |
| && (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) { |
| return false; |
| } |
| } |
| |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION) |
| && mPlatformCompat.isChangeEnabledByPackageName(BACKGROUND_RATIONALE_CHANGE_ID, |
| packageName, userId)) { |
| return true; |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "Unable to check if compatibility change is enabled.", e); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| |
| return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0; |
| } |
| |
| @Override |
| public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) { |
| if (UserHandle.getCallingUserId() != userId) { |
| mContext.enforceCallingPermission( |
| android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, |
| "isPermissionRevokedByPolicy for user " + userId); |
| } |
| |
| if (checkPermission(packageName, permName, userId) == PackageManager.PERMISSION_GRANTED) { |
| return false; |
| } |
| |
| final int callingUid = Binder.getCallingUid(); |
| if (mPackageManagerInt.filterAppAccess(packageName, callingUid, userId)) { |
| return false; |
| } |
| |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| final int flags = getPermissionFlagsInternal(packageName, permName, callingUid, userId); |
| return (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0; |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| /** |
| * Get the state of the runtime permissions as xml file. |
| * |
| * <p>Can not be called on main thread. |
| * |
| * @param userId The user ID the data should be extracted for |
| * |
| * @return The state as a xml file |
| */ |
| @Nullable |
| @Override |
| public byte[] backupRuntimePermissions(@UserIdInt int userId) { |
| Preconditions.checkArgumentNonNegative(userId, "userId"); |
| CompletableFuture<byte[]> backup = new CompletableFuture<>(); |
| mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId), |
| mContext.getMainExecutor(), backup::complete); |
| |
| try { |
| return backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); |
| } catch (InterruptedException | ExecutionException | TimeoutException e) { |
| Slog.e(TAG, "Cannot create permission backup for user " + userId, e); |
| return null; |
| } |
| } |
| |
| /** |
| * Restore a permission state previously backed up via {@link #backupRuntimePermissions}. |
| * |
| * <p>If not all state can be restored, the un-appliable state will be delayed and can be |
| * applied via {@link #restoreDelayedRuntimePermissions}. |
| * |
| * @param backup The state as an xml file |
| * @param userId The user ID the data should be restored for |
| */ |
| @Override |
| public void restoreRuntimePermissions(@NonNull byte[] backup, @UserIdInt int userId) { |
| Objects.requireNonNull(backup, "backup"); |
| Preconditions.checkArgumentNonNegative(userId, "userId"); |
| synchronized (mLock) { |
| mHasNoDelayedPermBackup.delete(userId); |
| } |
| mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(backup, |
| UserHandle.of(userId)); |
| } |
| |
| /** |
| * Try to apply permission backup that was previously not applied. |
| * |
| * <p>Can not be called on main thread. |
| * |
| * @param packageName The package that is newly installed |
| * @param userId The user ID the package is installed for |
| * |
| * @see #restoreRuntimePermissions |
| */ |
| @Override |
| public void restoreDelayedRuntimePermissions(@NonNull String packageName, |
| @UserIdInt int userId) { |
| Objects.requireNonNull(packageName, "packageName"); |
| Preconditions.checkArgumentNonNegative(userId, "userId"); |
| synchronized (mLock) { |
| if (mHasNoDelayedPermBackup.get(userId, false)) { |
| return; |
| } |
| } |
| mPermissionControllerManager.applyStagedRuntimePermissionBackup(packageName, |
| UserHandle.of(userId), mContext.getMainExecutor(), (hasMoreBackup) -> { |
| if (hasMoreBackup) { |
| return; |
| } |
| synchronized (mLock) { |
| mHasNoDelayedPermBackup.put(userId, true); |
| } |
| }); |
| } |
| |
| @Override |
| public void addOnRuntimePermissionStateChangedListener( |
| PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener listener) { |
| synchronized (mLock) { |
| mRuntimePermissionStateChangedListeners.add(listener); |
| } |
| } |
| |
| @Override |
| public void removeOnRuntimePermissionStateChangedListener( |
| PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener listener) { |
| synchronized (mLock) { |
| mRuntimePermissionStateChangedListeners.remove(listener); |
| } |
| } |
| |
| private void notifyRuntimePermissionStateChanged(@NonNull String packageName, |
| @UserIdInt int userId) { |
| FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( |
| PermissionManagerServiceImpl::doNotifyRuntimePermissionStateChanged, |
| PermissionManagerServiceImpl.this, packageName, userId)); |
| } |
| |
| private void doNotifyRuntimePermissionStateChanged(@NonNull String packageName, |
| @UserIdInt int userId) { |
| final ArrayList<PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener> |
| listeners; |
| synchronized (mLock) { |
| if (mRuntimePermissionStateChangedListeners.isEmpty()) { |
| return; |
| } |
| listeners = new ArrayList<>(mRuntimePermissionStateChangedListeners); |
| } |
| final int listenerCount = listeners.size(); |
| for (int i = 0; i < listenerCount; i++) { |
| listeners.get(i).onRuntimePermissionStateChanged(packageName, userId); |
| } |
| } |
| |
| /** |
| * If the app is updated, and has scoped storage permissions, then it is possible that the |
| * app updated in an attempt to get unscoped storage. If so, revoke all storage permissions. |
| * @param newPackage The new package that was installed |
| * @param oldPackage The old package that was updated |
| */ |
| private void revokeStoragePermissionsIfScopeExpandedInternal( |
| @NonNull AndroidPackage newPackage, |
| @NonNull AndroidPackage oldPackage) { |
| boolean downgradedSdk = oldPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q |
| && newPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q; |
| boolean upgradedSdk = oldPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q |
| && newPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q; |
| boolean newlyRequestsLegacy = !upgradedSdk && !oldPackage.isRequestLegacyExternalStorage() |
| && newPackage.isRequestLegacyExternalStorage(); |
| |
| if (!newlyRequestsLegacy && !downgradedSdk) { |
| return; |
| } |
| |
| final int callingUid = Binder.getCallingUid(); |
| for (int userId: getAllUserIds()) { |
| int numRequestedPermissions = newPackage.getRequestedPermissions().size(); |
| for (int i = 0; i < numRequestedPermissions; i++) { |
| PermissionInfo permInfo = getPermissionInfo( |
| newPackage.getRequestedPermissions().get(i), |
| newPackage.getPackageName(), 0); |
| if (permInfo == null) { |
| continue; |
| } |
| boolean isStorageOrMedia = STORAGE_PERMISSIONS.contains(permInfo.name) |
| || READ_MEDIA_AURAL_PERMISSIONS.contains(permInfo.name) |
| || READ_MEDIA_VISUAL_PERMISSIONS.contains(permInfo.name); |
| if (!isStorageOrMedia) { |
| continue; |
| } |
| |
| EventLog.writeEvent(0x534e4554, "171430330", newPackage.getUid(), |
| "Revoking permission " + permInfo.name + " from package " |
| + newPackage.getPackageName() + " as either the sdk downgraded " |
| + downgradedSdk + " or newly requested legacy full storage " |
| + newlyRequestsLegacy); |
| |
| try { |
| revokeRuntimePermissionInternal(newPackage.getPackageName(), permInfo.name, |
| false, callingUid, userId, null, mDefaultPermissionCallback); |
| } catch (IllegalStateException | SecurityException e) { |
| Log.e(TAG, "unable to revoke " + permInfo.name + " for " |
| + newPackage.getPackageName() + " user " + userId, e); |
| } |
| } |
| } |
| |
| } |
| |
| /** |
| * We might auto-grant permissions if any permission of the group is already granted. Hence if |
| * the group of a granted permission changes we need to revoke it to avoid having permissions of |
| * the new group auto-granted. |
| * |
| * @param newPackage The new package that was installed |
| * @param oldPackage The old package that was updated |
| */ |
| private void revokeRuntimePermissionsIfGroupChangedInternal(@NonNull AndroidPackage newPackage, |
| @NonNull AndroidPackage oldPackage) { |
| final int numOldPackagePermissions = ArrayUtils.size(oldPackage.getPermissions()); |
| final ArrayMap<String, String> oldPermissionNameToGroupName = |
| new ArrayMap<>(numOldPackagePermissions); |
| |
| for (int i = 0; i < numOldPackagePermissions; i++) { |
| final ParsedPermission permission = oldPackage.getPermissions().get(i); |
| |
| if (permission.getParsedPermissionGroup() != null) { |
| oldPermissionNameToGroupName.put(permission.getName(), |
| permission.getParsedPermissionGroup().getName()); |
| } |
| } |
| |
| final int callingUid = Binder.getCallingUid(); |
| final int numNewPackagePermissions = ArrayUtils.size(newPackage.getPermissions()); |
| for (int newPermissionNum = 0; newPermissionNum < numNewPackagePermissions; |
| newPermissionNum++) { |
| final ParsedPermission newPermission = |
| newPackage.getPermissions().get(newPermissionNum); |
| final int newProtection = ParsedPermissionUtils.getProtection(newPermission); |
| |
| if ((newProtection & PermissionInfo.PROTECTION_DANGEROUS) != 0) { |
| final String permissionName = newPermission.getName(); |
| final String newPermissionGroupName = |
| newPermission.getParsedPermissionGroup() == null |
| ? null : newPermission.getParsedPermissionGroup().getName(); |
| final String oldPermissionGroupName = oldPermissionNameToGroupName.get( |
| permissionName); |
| |
| if (newPermissionGroupName != null |
| && !newPermissionGroupName.equals(oldPermissionGroupName)) { |
| final int[] userIds = mUserManagerInt.getUserIds(); |
| mPackageManagerInt.forEachPackage(pkg -> { |
| final String packageName = pkg.getPackageName(); |
| for (final int userId : userIds) { |
| final int permissionState = |
| checkPermission(packageName, permissionName, userId); |
| if (permissionState == PackageManager.PERMISSION_GRANTED) { |
| EventLog.writeEvent(0x534e4554, "72710897", |
| newPackage.getUid(), |
| "Revoking permission " + permissionName + |
| " from package " + packageName + |
| " as the group changed from " + oldPermissionGroupName + |
| " to " + newPermissionGroupName); |
| |
| try { |
| revokeRuntimePermissionInternal(packageName, permissionName, |
| false, callingUid, userId, null, |
| mDefaultPermissionCallback); |
| } catch (IllegalArgumentException e) { |
| Slog.e(TAG, "Could not revoke " + permissionName + " from " |
| + packageName, e); |
| } |
| } |
| } |
| }); |
| } |
| } |
| } |
| } |
| |
| /** |
| * If permissions are upgraded to runtime, or their owner changes to the system, then any |
| * granted permissions must be revoked. |
| * |
| * @param permissionsToRevoke A list of permission names to revoke |
| */ |
| private void revokeRuntimePermissionsIfPermissionDefinitionChangedInternal( |
| @NonNull List<String> permissionsToRevoke) { |
| final int[] userIds = mUserManagerInt.getUserIds(); |
| final int numPermissions = permissionsToRevoke.size(); |
| final int callingUid = Binder.getCallingUid(); |
| |
| for (int permNum = 0; permNum < numPermissions; permNum++) { |
| final String permName = permissionsToRevoke.get(permNum); |
| final boolean isInternalPermission; |
| synchronized (mLock) { |
| final Permission bp = mRegistry.getPermission(permName); |
| if (bp == null || !(bp.isInternal() || bp.isRuntime())) { |
| continue; |
| } |
| isInternalPermission = bp.isInternal(); |
| } |
| mPackageManagerInt.forEachPackage(pkg -> { |
| final String packageName = pkg.getPackageName(); |
| final int appId = pkg.getUid(); |
| if (appId < Process.FIRST_APPLICATION_UID) { |
| // do not revoke from system apps |
| return; |
| } |
| for (final int userId : userIds) { |
| final int permissionState = checkPermission(packageName, permName, |
| userId); |
| final int flags = getPermissionFlags(packageName, permName, userId); |
| final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED |
| | FLAG_PERMISSION_POLICY_FIXED |
| | FLAG_PERMISSION_GRANTED_BY_DEFAULT |
| | FLAG_PERMISSION_GRANTED_BY_ROLE; |
| if (permissionState == PackageManager.PERMISSION_GRANTED |
| && (flags & flagMask) == 0) { |
| final int uid = UserHandle.getUid(userId, appId); |
| if (isInternalPermission) { |
| EventLog.writeEvent(0x534e4554, "195338390", uid, |
| "Revoking permission " + permName + " from package " |
| + packageName + " due to definition change"); |
| } else { |
| EventLog.writeEvent(0x534e4554, "154505240", uid, |
| "Revoking permission " + permName + " from package " |
| + packageName + " due to definition change"); |
| EventLog.writeEvent(0x534e4554, "168319670", uid, |
| "Revoking permission " + permName + " from package " |
| + packageName + " due to definition change"); |
| } |
| Slog.e(TAG, "Revoking permission " + permName + " from package " |
| + packageName + " due to definition change"); |
| try { |
| revokeRuntimePermissionInternal(packageName, permName, |
| false, callingUid, userId, null, mDefaultPermissionCallback); |
| } catch (Exception e) { |
| Slog.e(TAG, "Could not revoke " + permName + " from " |
| + packageName, e); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| private List<String> addAllPermissionsInternal(@NonNull AndroidPackage pkg) { |
| final int N = ArrayUtils.size(pkg.getPermissions()); |
| ArrayList<String> definitionChangedPermissions = new ArrayList<>(); |
| for (int i=0; i<N; i++) { |
| ParsedPermission p = pkg.getPermissions().get(i); |
| |
| // Assume by default that we did not install this permission into the system. |
| ComponentMutateUtils.setExactFlags(p, p.getFlags() & ~PermissionInfo.FLAG_INSTALLED); |
| |
| final PermissionInfo permissionInfo; |
| final Permission oldPermission; |
| synchronized (mLock) { |
| // Now that permission groups have a special meaning, we ignore permission |
| // groups for legacy apps to prevent unexpected behavior. In particular, |
| // permissions for one app being granted to someone just because they happen |
| // to be in a group defined by another app (before this had no implications). |
| if (pkg.getTargetSdkVersion() > Build.VERSION_CODES.LOLLIPOP_MR1) { |
| ComponentMutateUtils.setParsedPermissionGroup(p, |
| mRegistry.getPermissionGroup(p.getGroup())); |
| // Warn for a permission in an unknown group. |
| if (DEBUG_PERMISSIONS |
| && p.getGroup() != null && p.getParsedPermissionGroup() == null) { |
| Slog.i(TAG, "Permission " + p.getName() + " from package " |
| + p.getPackageName() + " in an unknown group " + p.getGroup()); |
| } |
| } |
| |
| permissionInfo = PackageInfoUtils.generatePermissionInfo(p, |
| PackageManager.GET_META_DATA); |
| oldPermission = p.isTree() ? mRegistry.getPermissionTree(p.getName()) |
| : mRegistry.getPermission(p.getName()); |
| } |
| // TODO(zhanghai): Maybe we should store whether a permission is owned by system inside |
| // itself. |
| final boolean isOverridingSystemPermission = Permission.isOverridingSystemPermission( |
| oldPermission, permissionInfo, mPackageManagerInt); |
| synchronized (mLock) { |
| final Permission permission = Permission.createOrUpdate(oldPermission, |
| permissionInfo, pkg, mRegistry.getPermissionTrees(), |
| isOverridingSystemPermission); |
| if (p.isTree()) { |
| mRegistry.addPermissionTree(permission); |
| } else { |
| mRegistry.addPermission(permission); |
| } |
| if (permission.isInstalled()) { |
| ComponentMutateUtils.setExactFlags(p, |
| p.getFlags() | PermissionInfo.FLAG_INSTALLED); |
| } |
| if (permission.isDefinitionChanged()) { |
| definitionChangedPermissions.add(p.getName()); |
| permission.setDefinitionChanged(false); |
| } |
| } |
| } |
| return definitionChangedPermissions; |
| } |
| |
| private void addAllPermissionGroupsInternal(@NonNull AndroidPackage pkg) { |
| synchronized (mLock) { |
| final int N = ArrayUtils.size(pkg.getPermissionGroups()); |
| StringBuilder r = null; |
| for (int i = 0; i < N; i++) { |
| final ParsedPermissionGroup pg = pkg.getPermissionGroups().get(i); |
| final ParsedPermissionGroup cur = mRegistry.getPermissionGroup(pg.getName()); |
| final String curPackageName = (cur == null) ? null : cur.getPackageName(); |
| final boolean isPackageUpdate = pg.getPackageName().equals(curPackageName); |
| if (cur == null || isPackageUpdate) { |
| mRegistry.addPermissionGroup(pg); |
| if (DEBUG_PACKAGE_SCANNING) { |
| if (r == null) { |
| r = new StringBuilder(256); |
| } else { |
| r.append(' '); |
| } |
| if (isPackageUpdate) { |
| r.append("UPD:"); |
| } |
| r.append(pg.getName()); |
| } |
| } else { |
| Slog.w(TAG, "Permission group " + pg.getName() + " from package " |
| + pg.getPackageName() + " ignored: original from " |
| + cur.getPackageName()); |
| if (DEBUG_PACKAGE_SCANNING) { |
| if (r == null) { |
| r = new StringBuilder(256); |
| } else { |
| r.append(' '); |
| } |
| r.append("DUP:"); |
| r.append(pg.getName()); |
| } |
| } |
| } |
| if (r != null && DEBUG_PACKAGE_SCANNING) { |
| Log.d(TAG, " Permission Groups: " + r); |
| } |
| } |
| } |
| |
| private void removeAllPermissionsInternal(@NonNull AndroidPackage pkg) { |
| synchronized (mLock) { |
| int n = ArrayUtils.size(pkg.getPermissions()); |
| StringBuilder r = null; |
| for (int i = 0; i < n; i++) { |
| ParsedPermission p = pkg.getPermissions().get(i); |
| Permission bp = mRegistry.getPermission(p.getName()); |
| if (bp == null) { |
| bp = mRegistry.getPermissionTree(p.getName()); |
| } |
| if (bp != null && bp.isPermission(p)) { |
| bp.setPermissionInfo(null); |
| if (DEBUG_REMOVE) { |
| if (r == null) { |
| r = new StringBuilder(256); |
| } else { |
| r.append(' '); |
| } |
| r.append(p.getName()); |
| } |
| } |
| if (ParsedPermissionUtils.isAppOp(p)) { |
| // TODO(zhanghai): Should we just remove the entry for this permission directly? |
| mRegistry.removeAppOpPermissionPackage(p.getName(), pkg.getPackageName()); |
| } |
| } |
| if (r != null) { |
| if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); |
| } |
| |
| n = pkg.getRequestedPermissions().size(); |
| r = null; |
| for (int i = 0; i < n; i++) { |
| final String permissionName = pkg.getRequestedPermissions().get(i); |
| final Permission permission = mRegistry.getPermission(permissionName); |
| if (permission != null && permission.isAppOp()) { |
| mRegistry.removeAppOpPermissionPackage(permissionName, |
| pkg.getPackageName()); |
| } |
| } |
| if (r != null) { |
| if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); |
| } |
| } |
| } |
| |
| @Override |
| public void onUserRemoved(@UserIdInt int userId) { |
| Preconditions.checkArgumentNonNegative(userId, "userId"); |
| synchronized (mLock) { |
| mState.removeUserState(userId); |
| } |
| } |
| |
| @NonNull |
| private Set<String> getGrantedPermissionsInternal(@NonNull String packageName, |
| @UserIdInt int userId) { |
| final PackageStateInternal ps = mPackageManagerInt.getPackageStateInternal(packageName); |
| if (ps == null) { |
| return Collections.emptySet(); |
| } |
| |
| synchronized (mLock) { |
| final UidPermissionState uidState = getUidStateLocked(ps, userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); |
| return Collections.emptySet(); |
| } |
| if (!ps.getUserStateOrDefault(userId).isInstantApp()) { |
| return uidState.getGrantedPermissions(); |
| } else { |
| // Install permission state is shared among all users, but instant app state is |
| // per-user, so we can only filter it here unless we make install permission state |
| // per-user as well. |
| final Set<String> instantPermissions = |
| new ArraySet<>(uidState.getGrantedPermissions()); |
| instantPermissions.removeIf(permissionName -> { |
| Permission permission = mRegistry.getPermission(permissionName); |
| if (permission == null) { |
| return true; |
| } |
| if (!permission.isInstant()) { |
| EventLog.writeEvent(0x534e4554, "140256621", UserHandle.getUid(userId, |
| ps.getAppId()), permissionName); |
| return true; |
| } |
| return false; |
| }); |
| return instantPermissions; |
| } |
| } |
| } |
| |
| @NonNull |
| private int[] getPermissionGidsInternal(@NonNull String permissionName, @UserIdInt int userId) { |
| synchronized (mLock) { |
| Permission permission = mRegistry.getPermission(permissionName); |
| if (permission == null) { |
| return EmptyArray.INT; |
| } |
| return permission.computeGids(userId); |
| } |
| } |
| |
| /** |
| * Restore the permission state for a package. |
| * |
| * <ul> |
| * <li>During boot the state gets restored from the disk</li> |
| * <li>During app update the state gets restored from the last version of the app</li> |
| * </ul> |
| * |
| * @param pkg the package the permissions belong to |
| * @param replace if the package is getting replaced (this might change the requested |
| * permissions of this package) |
| * @param packageOfInterest If this is the name of {@code pkg} add extra logging |
| * @param callback Result call back |
| * @param filterUserId If not {@link UserHandle.USER_ALL}, only restore the permission state for |
| * this particular user |
| */ |
| private void restorePermissionState(@NonNull AndroidPackage pkg, boolean replace, |
| @Nullable String packageOfInterest, @Nullable PermissionCallback callback, |
| @UserIdInt int filterUserId) { |
| // IMPORTANT: There are two types of permissions: install and runtime. |
| // Install time permissions are granted when the app is installed to |
| // all device users and users added in the future. Runtime permissions |
| // are granted at runtime explicitly to specific users. Normal and signature |
| // protected permissions are install time permissions. Dangerous permissions |
| // are install permissions if the app's target SDK is Lollipop MR1 or older, |
| // otherwise they are runtime permissions. This function does not manage |
| // runtime permissions except for the case an app targeting Lollipop MR1 |
| // being upgraded to target a newer SDK, in which case dangerous permissions |
| // are transformed from install time to runtime ones. |
| |
| final PackageStateInternal ps = |
| mPackageManagerInt.getPackageStateInternal(pkg.getPackageName()); |
| if (ps == null) { |
| return; |
| } |
| |
| final int[] userIds = filterUserId == UserHandle.USER_ALL ? getAllUserIds() |
| : new int[] { filterUserId }; |
| |
| boolean runtimePermissionsRevoked = false; |
| int[] updatedUserIds = EMPTY_INT_ARRAY; |
| |
| ArraySet<String> isPrivilegedPermissionAllowlisted = null; |
| ArraySet<String> shouldGrantSignaturePermission = null; |
| ArraySet<String> shouldGrantInternalPermission = null; |
| ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted = new ArraySet<>(); |
| final List<String> requestedPermissions = pkg.getRequestedPermissions(); |
| final int requestedPermissionsSize = requestedPermissions.size(); |
| for (int i = 0; i < requestedPermissionsSize; i++) { |
| final String permissionName = pkg.getRequestedPermissions().get(i); |
| |
| final Permission permission; |
| synchronized (mLock) { |
| permission = mRegistry.getPermission(permissionName); |
| } |
| if (permission == null) { |
| continue; |
| } |
| if (permission.isPrivileged() |
| && checkPrivilegedPermissionAllowlist(pkg, ps, permission)) { |
| if (isPrivilegedPermissionAllowlisted == null) { |
| isPrivilegedPermissionAllowlisted = new ArraySet<>(); |
| } |
| isPrivilegedPermissionAllowlisted.add(permissionName); |
| } |
| if (permission.isSignature() && (shouldGrantPermissionBySignature(pkg, permission) |
| || shouldGrantPermissionByProtectionFlags(pkg, ps, permission, |
| shouldGrantPrivilegedPermissionIfWasGranted))) { |
| if (shouldGrantSignaturePermission == null) { |
| shouldGrantSignaturePermission = new ArraySet<>(); |
| } |
| shouldGrantSignaturePermission.add(permissionName); |
| } |
| if (permission.isInternal() |
| && shouldGrantPermissionByProtectionFlags(pkg, ps, permission, |
| shouldGrantPrivilegedPermissionIfWasGranted)) { |
| if (shouldGrantInternalPermission == null) { |
| shouldGrantInternalPermission = new ArraySet<>(); |
| } |
| shouldGrantInternalPermission.add(permissionName); |
| } |
| } |
| |
| final SparseBooleanArray isPermissionPolicyInitialized = new SparseBooleanArray(); |
| if (mPermissionPolicyInternal != null) { |
| for (final int userId : userIds) { |
| if (mPermissionPolicyInternal.isInitialized(userId)) { |
| isPermissionPolicyInitialized.put(userId, true); |
| } |
| } |
| } |
| |
| Collection<String> uidRequestedPermissions; |
| Collection<String> uidImplicitPermissions; |
| int uidTargetSdkVersion; |
| if (!ps.hasSharedUser()) { |
| uidRequestedPermissions = pkg.getRequestedPermissions(); |
| uidImplicitPermissions = pkg.getImplicitPermissions(); |
| uidTargetSdkVersion = pkg.getTargetSdkVersion(); |
| } else { |
| uidRequestedPermissions = new ArraySet<>(); |
| uidImplicitPermissions = new ArraySet<>(); |
| uidTargetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; |
| final ArraySet<PackageStateInternal> packages = |
| mPackageManagerInt.getSharedUserPackages(ps.getSharedUserAppId()); |
| int packagesSize = packages.size(); |
| for (int i = 0; i < packagesSize; i++) { |
| AndroidPackageApi sharedUserPackage = |
| packages.valueAt(i).getAndroidPackage(); |
| if (sharedUserPackage == null) { |
| continue; |
| } |
| uidRequestedPermissions.addAll( |
| sharedUserPackage.getRequestedPermissions()); |
| uidImplicitPermissions.addAll( |
| sharedUserPackage.getImplicitPermissions()); |
| uidTargetSdkVersion = Math.min(uidTargetSdkVersion, |
| sharedUserPackage.getTargetSdkVersion()); |
| } |
| } |
| |
| synchronized (mLock) { |
| for (final int userId : userIds) { |
| final UserPermissionState userState = mState.getOrCreateUserState(userId); |
| final UidPermissionState uidState = userState.getOrCreateUidState(ps.getAppId()); |
| |
| if (uidState.isMissing()) { |
| for (String permissionName : uidRequestedPermissions) { |
| Permission permission = mRegistry.getPermission(permissionName); |
| if (permission == null) { |
| continue; |
| } |
| if (Objects.equals(permission.getPackageName(), PLATFORM_PACKAGE_NAME) |
| && permission.isRuntime() && !permission.isRemoved()) { |
| if (permission.isHardOrSoftRestricted() |
| || permission.isImmutablyRestricted()) { |
| uidState.updatePermissionFlags(permission, |
| FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, |
| FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT); |
| } |
| if (uidTargetSdkVersion < Build.VERSION_CODES.M) { |
| uidState.updatePermissionFlags(permission, |
| PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED |
| | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, |
| PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED |
| | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT); |
| uidState.grantPermission(permission); |
| } |
| } |
| } |
| |
| uidState.setMissing(false); |
| updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); |
| } |
| |
| UidPermissionState origState = uidState; |
| |
| boolean changedInstallPermission = false; |
| |
| if (replace) { |
| userState.setInstallPermissionsFixed(ps.getPackageName(), false); |
| if (!ps.hasSharedUser()) { |
| origState = new UidPermissionState(uidState); |
| uidState.reset(); |
| } else { |
| // We need to know only about runtime permission changes since the |
| // calling code always writes the install permissions state but |
| // the runtime ones are written only if changed. The only cases of |
| // changed runtime permissions here are promotion of an install to |
| // runtime and revocation of a runtime from a shared user. |
| if (revokeUnusedSharedUserPermissionsLocked(uidRequestedPermissions, |
| uidState)) { |
| updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); |
| runtimePermissionsRevoked = true; |
| } |
| } |
| } |
| |
| ArraySet<String> newImplicitPermissions = new ArraySet<>(); |
| final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")"; |
| |
| for (int i = 0; i < requestedPermissionsSize; i++) { |
| final String permName = requestedPermissions.get(i); |
| |
| final Permission bp = mRegistry.getPermission(permName); |
| final boolean appSupportsRuntimePermissions = |
| pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M; |
| String legacyActivityRecognitionPermission = null; |
| |
| if (DEBUG_INSTALL && bp != null) { |
| Log.i(TAG, "Package " + friendlyName |
| + " checking " + permName + ": " + bp); |
| } |
| |
| // TODO(zhanghai): I don't think we need to check source package setting if |
| // permission is present, because otherwise the permission should have been |
| // removed. |
| if (bp == null /*|| getSourcePackageSetting(bp) == null*/) { |
| if (packageOfInterest == null || packageOfInterest.equals( |
| pkg.getPackageName())) { |
| if (DEBUG_PERMISSIONS) { |
| Slog.i(TAG, "Unknown permission " + permName |
| + " in package " + friendlyName); |
| } |
| } |
| continue; |
| } |
| |
| // Cache newImplicitPermissions before modifing permissionsState as for the |
| // shared uids the original and new state are the same object |
| if (!origState.hasPermissionState(permName) |
| && (pkg.getImplicitPermissions().contains(permName) |
| || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) { |
| if (pkg.getImplicitPermissions().contains(permName)) { |
| // If permName is an implicit permission, try to auto-grant |
| newImplicitPermissions.add(permName); |
| |
| if (DEBUG_PERMISSIONS) { |
| Slog.i(TAG, permName + " is newly added for " + friendlyName); |
| } |
| } else { |
| // Special case for Activity Recognition permission. Even if AR |
| // permission is not an implicit permission we want to add it to the |
| // list (try to auto-grant it) if the app was installed on a device |
| // before AR permission was split, regardless of if the app now requests |
| // the new AR permission or has updated its target SDK and AR is no |
| // longer implicit to it. This is a compatibility workaround for apps |
| // when AR permission was split in Q. |
| // TODO(zhanghai): This calls into SystemConfig, which generally |
| // shouldn't cause deadlock, but maybe we should keep a cache of the |
| // split permission list and just eliminate the possibility. |
| final List<PermissionManager.SplitPermissionInfo> permissionList = |
| getSplitPermissionInfos(); |
| int numSplitPerms = permissionList.size(); |
| for (int splitPermNum = 0; splitPermNum < numSplitPerms; |
| splitPermNum++) { |
| PermissionManager.SplitPermissionInfo sp = permissionList.get( |
| splitPermNum); |
| String splitPermName = sp.getSplitPermission(); |
| if (sp.getNewPermissions().contains(permName) |
| && origState.isPermissionGranted(splitPermName)) { |
| legacyActivityRecognitionPermission = splitPermName; |
| newImplicitPermissions.add(permName); |
| |
| if (DEBUG_PERMISSIONS) { |
| Slog.i(TAG, permName + " is newly added for " |
| + friendlyName); |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| // TODO(b/140256621): The package instant app method has been removed |
| // as part of work in b/135203078, so this has been commented out in the |
| // meantime |
| // Limit ephemeral apps to ephemeral allowed permissions. |
| // if (/*pkg.isInstantApp()*/ false && !bp.isInstant()) { |
| // if (DEBUG_PERMISSIONS) { |
| // Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() |
| // + " for package " + pkg.getPackageName()); |
| // } |
| // continue; |
| // } |
| |
| if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) { |
| if (DEBUG_PERMISSIONS) { |
| Log.i(TAG, "Denying runtime-only permission " + bp.getName() |
| + " for package " + friendlyName); |
| } |
| continue; |
| } |
| |
| final String perm = bp.getName(); |
| |
| // Keep track of app op permissions. |
| if (bp.isAppOp()) { |
| mRegistry.addAppOpPermissionPackage(perm, pkg.getPackageName()); |
| } |
| |
| boolean shouldGrantNormalPermission = true; |
| if (bp.isNormal() && !origState.isPermissionGranted(perm)) { |
| // If this is an existing, non-system package, then |
| // we can't add any new permissions to it. Runtime |
| // permissions can be added any time - they are dynamic. |
| if (!ps.isSystem() && userState.areInstallPermissionsFixed( |
| ps.getPackageName())) { |
| // Except... if this is a permission that was added |
| // to the platform (note: need to only do this when |
| // updating the platform). |
| if (!isCompatPlatformPermissionForPackage(perm, pkg)) { |
| shouldGrantNormalPermission = false; |
| } |
| } |
| } |
| |
| if (DEBUG_PERMISSIONS) { |
| Slog.i(TAG, "Considering granting permission " + perm + " to package " |
| + pkg.getPackageName()); |
| } |
| |
| if ((bp.isNormal() && shouldGrantNormalPermission) |
| || (bp.isSignature() |
| && (!bp.isPrivileged() || CollectionUtils.contains( |
| isPrivilegedPermissionAllowlisted, permName)) |
| && (CollectionUtils.contains(shouldGrantSignaturePermission, |
| permName) |
| || (((bp.isPrivileged() && CollectionUtils.contains( |
| shouldGrantPrivilegedPermissionIfWasGranted, |
| permName)) || bp.isDevelopment() || bp.isRole()) |
| && origState.isPermissionGranted(permName)))) |
| || (bp.isInternal() |
| && (!bp.isPrivileged() || CollectionUtils.contains( |
| isPrivilegedPermissionAllowlisted, permName)) |
| && (CollectionUtils.contains(shouldGrantInternalPermission, |
| permName) |
| || (((bp.isPrivileged() && CollectionUtils.contains( |
| shouldGrantPrivilegedPermissionIfWasGranted, |
| permName)) || bp.isDevelopment() || bp.isRole()) |
| && origState.isPermissionGranted(permName))))) { |
| // Grant an install permission. |
| if (uidState.grantPermission(bp)) { |
| changedInstallPermission = true; |
| } |
| } else if (bp.isRuntime()) { |
| boolean hardRestricted = bp.isHardRestricted(); |
| boolean softRestricted = bp.isSoftRestricted(); |
| |
| // If permission policy is not ready we don't deal with restricted |
| // permissions as the policy may allowlist some permissions. Once |
| // the policy is initialized we would re-evaluate permissions. |
| final boolean permissionPolicyInitialized = |
| isPermissionPolicyInitialized.get(userId); |
| |
| PermissionState origPermState = origState.getPermissionState(perm); |
| int flags = origPermState != null ? origPermState.getFlags() : 0; |
| |
| boolean wasChanged = false; |
| |
| boolean restrictionExempt = |
| (origState.getPermissionFlags(bp.getName()) |
| & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; |
| boolean restrictionApplied = (origState.getPermissionFlags( |
| bp.getName()) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; |
| |
| if (appSupportsRuntimePermissions) { |
| // If hard restricted we don't allow holding it |
| if (permissionPolicyInitialized && hardRestricted) { |
| if (!restrictionExempt) { |
| if (origPermState != null && origPermState.isGranted() |
| && uidState.revokePermission(bp)) { |
| wasChanged = true; |
| } |
| if (!restrictionApplied) { |
| flags |= FLAG_PERMISSION_APPLY_RESTRICTION; |
| wasChanged = true; |
| } |
| } |
| // If soft restricted we allow holding in a restricted form |
| } else if (permissionPolicyInitialized && softRestricted) { |
| // Regardless if granted set the restriction flag as it |
| // may affect app treatment based on this permission. |
| if (!restrictionExempt && !restrictionApplied) { |
| flags |= FLAG_PERMISSION_APPLY_RESTRICTION; |
| wasChanged = true; |
| } |
| } |
| |
| // Remove review flag as it is not necessary anymore |
| if (!NOTIFICATION_PERMISSIONS.contains(perm)) { |
| if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { |
| flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED; |
| wasChanged = true; |
| } |
| } |
| |
| if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0 |
| && !isPermissionSplitFromNonRuntime(permName, |
| pkg.getTargetSdkVersion())) { |
| flags &= ~FLAG_PERMISSION_REVOKED_COMPAT; |
| wasChanged = true; |
| // Hard restricted permissions cannot be held. |
| } else if (!permissionPolicyInitialized |
| || (!hardRestricted || restrictionExempt)) { |
| if ((origPermState != null && origPermState.isGranted()) |
| || legacyActivityRecognitionPermission != null) { |
| if (!uidState.grantPermission(bp)) { |
| wasChanged = true; |
| } |
| } |
| } |
| if (mIsLeanback && NOTIFICATION_PERMISSIONS.contains(permName)) { |
| uidState.grantPermission(bp); |
| if (origPermState == null || !origPermState.isGranted()) { |
| if (uidState.grantPermission(bp)) { |
| wasChanged = true; |
| } |
| } |
| } |
| } else { |
| if (origPermState == null) { |
| // New permission |
| if (PLATFORM_PACKAGE_NAME.equals( |
| bp.getPackageName())) { |
| if (!bp.isRemoved()) { |
| flags |= FLAG_PERMISSION_REVIEW_REQUIRED |
| | FLAG_PERMISSION_REVOKED_COMPAT; |
| wasChanged = true; |
| } |
| } |
| } |
| |
| if (!uidState.isPermissionGranted(bp.getName()) |
| && uidState.grantPermission(bp)) { |
| wasChanged = true; |
| } |
| |
| // If legacy app always grant the permission but if restricted |
| // and not exempt take a note a restriction should be applied. |
| if (permissionPolicyInitialized |
| && (hardRestricted || softRestricted) |
| && !restrictionExempt && !restrictionApplied) { |
| flags |= FLAG_PERMISSION_APPLY_RESTRICTION; |
| wasChanged = true; |
| } |
| } |
| |
| // If unrestricted or restriction exempt, don't apply restriction. |
| if (permissionPolicyInitialized) { |
| if (!(hardRestricted || softRestricted) || restrictionExempt) { |
| if (restrictionApplied) { |
| flags &= ~FLAG_PERMISSION_APPLY_RESTRICTION; |
| // Dropping restriction on a legacy app implies a review |
| if (!appSupportsRuntimePermissions) { |
| flags |= FLAG_PERMISSION_REVIEW_REQUIRED; |
| } |
| wasChanged = true; |
| } |
| } |
| } |
| |
| if (wasChanged) { |
| updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); |
| } |
| |
| uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, |
| flags); |
| } else { |
| if (DEBUG_PERMISSIONS) { |
| boolean wasGranted = uidState.isPermissionGranted(bp.getName()); |
| if (wasGranted || bp.isAppOp()) { |
| Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting") |
| + " permission " + perm |
| + " from package " + friendlyName |
| + " (protectionLevel=" + bp.getProtectionLevel() |
| + " flags=0x" |
| + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, |
| ps)) |
| + ")"); |
| } |
| } |
| if (uidState.removePermissionState(bp.getName())) { |
| changedInstallPermission = true; |
| } |
| } |
| } |
| |
| if ((changedInstallPermission || replace) |
| && !userState.areInstallPermissionsFixed(ps.getPackageName()) |
| && !ps.isSystem() || ps.getTransientState().isUpdatedSystemApp()) { |
| // This is the first that we have heard about this package, so the |
| // permissions we have now selected are fixed until explicitly |
| // changed. |
| userState.setInstallPermissionsFixed(ps.getPackageName(), true); |
| } |
| |
| updatedUserIds = revokePermissionsNoLongerImplicitLocked(uidState, |
| pkg.getPackageName(), uidImplicitPermissions, uidTargetSdkVersion, userId, |
| updatedUserIds); |
| updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origState, |
| uidState, pkg, newImplicitPermissions, userId, updatedUserIds); |
| } |
| } |
| |
| updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, userIds, |
| updatedUserIds); |
| |
| // TODO: Kill UIDs whose GIDs or runtime permissions changed. This might be more important |
| // for shared users. |
| // Persist the runtime permissions state for users with changes. If permissions |
| // were revoked because no app in the shared user declares them we have to |
| // write synchronously to avoid losing runtime permissions state. |
| if (callback != null) { |
| callback.onPermissionUpdated(updatedUserIds, runtimePermissionsRevoked); |
| } |
| |
| for (int userId : updatedUserIds) { |
| notifyRuntimePermissionStateChanged(pkg.getPackageName(), userId); |
| } |
| } |
| |
| /** |
| * Returns all relevant user ids. This list include the current set of created user ids as well |
| * as pre-created user ids. |
| * @return user ids for created users and pre-created users |
| */ |
| private int[] getAllUserIds() { |
| return UserManagerService.getInstance().getUserIdsIncludingPreCreated(); |
| } |
| |
| /** |
| * Revoke permissions that are not implicit anymore and that have |
| * {@link PackageManager#FLAG_PERMISSION_REVOKE_WHEN_REQUESTED} set. |
| * |
| * @param ps The state of the permissions of the package |
| * @param packageName The name of the package |
| * @param uidImplicitPermissions The implicit permissions of all packages in the UID |
| * @param uidTargetSdkVersion The lowest target SDK version of all packages in the UID |
| * @param userIds All user IDs in the system, must be passed in because this method is locked |
| * @param updatedUserIds a list of user ids that needs to be amended if the permission state |
| * for a user is changed. |
| * |
| * @return The updated value of the {@code updatedUserIds} parameter |
| */ |
| @NonNull |
| @GuardedBy("mLock") |
| private int[] revokePermissionsNoLongerImplicitLocked(@NonNull UidPermissionState ps, |
| @NonNull String packageName, @NonNull Collection<String> uidImplicitPermissions, |
| int uidTargetSdkVersion, int userId, @NonNull int[] updatedUserIds) { |
| boolean supportsRuntimePermissions = uidTargetSdkVersion >= Build.VERSION_CODES.M; |
| |
| for (String permission : ps.getGrantedPermissions()) { |
| if (!uidImplicitPermissions.contains(permission)) { |
| Permission bp = mRegistry.getPermission(permission); |
| if (bp != null && bp.isRuntime()) { |
| int flags = ps.getPermissionFlags(permission); |
| if ((flags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) { |
| int flagsToRemove = FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; |
| |
| // We're willing to preserve an implicit "Nearby devices" |
| // permission grant if this app was already able to interact |
| // with nearby devices via background location access |
| boolean preserveGrant = false; |
| if (ArrayUtils.contains(NEARBY_DEVICES_PERMISSIONS, permission) |
| && ps.isPermissionGranted( |
| android.Manifest.permission.ACCESS_BACKGROUND_LOCATION) |
| && (ps.getPermissionFlags( |
| android.Manifest.permission.ACCESS_BACKGROUND_LOCATION) |
| & (FLAG_PERMISSION_REVOKE_WHEN_REQUESTED |
| | FLAG_PERMISSION_REVOKED_COMPAT)) == 0) { |
| preserveGrant = true; |
| } |
| |
| if ((flags & BLOCKING_PERMISSION_FLAGS) == 0 |
| && supportsRuntimePermissions |
| && !preserveGrant) { |
| if (ps.revokePermission(bp)) { |
| if (DEBUG_PERMISSIONS) { |
| Slog.i(TAG, "Revoking runtime permission " |
| + permission + " for " + packageName |
| + " as it is now requested"); |
| } |
| } |
| |
| flagsToRemove |= USER_PERMISSION_FLAGS; |
| } |
| |
| ps.updatePermissionFlags(bp, flagsToRemove, 0); |
| updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); |
| } |
| } |
| } |
| } |
| |
| return updatedUserIds; |
| } |
| |
| /** |
| * {@code newPerm} is newly added; Inherit the state from {@code sourcePerms}. |
| * |
| * <p>A single new permission can be split off from several source permissions. In this case |
| * the most leniant state is inherited. |
| * |
| * <p>Warning: This does not handle foreground / background permissions |
| * |
| * @param sourcePerms The permissions to inherit from |
| * @param newPerm The permission to inherit to |
| * @param ps The permission state of the package |
| * @param pkg The package requesting the permissions |
| */ |
| @GuardedBy("mLock") |
| private void inheritPermissionStateToNewImplicitPermissionLocked( |
| @NonNull ArraySet<String> sourcePerms, @NonNull String newPerm, |
| @NonNull UidPermissionState ps, @NonNull AndroidPackage pkg) { |
| String pkgName = pkg.getPackageName(); |
| boolean isGranted = false; |
| int flags = 0; |
| |
| int numSourcePerm = sourcePerms.size(); |
| for (int i = 0; i < numSourcePerm; i++) { |
| String sourcePerm = sourcePerms.valueAt(i); |
| if (ps.isPermissionGranted(sourcePerm)) { |
| if (!isGranted) { |
| flags = 0; |
| } |
| |
| isGranted = true; |
| flags |= ps.getPermissionFlags(sourcePerm); |
| } else { |
| if (!isGranted) { |
| flags |= ps.getPermissionFlags(sourcePerm); |
| } |
| } |
| } |
| |
| if (isGranted) { |
| if (DEBUG_PERMISSIONS) { |
| Slog.i(TAG, newPerm + " inherits runtime perm grant from " + sourcePerms |
| + " for " + pkgName); |
| } |
| |
| ps.grantPermission(mRegistry.getPermission(newPerm)); |
| } |
| |
| // Add permission flags |
| ps.updatePermissionFlags(mRegistry.getPermission(newPerm), flags, flags); |
| } |
| |
| /** |
| * When the app has requested legacy storage we might need to update |
| * {@link android.app.AppOpsManager#OP_LEGACY_STORAGE}. Hence force an update in |
| * {@link com.android.server.policy.PermissionPolicyService#synchronizePackagePermissionsAndAppOpsForUser(Context, String, int)} |
| * |
| * @param pkg The package for which the permissions are updated |
| * @param replace If the app is being replaced |
| * @param userIds All user IDs in the system, must be passed in because this method is locked |
| * @param updatedUserIds The ids of the users that already changed. |
| * |
| * @return The ids of the users that are changed |
| */ |
| private @NonNull int[] checkIfLegacyStorageOpsNeedToBeUpdated(@NonNull AndroidPackage pkg, |
| boolean replace, @NonNull int[] userIds, @NonNull int[] updatedUserIds) { |
| if (replace && pkg.isRequestLegacyExternalStorage() && ( |
| pkg.getRequestedPermissions().contains(READ_EXTERNAL_STORAGE) |
| || pkg.getRequestedPermissions().contains(WRITE_EXTERNAL_STORAGE))) { |
| return userIds.clone(); |
| } |
| |
| return updatedUserIds; |
| } |
| |
| /** |
| * Set the state of a implicit permission that is seen for the first time. |
| * |
| * @param origPs The permission state of the package before the split |
| * @param ps The new permission state |
| * @param pkg The package the permission belongs to |
| * @param userId The user ID |
| * @param updatedUserIds List of users for which the permission state has already been changed |
| * |
| * @return List of users for which the permission state has been changed |
| */ |
| @NonNull |
| @GuardedBy("mLock") |
| private int[] setInitialGrantForNewImplicitPermissionsLocked( |
| @NonNull UidPermissionState origPs, @NonNull UidPermissionState ps, |
| @NonNull AndroidPackage pkg, @NonNull ArraySet<String> newImplicitPermissions, |
| @UserIdInt int userId, @NonNull int[] updatedUserIds) { |
| String pkgName = pkg.getPackageName(); |
| ArrayMap<String, ArraySet<String>> newToSplitPerms = new ArrayMap<>(); |
| |
| final List<PermissionManager.SplitPermissionInfo> permissionList = |
| getSplitPermissionInfos(); |
| int numSplitPerms = permissionList.size(); |
| for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) { |
| PermissionManager.SplitPermissionInfo spi = permissionList.get(splitPermNum); |
| |
| List<String> newPerms = spi.getNewPermissions(); |
| int numNewPerms = newPerms.size(); |
| for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) { |
| String newPerm = newPerms.get(newPermNum); |
| |
| ArraySet<String> splitPerms = newToSplitPerms.get(newPerm); |
| if (splitPerms == null) { |
| splitPerms = new ArraySet<>(); |
| newToSplitPerms.put(newPerm, splitPerms); |
| } |
| |
| splitPerms.add(spi.getSplitPermission()); |
| } |
| } |
| |
| int numNewImplicitPerms = newImplicitPermissions.size(); |
| for (int newImplicitPermNum = 0; newImplicitPermNum < numNewImplicitPerms; |
| newImplicitPermNum++) { |
| String newPerm = newImplicitPermissions.valueAt(newImplicitPermNum); |
| ArraySet<String> sourcePerms = newToSplitPerms.get(newPerm); |
| |
| if (sourcePerms != null) { |
| Permission bp = mRegistry.getPermission(newPerm); |
| if (bp == null) { |
| throw new IllegalStateException("Unknown new permission in split permission: " |
| + newPerm); |
| } |
| if (bp.isRuntime()) { |
| |
| if (!(newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION) |
| || READ_MEDIA_AURAL_PERMISSIONS.contains(newPerm) |
| || READ_MEDIA_VISUAL_PERMISSIONS.contains(newPerm))) { |
| ps.updatePermissionFlags(bp, |
| FLAG_PERMISSION_REVOKE_WHEN_REQUESTED, |
| FLAG_PERMISSION_REVOKE_WHEN_REQUESTED); |
| } |
| updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); |
| |
| if (!origPs.hasPermissionState(sourcePerms)) { |
| boolean inheritsFromInstallPerm = false; |
| for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size(); |
| sourcePermNum++) { |
| final String sourcePerm = sourcePerms.valueAt(sourcePermNum); |
| Permission sourceBp = mRegistry.getPermission(sourcePerm); |
| if (sourceBp == null) { |
| throw new IllegalStateException("Unknown source permission in split" |
| + " permission: " + sourcePerm); |
| } |
| if (!sourceBp.isRuntime()) { |
| inheritsFromInstallPerm = true; |
| break; |
| } |
| } |
| |
| if (!inheritsFromInstallPerm) { |
| // Both permissions are new so nothing to inherit. |
| if (DEBUG_PERMISSIONS) { |
| Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms |
| + " for " + pkgName + " as split permission is also new"); |
| } |
| continue; |
| } |
| } |
| |
| // Inherit from new install or existing runtime permissions |
| inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms, newPerm, ps, |
| pkg); |
| } |
| } |
| } |
| |
| return updatedUserIds; |
| } |
| |
| @NonNull |
| @Override |
| public List<SplitPermissionInfoParcelable> getSplitPermissions() { |
| return PermissionManager.splitPermissionInfoListToParcelableList(getSplitPermissionInfos()); |
| } |
| |
| @NonNull |
| private List<PermissionManager.SplitPermissionInfo> getSplitPermissionInfos() { |
| return SystemConfig.getInstance().getSplitPermissions(); |
| } |
| |
| private static boolean isCompatPlatformPermissionForPackage(String perm, AndroidPackage pkg) { |
| boolean allowed = false; |
| for (int i = 0, size = CompatibilityPermissionInfo.COMPAT_PERMS.length; i < size; i++) { |
| final CompatibilityPermissionInfo info = CompatibilityPermissionInfo.COMPAT_PERMS[i]; |
| if (info.getName().equals(perm) |
| && pkg.getTargetSdkVersion() < info.getSdkVersion()) { |
| allowed = true; |
| Log.i(TAG, "Auto-granting " + perm + " to old pkg " |
| + pkg.getPackageName()); |
| break; |
| } |
| } |
| return allowed; |
| } |
| |
| private boolean checkPrivilegedPermissionAllowlist(@NonNull AndroidPackage pkg, |
| @NonNull PackageStateInternal packageSetting, @NonNull Permission permission) { |
| if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) { |
| return true; |
| } |
| final String packageName = pkg.getPackageName(); |
| if (Objects.equals(packageName, PLATFORM_PACKAGE_NAME)) { |
| return true; |
| } |
| if (!pkg.isPrivileged()) { |
| return true; |
| } |
| if (!mPrivilegedPermissionAllowlistSourcePackageNames |
| .contains(permission.getPackageName())) { |
| return true; |
| } |
| final String permissionName = permission.getName(); |
| final ApexManager apexManager = ApexManager.getInstance(); |
| final String containingApexPackageName = |
| apexManager.getActiveApexPackageNameContainingPackage(packageName); |
| if (isInSystemConfigPrivAppPermissions(pkg, permissionName, |
| containingApexPackageName)) { |
| return true; |
| } |
| if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName, |
| containingApexPackageName)) { |
| return false; |
| } |
| // Updated system apps do not need to be allowlisted |
| if (packageSetting.getTransientState().isUpdatedSystemApp()) { |
| // Let shouldGrantPermissionByProtectionFlags() decide whether the privileged permission |
| // can be granted, because an updated system app may be in a shared UID, and in case a |
| // new privileged permission is requested by the updated system app but not the factory |
| // app, although this app and permission combination isn't in the allowlist and can't |
| // get the permission this way, other apps in the shared UID may still get it. A proper |
| // fix for this would be to perform the reconciliation by UID, but for now let's keep |
| // the old workaround working, which is to keep granted privileged permissions still |
| // granted. |
| return true; |
| } |
| // Only enforce the allowlist on boot |
| if (!mSystemReady) { |
| final boolean isInUpdatedApex = containingApexPackageName != null |
| && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName, |
| MATCH_ACTIVE_PACKAGE)); |
| // Apps that are in updated apexs' do not need to be allowlisted |
| if (!isInUpdatedApex) { |
| Slog.w(TAG, "Privileged permission " + permissionName + " for package " |
| + packageName + " (" + pkg.getPath() |
| + ") not in privapp-permissions allowlist"); |
| if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { |
| synchronized (mLock) { |
| if (mPrivappPermissionsViolations == null) { |
| mPrivappPermissionsViolations = new ArraySet<>(); |
| } |
| mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): " |
| + permissionName); |
| } |
| } |
| } |
| } |
| return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE; |
| } |
| |
| private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg, |
| @NonNull String permission, String containingApexPackageName) { |
| final SystemConfig systemConfig = SystemConfig.getInstance(); |
| final Set<String> permissions; |
| if (pkg.isVendor()) { |
| permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName()); |
| } else if (pkg.isProduct()) { |
| permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName()); |
| } else if (pkg.isSystemExt()) { |
| permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName()); |
| } else if (containingApexPackageName != null) { |
| final ApexManager apexManager = ApexManager.getInstance(); |
| final String apexName = apexManager.getApexModuleNameForPackageName( |
| containingApexPackageName); |
| final Set<String> privAppPermissions = systemConfig.getPrivAppPermissions( |
| pkg.getPackageName()); |
| final Set<String> apexPermissions = systemConfig.getApexPrivAppPermissions( |
| apexName, pkg.getPackageName()); |
| if (privAppPermissions != null) { |
| // TODO(andreionea): Remove check as soon as all apk-in-apex |
| // permission allowlists are migrated. |
| Slog.w(TAG, "Package " + pkg.getPackageName() + " is an APK in APEX," |
| + " but has permission allowlist on the system image. Please bundle the" |
| + " allowlist in the " + containingApexPackageName + " APEX instead."); |
| if (apexPermissions != null) { |
| permissions = new ArraySet<>(privAppPermissions); |
| permissions.addAll(apexPermissions); |
| } else { |
| permissions = privAppPermissions; |
| } |
| } else { |
| permissions = apexPermissions; |
| } |
| } else { |
| permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName()); |
| } |
| return CollectionUtils.contains(permissions, permission); |
| } |
| |
| private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg, |
| @NonNull String permission, String containingApexPackageName) { |
| final SystemConfig systemConfig = SystemConfig.getInstance(); |
| final Set<String> permissions; |
| if (pkg.isVendor()) { |
| permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName()); |
| } else if (pkg.isProduct()) { |
| permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName()); |
| } else if (pkg.isSystemExt()) { |
| permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName()); |
| } else if (containingApexPackageName != null) { |
| permissions = systemConfig.getApexPrivAppDenyPermissions(containingApexPackageName, |
| pkg.getPackageName()); |
| } else { |
| permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName()); |
| } |
| return CollectionUtils.contains(permissions, permission); |
| } |
| |
| private boolean shouldGrantPermissionBySignature(@NonNull AndroidPackage pkg, |
| @NonNull Permission bp) { |
| // expect single system package |
| String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames( |
| KnownPackages.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM)); |
| final AndroidPackage systemPackage = |
| mPackageManagerInt.getPackage(systemPackageName); |
| // check if the package is allow to use this signature permission. A package is allowed to |
| // use a signature permission if: |
| // - it has the same set of signing certificates as the source package |
| // - or its signing certificate was rotated from the source package's certificate |
| // - or its signing certificate is a previous signing certificate of the defining |
| // package, and the defining package still trusts the old certificate for permissions |
| // - or it shares a common signing certificate in its lineage with the defining package, |
| // and the defining package still trusts the old certificate for permissions |
| // - or it shares the above relationships with the system package |
| final SigningDetails sourceSigningDetails = |
| getSourcePackageSigningDetails(bp); |
| return sourceSigningDetails.hasCommonSignerWithCapability( |
| pkg.getSigningDetails(), |
| SigningDetails.CertCapabilities.PERMISSION) |
| || pkg.getSigningDetails().hasAncestorOrSelf(systemPackage.getSigningDetails()) |
| || systemPackage.getSigningDetails().checkCapability( |
| pkg.getSigningDetails(), |
| SigningDetails.CertCapabilities.PERMISSION); |
| } |
| |
| private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg, |
| @NonNull PackageStateInternal pkgSetting, @NonNull Permission bp, |
| @NonNull ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted) { |
| boolean allowed = false; |
| final boolean isPrivilegedPermission = bp.isPrivileged(); |
| final boolean isOemPermission = bp.isOem(); |
| if (!allowed && (isPrivilegedPermission || isOemPermission) && pkg.isSystem()) { |
| final String permissionName = bp.getName(); |
| // For updated system applications, a privileged/oem permission |
| // is granted only if it had been defined by the original application. |
| if (pkgSetting.getTransientState().isUpdatedSystemApp()) { |
| final PackageStateInternal disabledPs = mPackageManagerInt |
| .getDisabledSystemPackage(pkg.getPackageName()); |
| final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.getPkg(); |
| if (disabledPkg != null |
| && ((isPrivilegedPermission && disabledPkg.isPrivileged()) |
| || (isOemPermission && canGrantOemPermission(disabledPkg, |
| permissionName)))) { |
| if (disabledPkg.getRequestedPermissions().contains(permissionName)) { |
| allowed = true; |
| } else { |
| // If the original was granted this permission, we take |
| // that grant decision as read and propagate it to the |
| // update. |
| shouldGrantPrivilegedPermissionIfWasGranted.add(permissionName); |
| } |
| } |
| } else { |
| allowed = (isPrivilegedPermission && pkg.isPrivileged()) |
| || (isOemPermission && canGrantOemPermission(pkg, permissionName)); |
| } |
| // In any case, don't grant a privileged permission to privileged vendor apps, if |
| // the permission's protectionLevel does not have the extra 'vendorPrivileged' |
| // flag. |
| if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged() && pkg.isVendor()) { |
| Slog.w(TAG, "Permission " + permissionName |
| + " cannot be granted to privileged vendor apk " + pkg.getPackageName() |
| + " because it isn't a 'vendorPrivileged' permission."); |
| allowed = false; |
| } |
| } |
| if (!allowed && bp.isPre23() && pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) { |
| // If this was a previously normal/dangerous permission that got moved |
| // to a system permission as part of the runtime permission redesign, then |
| // we still want to blindly grant it to old apps. |
| allowed = true; |
| } |
| // TODO (moltmann): The installer now shares the platforms signature. Hence it does not |
| // need a separate flag anymore. Hence we need to check which |
| // permissions are needed by the permission controller |
| if (!allowed && bp.isInstaller() |
| && (ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( |
| KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM), |
| pkg.getPackageName()) || ArrayUtils.contains( |
| mPackageManagerInt.getKnownPackageNames( |
| KnownPackages.PACKAGE_PERMISSION_CONTROLLER, |
| UserHandle.USER_SYSTEM), pkg.getPackageName()))) { |
| // If this permission is to be granted to the system installer and |
| // this app is an installer, then it gets the permission. |
| allowed = true; |
| } |
| if (!allowed && bp.isVerifier() |
| && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( |
| KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM), |
| pkg.getPackageName())) { |
| // If this permission is to be granted to the system verifier and |
| // this app is a verifier, then it gets the permission. |
| allowed = true; |
| } |
| if (!allowed && bp.isPreInstalled() |
| && pkg.isSystem()) { |
| // Any pre-installed system app is allowed to get this permission. |
| allowed = true; |
| } |
| if (!allowed && bp.isKnownSigner()) { |
| // If the permission is to be granted to a known signer then check if any of this |
| // app's signing certificates are in the trusted certificate digest Set. |
| allowed = pkg.getSigningDetails().hasAncestorOrSelfWithDigest(bp.getKnownCerts()); |
| } |
| // Deferred to be checked under permission data lock inside restorePermissionState(). |
| //if (!allowed && bp.isDevelopment()) { |
| // // For development permissions, a development permission |
| // // is granted only if it was already granted. |
| // allowed = origPermissions.isPermissionGranted(permissionName); |
| //} |
| if (!allowed && bp.isSetup() |
| && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( |
| KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM), |
| pkg.getPackageName())) { |
| // If this permission is to be granted to the system setup wizard and |
| // this app is a setup wizard, then it gets the permission. |
| allowed = true; |
| } |
| if (!allowed && bp.isSystemTextClassifier() |
| && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( |
| KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, |
| UserHandle.USER_SYSTEM), pkg.getPackageName())) { |
| // Special permissions for the system default text classifier. |
| allowed = true; |
| } |
| if (!allowed && bp.isConfigurator() |
| && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( |
| KnownPackages.PACKAGE_CONFIGURATOR, |
| UserHandle.USER_SYSTEM), pkg.getPackageName())) { |
| // Special permissions for the device configurator. |
| allowed = true; |
| } |
| if (!allowed && bp.isIncidentReportApprover() |
| && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( |
| KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, |
| UserHandle.USER_SYSTEM), pkg.getPackageName())) { |
| // If this permission is to be granted to the incident report approver and |
| // this app is the incident report approver, then it gets the permission. |
| allowed = true; |
| } |
| if (!allowed && bp.isAppPredictor() |
| && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( |
| KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM), |
| pkg.getPackageName())) { |
| // Special permissions for the system app predictor. |
| allowed = true; |
| } |
| if (!allowed && bp.isCompanion() |
| && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( |
| KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM), |
| pkg.getPackageName())) { |
| // Special permissions for the system companion device manager. |
| allowed = true; |
| } |
| if (!allowed && bp.isRetailDemo() |
| && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( |
| KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM), |
| pkg.getPackageName()) && isProfileOwner(pkg.getUid())) { |
| // Special permission granted only to the OEM specified retail demo app |
| allowed = true; |
| } |
| if (!allowed && bp.isRecents() |
| && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( |
| KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM), |
| pkg.getPackageName())) { |
| // Special permission for the recents app. |
| allowed = true; |
| } |
| return allowed; |
| } |
| |
| @NonNull |
| private SigningDetails getSourcePackageSigningDetails( |
| @NonNull Permission bp) { |
| final PackageStateInternal ps = getSourcePackageSetting(bp); |
| if (ps == null) { |
| return SigningDetails.UNKNOWN; |
| } |
| return ps.getSigningDetails(); |
| } |
| |
| @Nullable |
| private PackageStateInternal getSourcePackageSetting(@NonNull Permission bp) { |
| final String sourcePackageName = bp.getPackageName(); |
| return mPackageManagerInt.getPackageStateInternal(sourcePackageName); |
| } |
| |
| private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) { |
| if (!pkg.isOem()) { |
| return false; |
| } |
| // all oem permissions must explicitly be granted or denied |
| final Boolean granted = |
| SystemConfig.getInstance().getOemPermissions(pkg.getPackageName()).get(permission); |
| if (granted == null) { |
| throw new IllegalStateException("OEM permission " + permission |
| + " requested by package " + pkg.getPackageName() |
| + " must be explicitly declared granted or not"); |
| } |
| return Boolean.TRUE == granted; |
| } |
| |
| private static boolean isProfileOwner(int uid) { |
| DevicePolicyManagerInternal dpmInternal = |
| LocalServices.getService(DevicePolicyManagerInternal.class); |
| //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode. |
| if (dpmInternal != null) { |
| return dpmInternal.isActiveProfileOwner(uid) || dpmInternal.isActiveDeviceOwner(uid); |
| } |
| return false; |
| } |
| |
| private boolean isPermissionsReviewRequiredInternal(@NonNull String packageName, |
| @UserIdInt int userId) { |
| final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); |
| if (pkg == null) { |
| return false; |
| } |
| |
| // Permission review applies only to apps not supporting the new permission model. |
| if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) { |
| return false; |
| } |
| |
| // Legacy apps have the permission and get user consent on launch. |
| synchronized (mLock) { |
| final UidPermissionState uidState = getUidStateLocked(pkg, userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " |
| + userId); |
| return false; |
| } |
| return uidState.isPermissionsReviewRequired(); |
| } |
| } |
| |
| private void grantRequestedRuntimePermissionsInternal(@NonNull AndroidPackage pkg, |
| @Nullable List<String> permissions, int userId) { |
| final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED |
| | PackageManager.FLAG_PERMISSION_POLICY_FIXED; |
| |
| final int compatFlags = PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED |
| | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; |
| |
| final boolean supportsRuntimePermissions = pkg.getTargetSdkVersion() |
| >= Build.VERSION_CODES.M; |
| |
| final boolean instantApp = mPackageManagerInt.isInstantApp(pkg.getPackageName(), userId); |
| |
| final int myUid = Process.myUid(); |
| |
| for (String permission : pkg.getRequestedPermissions()) { |
| final boolean shouldGrantPermission; |
| synchronized (mLock) { |
| final Permission bp = mRegistry.getPermission(permission); |
| shouldGrantPermission = bp != null && (bp.isRuntime() || bp.isDevelopment()) |
| && (!instantApp || bp.isInstant()) |
| && (supportsRuntimePermissions || !bp.isRuntimeOnly()) |
| && (permissions == null || permissions.contains(permission)); |
| } |
| if (shouldGrantPermission) { |
| final int flags = getPermissionFlagsInternal(pkg.getPackageName(), permission, |
| myUid, userId); |
| if (supportsRuntimePermissions) { |
| // Installer cannot change immutable permissions. |
| if ((flags & immutableFlags) == 0) { |
| grantRuntimePermissionInternal(pkg.getPackageName(), permission, false, |
| myUid, userId, mDefaultPermissionCallback); |
| } |
| } else { |
| // In permission review mode we clear the review flag and the revoked compat |
| // flag when we are asked to install the app with all permissions granted. |
| if ((flags & compatFlags) != 0) { |
| updatePermissionFlagsInternal(pkg.getPackageName(), permission, compatFlags, |
| 0, myUid, userId, false, mDefaultPermissionCallback); |
| } |
| } |
| } |
| } |
| } |
| |
| private void setAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg, |
| @Nullable List<String> permissions, |
| @PackageManager.PermissionWhitelistFlags int allowlistFlags, |
| @UserIdInt int userId) { |
| ArraySet<String> oldGrantedRestrictedPermissions = null; |
| boolean updatePermissions = false; |
| final int permissionCount = pkg.getRequestedPermissions().size(); |
| final int myUid = Process.myUid(); |
| |
| for (int j = 0; j < permissionCount; j++) { |
| final String permissionName = pkg.getRequestedPermissions().get(j); |
| |
| final boolean isGranted; |
| synchronized (mLock) { |
| final Permission bp = mRegistry.getPermission(permissionName); |
| if (bp == null || !bp.isHardOrSoftRestricted()) { |
| continue; |
| } |
| |
| final UidPermissionState uidState = getUidStateLocked(pkg, userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() |
| + " and user " + userId); |
| continue; |
| } |
| isGranted = uidState.isPermissionGranted(permissionName); |
| } |
| |
| if (isGranted) { |
| if (oldGrantedRestrictedPermissions == null) { |
| oldGrantedRestrictedPermissions = new ArraySet<>(); |
| } |
| oldGrantedRestrictedPermissions.add(permissionName); |
| } |
| |
| final int oldFlags = getPermissionFlagsInternal(pkg.getPackageName(), permissionName, |
| myUid, userId); |
| |
| int newFlags = oldFlags; |
| int mask = 0; |
| int allowlistFlagsCopy = allowlistFlags; |
| while (allowlistFlagsCopy != 0) { |
| final int flag = 1 << Integer.numberOfTrailingZeros(allowlistFlagsCopy); |
| allowlistFlagsCopy &= ~flag; |
| switch (flag) { |
| case FLAG_PERMISSION_WHITELIST_SYSTEM: { |
| mask |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; |
| if (permissions != null && permissions.contains(permissionName)) { |
| newFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; |
| } else { |
| newFlags &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; |
| } |
| } |
| break; |
| case FLAG_PERMISSION_WHITELIST_UPGRADE: { |
| mask |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; |
| if (permissions != null && permissions.contains(permissionName)) { |
| newFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; |
| } else { |
| newFlags &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; |
| } |
| } |
| break; |
| case FLAG_PERMISSION_WHITELIST_INSTALLER: { |
| mask |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; |
| if (permissions != null && permissions.contains(permissionName)) { |
| newFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; |
| } else { |
| newFlags &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; |
| } |
| } |
| break; |
| } |
| } |
| |
| if (oldFlags == newFlags) { |
| continue; |
| } |
| |
| updatePermissions = true; |
| |
| final boolean wasAllowlisted = (oldFlags |
| & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0; |
| final boolean isAllowlisted = (newFlags |
| & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0; |
| |
| // If the permission is policy fixed as granted but it is no longer |
| // on any of the allowlists we need to clear the policy fixed flag |
| // as allowlisting trumps policy i.e. policy cannot grant a non |
| // grantable permission. |
| if ((oldFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) { |
| if (!isAllowlisted && isGranted) { |
| mask |= PackageManager.FLAG_PERMISSION_POLICY_FIXED; |
| newFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED; |
| } |
| } |
| |
| // If we are allowlisting an app that does not support runtime permissions |
| // we need to make sure it goes through the permission review UI at launch. |
| if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M |
| && !wasAllowlisted && isAllowlisted) { |
| mask |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; |
| newFlags |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; |
| } |
| |
| updatePermissionFlagsInternal(pkg.getPackageName(), permissionName, mask, newFlags, |
| myUid, userId, false, null /*callback*/); |
| } |
| |
| if (updatePermissions) { |
| // Update permission of this app to take into account the new allowlist state. |
| restorePermissionState(pkg, false, pkg.getPackageName(), mDefaultPermissionCallback, |
| userId); |
| |
| // If this resulted in losing a permission we need to kill the app. |
| if (oldGrantedRestrictedPermissions == null) { |
| return; |
| } |
| |
| final int oldGrantedCount = oldGrantedRestrictedPermissions.size(); |
| for (int j = 0; j < oldGrantedCount; j++) { |
| final String permissionName = oldGrantedRestrictedPermissions.valueAt(j); |
| // Sometimes we create a new permission state instance during update. |
| final boolean isGranted; |
| synchronized (mLock) { |
| final UidPermissionState uidState = getUidStateLocked(pkg, userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() |
| + " and user " + userId); |
| continue; |
| } |
| isGranted = uidState.isPermissionGranted(permissionName); |
| } |
| if (!isGranted) { |
| mDefaultPermissionCallback.onPermissionRevoked(pkg.getUid(), userId, null); |
| break; |
| } |
| } |
| } |
| } |
| |
| private void revokeSharedUserPermissionsForLeavingPackageInternal( |
| @Nullable AndroidPackage pkg, int appId, @NonNull List<AndroidPackage> sharedUserPkgs, |
| @UserIdInt int userId) { |
| if (pkg == null) { |
| Slog.i(TAG, "Trying to update info for null package. Just ignoring"); |
| return; |
| } |
| |
| // No shared user packages |
| if (sharedUserPkgs.isEmpty()) { |
| return; |
| } |
| |
| PackageStateInternal disabledPs = mPackageManagerInt.getDisabledSystemPackage( |
| pkg.getPackageName()); |
| boolean isShadowingSystemPkg = disabledPs != null && disabledPs.getAppId() == pkg.getUid(); |
| |
| boolean shouldKillUid = false; |
| // Update permissions |
| for (String eachPerm : pkg.getRequestedPermissions()) { |
| // Check if another package in the shared user needs the permission. |
| boolean used = false; |
| for (AndroidPackage sharedUserpkg : sharedUserPkgs) { |
| if (sharedUserpkg != null |
| && !sharedUserpkg.getPackageName().equals(pkg.getPackageName()) |
| && sharedUserpkg.getRequestedPermissions().contains(eachPerm)) { |
| used = true; |
| break; |
| } |
| } |
| if (used) { |
| continue; |
| } |
| |
| // If the package is shadowing a disabled system package, |
| // do not drop permissions that the shadowed package requests. |
| if (isShadowingSystemPkg |
| && disabledPs.getPkg().getRequestedPermissions().contains(eachPerm)) { |
| continue; |
| } |
| |
| synchronized (mLock) { |
| UidPermissionState uidState = getUidStateLocked(appId, userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() |
| + " and user " + userId); |
| continue; |
| } |
| |
| Permission bp = mRegistry.getPermission(eachPerm); |
| if (bp == null) { |
| continue; |
| } |
| |
| // TODO(zhanghai): Why are we only killing the UID when GIDs changed, instead of any |
| // permission change? |
| if (uidState.removePermissionState(bp.getName()) && bp.hasGids()) { |
| shouldKillUid = true; |
| } |
| } |
| } |
| |
| // If gids changed, kill all affected packages. |
| if (shouldKillUid) { |
| mHandler.post(() -> { |
| // This has to happen with no lock held. |
| killUid(appId, UserHandle.USER_ALL, KILL_APP_REASON_GIDS_CHANGED); |
| }); |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private boolean revokeUnusedSharedUserPermissionsLocked( |
| @NonNull Collection<String> uidRequestedPermissions, |
| @NonNull UidPermissionState uidState) { |
| boolean runtimePermissionChanged = false; |
| |
| // Prune permissions |
| final List<PermissionState> permissionStates = uidState.getPermissionStates(); |
| final int permissionStatesSize = permissionStates.size(); |
| for (int i = permissionStatesSize - 1; i >= 0; i--) { |
| PermissionState permissionState = permissionStates.get(i); |
| if (!uidRequestedPermissions.contains(permissionState.getName())) { |
| Permission bp = mRegistry.getPermission(permissionState.getName()); |
| if (bp != null) { |
| if (uidState.removePermissionState(bp.getName()) && bp.isRuntime()) { |
| runtimePermissionChanged = true; |
| } |
| } |
| } |
| } |
| |
| return runtimePermissionChanged; |
| } |
| |
| /** |
| * Update permissions when a package changed. |
| * |
| * <p><ol> |
| * <li>Reconsider the ownership of permission</li> |
| * <li>Update the state (grant, flags) of the permissions</li> |
| * </ol> |
| * |
| * @param packageName The package that is updated |
| * @param pkg The package that is updated, or {@code null} if package is deleted |
| */ |
| private void updatePermissions(@NonNull String packageName, @Nullable AndroidPackage pkg) { |
| // If the package is being deleted, update the permissions of all the apps |
| final int flags = |
| (pkg == null ? UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG |
| : UPDATE_PERMISSIONS_REPLACE_PKG); |
| updatePermissions( |
| packageName, pkg, getVolumeUuidForPackage(pkg), flags, mDefaultPermissionCallback); |
| } |
| |
| /** |
| * Update all permissions for all apps. |
| * |
| * <p><ol> |
| * <li>Reconsider the ownership of permission</li> |
| * <li>Update the state (grant, flags) of the permissions</li> |
| * </ol> |
| * |
| * @param volumeUuid The volume UUID of the packages to be updated |
| * @param fingerprintChanged whether the current build fingerprint is different from what it was |
| * when this volume was last mounted |
| */ |
| private void updateAllPermissions(@NonNull String volumeUuid, boolean fingerprintChanged) { |
| PackageManager.corkPackageInfoCache(); // Prevent invalidation storm |
| try { |
| final int flags = UPDATE_PERMISSIONS_ALL | |
| (fingerprintChanged |
| ? UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL |
| : 0); |
| updatePermissions(null, null, volumeUuid, flags, mDefaultPermissionCallback); |
| } finally { |
| PackageManager.uncorkPackageInfoCache(); |
| } |
| } |
| |
| /** |
| * Update all packages on the volume, <u>beside</u> the changing package. If the changing |
| * package is set too, all packages are updated. |
| */ |
| private static final int UPDATE_PERMISSIONS_ALL = 1 << 0; |
| /** The changing package is replaced. Requires the changing package to be set */ |
| private static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1 << 1; |
| /** |
| * Schedule all packages <u>beside</u> the changing package for replacement. Requires |
| * UPDATE_PERMISSIONS_ALL to be set |
| */ |
| private static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1 << 2; |
| |
| @IntDef(flag = true, prefix = { "UPDATE_PERMISSIONS_" }, value = { |
| UPDATE_PERMISSIONS_ALL, UPDATE_PERMISSIONS_REPLACE_PKG, |
| UPDATE_PERMISSIONS_REPLACE_ALL }) |
| @Retention(RetentionPolicy.SOURCE) |
| private @interface UpdatePermissionFlags {} |
| |
| /** |
| * Update permissions when packages changed. |
| * |
| * <p><ol> |
| * <li>Reconsider the ownership of permission</li> |
| * <li>Update the state (grant, flags) of the permissions</li> |
| * </ol> |
| * |
| * <p>Meaning of combination of package parameters: |
| * <table> |
| * <tr><th></th><th>changingPkgName != null</th><th>changingPkgName == null</th></tr> |
| * <tr><th>changingPkg != null</th><td>package is updated</td><td>invalid</td></tr> |
| * <tr><th>changingPkg == null</th><td>package is deleted</td><td>all packages are |
| * updated</td></tr> |
| * </table> |
| * |
| * @param changingPkgName The package that is updated, or {@code null} if all packages should be |
| * updated |
| * @param changingPkg The package that is updated, or {@code null} if all packages should be |
| * updated or package is deleted |
| * @param replaceVolumeUuid The volume of the packages to be updated are on, {@code null} for |
| * all volumes |
| * @param flags Control permission for which apps should be updated |
| * @param callback Callback to call after permission changes |
| */ |
| private void updatePermissions(final @Nullable String changingPkgName, |
| final @Nullable AndroidPackage changingPkg, |
| final @Nullable String replaceVolumeUuid, |
| @UpdatePermissionFlags int flags, |
| final @Nullable PermissionCallback callback) { |
| // TODO: Most of the methods exposing BasePermission internals [source package name, |
| // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't |
| // have package settings, we should make note of it elsewhere [map between |
| // source package name and BasePermission] and cycle through that here. Then we |
| // define a single method on BasePermission that takes a PackageSetting, changing |
| // package name and a package. |
| // NOTE: With this approach, we also don't need to tree trees differently than |
| // normal permissions. Today, we need two separate loops because these BasePermission |
| // objects are stored separately. |
| // Make sure there are no dangling permission trees. |
| boolean permissionTreesSourcePackageChanged = updatePermissionTreeSourcePackage( |
| changingPkgName, changingPkg); |
| // Make sure all dynamic permissions have been assigned to a package, |
| // and make sure there are no dangling permissions. |
| boolean permissionSourcePackageChanged = updatePermissionSourcePackage(changingPkgName, |
| callback); |
| |
| if (permissionTreesSourcePackageChanged | permissionSourcePackageChanged) { |
| // Permission ownership has changed. This e.g. changes which packages can get signature |
| // permissions |
| Slog.i(TAG, "Permission ownership changed. Updating all permissions."); |
| flags |= UPDATE_PERMISSIONS_ALL; |
| } |
| |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "restorePermissionState"); |
| // Now update the permissions for all packages. |
| if ((flags & UPDATE_PERMISSIONS_ALL) != 0) { |
| final boolean replaceAll = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0); |
| mPackageManagerInt.forEachPackage((AndroidPackage pkg) -> { |
| if (pkg == changingPkg) { |
| return; |
| } |
| // Only replace for packages on requested volume |
| final String volumeUuid = getVolumeUuidForPackage(pkg); |
| final boolean replace = replaceAll && Objects.equals(replaceVolumeUuid, volumeUuid); |
| restorePermissionState(pkg, replace, changingPkgName, callback, |
| UserHandle.USER_ALL); |
| }); |
| } |
| |
| if (changingPkg != null) { |
| // Only replace for packages on requested volume |
| final String volumeUuid = getVolumeUuidForPackage(changingPkg); |
| final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0) |
| && Objects.equals(replaceVolumeUuid, volumeUuid); |
| restorePermissionState(changingPkg, replace, changingPkgName, callback, |
| UserHandle.USER_ALL); |
| } |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| |
| /** |
| * Update which app declares a permission. |
| * |
| * @param packageName The package that is updated, or {@code null} if all packages should be |
| * updated |
| * |
| * @return {@code true} if a permission source package might have changed |
| */ |
| private boolean updatePermissionSourcePackage(@Nullable String packageName, |
| final @Nullable PermissionCallback callback) { |
| // Always need update if packageName is null |
| if (packageName == null) { |
| return true; |
| } |
| |
| boolean changed = false; |
| Set<Permission> needsUpdate = null; |
| synchronized (mLock) { |
| for (final Permission bp : mRegistry.getPermissions()) { |
| if (bp.isDynamic()) { |
| bp.updateDynamicPermission(mRegistry.getPermissionTrees()); |
| } |
| if (!packageName.equals(bp.getPackageName())) { |
| // Not checking sourcePackageSetting because it can be null when |
| // the permission source package is the target package and the target package is |
| // being uninstalled, |
| continue; |
| } |
| // The target package is the source of the current permission |
| // Set to changed for either install or uninstall |
| changed = true; |
| if (needsUpdate == null) { |
| needsUpdate = new ArraySet<>(); |
| } |
| needsUpdate.add(bp); |
| } |
| } |
| if (needsUpdate != null) { |
| final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); |
| for (final Permission bp : needsUpdate) { |
| // If the target package is being uninstalled, we need to revoke this permission |
| // From all other packages |
| if (pkg == null || !hasPermission(pkg, bp.getName())) { |
| if (!isPermissionDeclaredByDisabledSystemPkg(bp)) { |
| Slog.i(TAG, "Removing permission " + bp.getName() |
| + " that used to be declared by " + bp.getPackageName()); |
| if (bp.isRuntime()) { |
| final int[] userIds = mUserManagerInt.getUserIds(); |
| final int numUserIds = userIds.length; |
| for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) { |
| final int userId = userIds[userIdNum]; |
| mPackageManagerInt.forEachPackage((AndroidPackage p) -> |
| revokePermissionFromPackageForUser(p.getPackageName(), |
| bp.getName(), true, userId, callback)); |
| } |
| } else { |
| mPackageManagerInt.forEachPackage(p -> { |
| final int[] userIds = mUserManagerInt.getUserIds(); |
| synchronized (mLock) { |
| for (final int userId : userIds) { |
| final UidPermissionState uidState = getUidStateLocked(p, |
| userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for " |
| + p.getPackageName() + " and user " + userId); |
| continue; |
| } |
| uidState.removePermissionState(bp.getName()); |
| } |
| } |
| }); |
| } |
| } |
| synchronized (mLock) { |
| mRegistry.removePermission(bp.getName()); |
| } |
| continue; |
| } |
| final AndroidPackage sourcePkg = |
| mPackageManagerInt.getPackage(bp.getPackageName()); |
| final PackageStateInternal sourcePs = |
| mPackageManagerInt.getPackageStateInternal(bp.getPackageName()); |
| synchronized (mLock) { |
| if (sourcePkg != null && sourcePs != null) { |
| continue; |
| } |
| Slog.w(TAG, "Removing dangling permission: " + bp.getName() |
| + " from package " + bp.getPackageName()); |
| mRegistry.removePermission(bp.getName()); |
| } |
| } |
| } |
| return changed; |
| } |
| |
| private boolean isPermissionDeclaredByDisabledSystemPkg(@NonNull Permission permission) { |
| final PackageStateInternal disabledSourcePs = mPackageManagerInt.getDisabledSystemPackage( |
| permission.getPackageName()); |
| if (disabledSourcePs != null && disabledSourcePs.getPkg() != null) { |
| final String permissionName = permission.getName(); |
| final List<ParsedPermission> sourcePerms = disabledSourcePs.getPkg().getPermissions(); |
| for (ParsedPermission sourcePerm : sourcePerms) { |
| if (TextUtils.equals(permissionName, sourcePerm.getName()) |
| && permission.getProtectionLevel() == sourcePerm.getProtectionLevel()) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Revoke a runtime permission from a package for a given user ID. |
| */ |
| private void revokePermissionFromPackageForUser(@NonNull String pName, |
| @NonNull String permissionName, boolean overridePolicy, int userId, |
| @Nullable PermissionCallback callback) { |
| final ApplicationInfo appInfo = |
| mPackageManagerInt.getApplicationInfo(pName, 0, |
| Process.SYSTEM_UID, UserHandle.USER_SYSTEM); |
| if (appInfo != null |
| && appInfo.targetSdkVersion < Build.VERSION_CODES.M) { |
| return; |
| } |
| |
| if (checkPermission(pName, permissionName, userId) |
| == PackageManager.PERMISSION_GRANTED) { |
| try { |
| revokeRuntimePermissionInternal( |
| pName, permissionName, |
| overridePolicy, |
| Process.SYSTEM_UID, |
| userId, |
| null, callback); |
| } catch (IllegalArgumentException e) { |
| Slog.e(TAG, |
| "Failed to revoke " |
| + permissionName |
| + " from " |
| + pName, |
| e); |
| } |
| } |
| } |
| |
| /** |
| * Update which app owns a permission trees. |
| * |
| * <p>Possible parameter combinations |
| * <table> |
| * <tr><th></th><th>packageName != null</th><th>packageName == null</th></tr> |
| * <tr><th>pkg != null</th><td>package is updated</td><td>invalid</td></tr> |
| * <tr><th>pkg == null</th><td>package is deleted</td><td>all packages are updated</td></tr> |
| * </table> |
| * |
| * @param packageName The package that is updated, or {@code null} if all packages should be |
| * updated |
| * @param pkg The package that is updated, or {@code null} if all packages should be updated or |
| * package is deleted |
| * |
| * @return {@code true} if a permission tree ownership might have changed |
| */ |
| private boolean updatePermissionTreeSourcePackage(@Nullable String packageName, |
| @Nullable AndroidPackage pkg) { |
| // Always need update if packageName is null |
| if (packageName == null) { |
| return true; |
| } |
| boolean changed = false; |
| |
| Set<Permission> needsUpdate = null; |
| synchronized (mLock) { |
| final Iterator<Permission> it = mRegistry.getPermissionTrees().iterator(); |
| while (it.hasNext()) { |
| final Permission bp = it.next(); |
| if (!packageName.equals(bp.getPackageName())) { |
| // Not checking sourcePackageSetting because it can be null when |
| // the permission source package is the target package and the target package is |
| // being uninstalled, |
| continue; |
| } |
| // The target package is the source of the current permission tree |
| // Set to changed for either install or uninstall |
| changed = true; |
| if (pkg == null || !hasPermission(pkg, bp.getName())) { |
| Slog.i(TAG, "Removing permission tree " + bp.getName() |
| + " that used to be declared by " + bp.getPackageName()); |
| it.remove(); |
| } |
| if (needsUpdate == null) { |
| needsUpdate = new ArraySet<>(); |
| } |
| needsUpdate.add(bp); |
| } |
| } |
| if (needsUpdate != null) { |
| for (final Permission bp : needsUpdate) { |
| final AndroidPackage sourcePkg = |
| mPackageManagerInt.getPackage(bp.getPackageName()); |
| final PackageStateInternal sourcePs = |
| mPackageManagerInt.getPackageStateInternal(bp.getPackageName()); |
| synchronized (mLock) { |
| if (sourcePkg != null && sourcePs != null) { |
| continue; |
| } |
| Slog.w(TAG, "Removing dangling permission tree: " + bp.getName() |
| + " from package " + bp.getPackageName()); |
| mRegistry.removePermission(bp.getName()); |
| } |
| } |
| } |
| return changed; |
| } |
| |
| private void enforceGrantRevokeRuntimePermissionPermissions(String message) { |
| if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS) |
| != PackageManager.PERMISSION_GRANTED |
| && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) |
| != PackageManager.PERMISSION_GRANTED) { |
| throw new SecurityException(message + " requires " |
| + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or " |
| + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS); |
| } |
| } |
| |
| private void enforceGrantRevokeGetRuntimePermissionPermissions(@NonNull String message) { |
| if (mContext.checkCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS) |
| != PackageManager.PERMISSION_GRANTED |
| && mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS) |
| != PackageManager.PERMISSION_GRANTED |
| && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) |
| != PackageManager.PERMISSION_GRANTED) { |
| throw new SecurityException(message + " requires " |
| + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or " |
| + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS + " or " |
| + Manifest.permission.GET_RUNTIME_PERMISSIONS); |
| } |
| } |
| |
| /** |
| * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS |
| * or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userId} is not for the caller. |
| * |
| * @param checkShell whether to prevent shell from access if there's a debugging restriction |
| * @param message the message to log on security exception |
| */ |
| private void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, |
| boolean requireFullPermission, boolean checkShell, @Nullable String message) { |
| if (userId < 0) { |
| throw new IllegalArgumentException("Invalid userId " + userId); |
| } |
| if (checkShell) { |
| enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); |
| } |
| final int callingUserId = UserHandle.getUserId(callingUid); |
| if (checkCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission)) { |
| return; |
| } |
| String errorMessage = buildInvalidCrossUserPermissionMessage( |
| callingUid, userId, message, requireFullPermission); |
| Slog.w(TAG, errorMessage); |
| throw new SecurityException(errorMessage); |
| } |
| |
| /** |
| * Enforces that if the caller is shell, it does not have the provided user restriction. |
| */ |
| private void enforceShellRestriction(@NonNull String restriction, int callingUid, |
| @UserIdInt int userId) { |
| if (callingUid == Process.SHELL_UID) { |
| if (userId >= 0 && mUserManagerInt.hasUserRestriction(restriction, userId)) { |
| throw new SecurityException("Shell does not have permission to access user " |
| + userId); |
| } else if (userId < 0) { |
| Slog.e(LOG_TAG, "Unable to check shell permission for user " |
| + userId + "\n\t" + Debug.getCallers(3)); |
| } |
| } |
| } |
| |
| private boolean checkCrossUserPermission(int callingUid, @UserIdInt int callingUserId, |
| @UserIdInt int userId, boolean requireFullPermission) { |
| if (userId == callingUserId) { |
| return true; |
| } |
| if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) { |
| return true; |
| } |
| if (requireFullPermission) { |
| return checkCallingOrSelfPermission( |
| android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); |
| } |
| return checkCallingOrSelfPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) |
| || checkCallingOrSelfPermission(android.Manifest.permission.INTERACT_ACROSS_USERS); |
| } |
| |
| private boolean checkCallingOrSelfPermission(String permission) { |
| return mContext.checkCallingOrSelfPermission(permission) |
| == PackageManager.PERMISSION_GRANTED; |
| } |
| |
| @NonNull |
| private static String buildInvalidCrossUserPermissionMessage(int callingUid, |
| @UserIdInt int userId, @Nullable String message, boolean requireFullPermission) { |
| StringBuilder builder = new StringBuilder(); |
| if (message != null) { |
| builder.append(message); |
| builder.append(": "); |
| } |
| builder.append("UID "); |
| builder.append(callingUid); |
| builder.append(" requires "); |
| builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); |
| if (!requireFullPermission) { |
| builder.append(" or "); |
| builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS); |
| } |
| builder.append(" to access user "); |
| builder.append(userId); |
| builder.append("."); |
| return builder.toString(); |
| } |
| |
| @GuardedBy("mLock") |
| private int calculateCurrentPermissionFootprintLocked(@NonNull Permission permissionTree) { |
| int size = 0; |
| for (final Permission permission : mRegistry.getPermissions()) { |
| size += permissionTree.calculateFootprint(permission); |
| } |
| return size; |
| } |
| |
| @GuardedBy("mLock") |
| private void enforcePermissionCapLocked(PermissionInfo info, Permission tree) { |
| // We calculate the max size of permissions defined by this uid and throw |
| // if that plus the size of 'info' would exceed our stated maximum. |
| if (tree.getUid() != Process.SYSTEM_UID) { |
| final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree); |
| if (curTreeSize + info.calculateFootprint() > MAX_PERMISSION_TREE_FOOTPRINT) { |
| throw new SecurityException("Permission tree size cap exceeded"); |
| } |
| } |
| } |
| |
| @Override |
| public void onSystemReady() { |
| // Now that we've scanned all packages, and granted any default |
| // permissions, ensure permissions are updated. Beware of dragons if you |
| // try optimizing this. |
| updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false); |
| |
| final PermissionPolicyInternal permissionPolicyInternal = LocalServices.getService( |
| PermissionPolicyInternal.class); |
| permissionPolicyInternal.setOnInitializedCallback(userId -> |
| // The SDK updated case is already handled when we run during the ctor. |
| updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false) |
| ); |
| |
| synchronized (mLock) { |
| mSystemReady = true; |
| |
| if (mPrivappPermissionsViolations != null) { |
| throw new IllegalStateException("Signature|privileged permissions not in " |
| + "privapp-permissions allowlist: " + mPrivappPermissionsViolations); |
| } |
| } |
| |
| mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class); |
| mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class); |
| } |
| |
| private static String getVolumeUuidForPackage(AndroidPackage pkg) { |
| if (pkg == null) { |
| return StorageManager.UUID_PRIVATE_INTERNAL; |
| } |
| if (pkg.isExternalStorage()) { |
| if (TextUtils.isEmpty(pkg.getVolumeUuid())) { |
| return StorageManager.UUID_PRIMARY_PHYSICAL; |
| } else { |
| return pkg.getVolumeUuid(); |
| } |
| } else { |
| return StorageManager.UUID_PRIVATE_INTERNAL; |
| } |
| } |
| |
| private static boolean hasPermission(AndroidPackage pkg, String permName) { |
| if (pkg.getPermissions().isEmpty()) { |
| return false; |
| } |
| |
| for (int i = pkg.getPermissions().size() - 1; i >= 0; i--) { |
| if (pkg.getPermissions().get(i).getName().equals(permName)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Log that a permission request was granted/revoked. |
| * |
| * @param action the action performed |
| * @param name name of the permission |
| * @param packageName package permission is for |
| */ |
| private void logPermission(int action, @NonNull String name, @NonNull String packageName) { |
| final LogMaker log = new LogMaker(action); |
| log.setPackageName(packageName); |
| log.addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, name); |
| |
| mMetricsLogger.write(log); |
| } |
| |
| @GuardedBy("mLock") |
| @Nullable |
| private UidPermissionState getUidStateLocked(@NonNull PackageStateInternal ps, |
| @UserIdInt int userId) { |
| return getUidStateLocked(ps.getAppId(), userId); |
| } |
| |
| @GuardedBy("mLock") |
| @Nullable |
| private UidPermissionState getUidStateLocked(@NonNull AndroidPackage pkg, |
| @UserIdInt int userId) { |
| return getUidStateLocked(pkg.getUid(), userId); |
| } |
| |
| @GuardedBy("mLock") |
| @Nullable |
| private UidPermissionState getUidStateLocked(@AppIdInt int appId, @UserIdInt int userId) { |
| final UserPermissionState userState = mState.getUserState(userId); |
| if (userState == null) { |
| return null; |
| } |
| return userState.getUidState(appId); |
| } |
| |
| private void removeUidStateAndResetPackageInstallPermissionsFixed(@AppIdInt int appId, |
| @NonNull String packageName, @UserIdInt int userId) { |
| synchronized (mLock) { |
| final UserPermissionState userState = mState.getUserState(userId); |
| if (userState == null) { |
| return; |
| } |
| userState.removeUidState(appId); |
| userState.setInstallPermissionsFixed(packageName, false); |
| } |
| } |
| |
| @Override |
| public void readLegacyPermissionStateTEMP() { |
| final int[] userIds = getAllUserIds(); |
| mPackageManagerInt.forEachPackageState(ps -> { |
| final int appId = ps.getAppId(); |
| final LegacyPermissionState legacyState; |
| if (ps.hasSharedUser()) { |
| legacyState = mPackageManagerInt.getSharedUserApi( |
| ps.getSharedUserAppId()).getSharedUserLegacyPermissionState(); |
| } else { |
| legacyState = ps.getLegacyPermissionState(); |
| } |
| synchronized (mLock) { |
| for (final int userId : userIds) { |
| final UserPermissionState userState = mState.getOrCreateUserState(userId); |
| |
| userState.setInstallPermissionsFixed(ps.getPackageName(), |
| ps.isInstallPermissionsFixed()); |
| final UidPermissionState uidState = userState.getOrCreateUidState(appId); |
| uidState.reset(); |
| uidState.setMissing(legacyState.isMissing(userId)); |
| readLegacyPermissionStatesLocked(uidState, |
| legacyState.getPermissionStates(userId)); |
| } |
| } |
| }); |
| } |
| |
| @GuardedBy("mLock") |
| private void readLegacyPermissionStatesLocked(@NonNull UidPermissionState uidState, |
| @NonNull Collection<LegacyPermissionState.PermissionState> permissionStates) { |
| for (final LegacyPermissionState.PermissionState permissionState : permissionStates) { |
| final String permissionName = permissionState.getName(); |
| final Permission permission = mRegistry.getPermission(permissionName); |
| if (permission == null) { |
| Slog.w(TAG, "Unknown permission: " + permissionName); |
| continue; |
| } |
| uidState.putPermissionState(permission, permissionState.isGranted(), |
| permissionState.getFlags()); |
| } |
| } |
| |
| @Override |
| public void writeLegacyPermissionStateTEMP() { |
| final int[] userIds; |
| synchronized (mLock) { |
| userIds = mState.getUserIds(); |
| } |
| mPackageManagerInt.forEachPackageSetting(ps -> { |
| ps.setInstallPermissionsFixed(false); |
| final LegacyPermissionState legacyState; |
| if (ps.hasSharedUser()) { |
| legacyState = mPackageManagerInt.getSharedUserApi( |
| ps.getSharedUserAppId()).getSharedUserLegacyPermissionState(); |
| } else { |
| legacyState = ps.getLegacyPermissionState(); |
| } |
| legacyState.reset(); |
| final int appId = ps.getAppId(); |
| |
| synchronized (mLock) { |
| for (final int userId : userIds) { |
| final UserPermissionState userState = mState.getUserState(userId); |
| if (userState == null) { |
| Slog.e(TAG, "Missing user state for " + userId); |
| continue; |
| } |
| |
| if (userState.areInstallPermissionsFixed(ps.getPackageName())) { |
| ps.setInstallPermissionsFixed(true); |
| } |
| |
| final UidPermissionState uidState = userState.getUidState(appId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permission state for " + ps.getPackageName() |
| + " and user " + userId); |
| continue; |
| } |
| |
| legacyState.setMissing(uidState.isMissing(), userId); |
| final List<PermissionState> permissionStates = uidState.getPermissionStates(); |
| final int permissionStatesSize = permissionStates.size(); |
| for (int i = 0; i < permissionStatesSize; i++) { |
| final PermissionState permissionState = permissionStates.get(i); |
| |
| final LegacyPermissionState.PermissionState legacyPermissionState = |
| new LegacyPermissionState.PermissionState(permissionState.getName(), |
| permissionState.getPermission().isRuntime(), |
| permissionState.isGranted(), permissionState.getFlags()); |
| legacyState.putPermissionState(legacyPermissionState, userId); |
| } |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void readLegacyPermissionsTEMP( |
| @NonNull LegacyPermissionSettings legacyPermissionSettings) { |
| for (int readPermissionOrPermissionTree = 0; readPermissionOrPermissionTree < 2; |
| readPermissionOrPermissionTree++) { |
| final List<LegacyPermission> legacyPermissions = readPermissionOrPermissionTree == 0 |
| ? legacyPermissionSettings.getPermissions() |
| : legacyPermissionSettings.getPermissionTrees(); |
| synchronized (mLock) { |
| final int legacyPermissionsSize = legacyPermissions.size(); |
| for (int i = 0; i < legacyPermissionsSize; i++) { |
| final LegacyPermission legacyPermission = legacyPermissions.get(i); |
| final Permission permission = new Permission( |
| legacyPermission.getPermissionInfo(), legacyPermission.getType()); |
| if (readPermissionOrPermissionTree == 0) { |
| // Config permissions are currently read in PermissionManagerService |
| // constructor. The old behavior was to add other attributes to the config |
| // permission in LegacyPermission.read(), so equivalently we can add the |
| // GIDs to the new permissions here, since config permissions created in |
| // PermissionManagerService constructor get only their names and GIDs there. |
| final Permission configPermission = mRegistry.getPermission( |
| permission.getName()); |
| if (configPermission != null |
| && configPermission.getType() == Permission.TYPE_CONFIG) { |
| permission.setGids(configPermission.getRawGids(), |
| configPermission.areGidsPerUser()); |
| } |
| mRegistry.addPermission(permission); |
| } else { |
| mRegistry.addPermissionTree(permission); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void writeLegacyPermissionsTEMP( |
| @NonNull LegacyPermissionSettings legacyPermissionSettings) { |
| for (int writePermissionOrPermissionTree = 0; writePermissionOrPermissionTree < 2; |
| writePermissionOrPermissionTree++) { |
| final List<LegacyPermission> legacyPermissions = new ArrayList<>(); |
| synchronized (mLock) { |
| final Collection<Permission> permissions = writePermissionOrPermissionTree == 0 |
| ? mRegistry.getPermissions() : mRegistry.getPermissionTrees(); |
| for (final Permission permission : permissions) { |
| // We don't need to provide UID and GIDs, which are only retrieved when dumping. |
| final LegacyPermission legacyPermission = new LegacyPermission( |
| permission.getPermissionInfo(), permission.getType(), 0, |
| EmptyArray.INT); |
| legacyPermissions.add(legacyPermission); |
| } |
| } |
| if (writePermissionOrPermissionTree == 0) { |
| legacyPermissionSettings.replacePermissions(legacyPermissions); |
| } else { |
| legacyPermissionSettings.replacePermissionTrees(legacyPermissions); |
| } |
| } |
| } |
| |
| private void onPackageAddedInternal(@NonNull AndroidPackage pkg, boolean isInstantApp, |
| @Nullable AndroidPackage oldPkg) { |
| if (!pkg.getAdoptPermissions().isEmpty()) { |
| // This package wants to adopt ownership of permissions from |
| // another package. |
| for (int i = pkg.getAdoptPermissions().size() - 1; i >= 0; i--) { |
| final String origName = pkg.getAdoptPermissions().get(i); |
| if (canAdoptPermissionsInternal(origName, pkg)) { |
| Slog.i(TAG, "Adopting permissions from " + origName + " to " |
| + pkg.getPackageName()); |
| synchronized (mLock) { |
| mRegistry.transferPermissions(origName, pkg.getPackageName()); |
| } |
| } |
| } |
| } |
| |
| // Don't allow ephemeral applications to define new permissions groups. |
| if (isInstantApp) { |
| Slog.w(TAG, "Permission groups from package " + pkg.getPackageName() |
| + " ignored: instant apps cannot define new permission groups."); |
| } else { |
| addAllPermissionGroupsInternal(pkg); |
| } |
| |
| // If a permission has had its defining app changed, or it has had its protection |
| // upgraded, we need to revoke apps that hold it |
| final List<String> permissionsWithChangedDefinition; |
| // Don't allow ephemeral applications to define new permissions. |
| if (isInstantApp) { |
| permissionsWithChangedDefinition = null; |
| Slog.w(TAG, "Permissions from package " + pkg.getPackageName() |
| + " ignored: instant apps cannot define new permissions."); |
| } else { |
| permissionsWithChangedDefinition = addAllPermissionsInternal(pkg); |
| } |
| |
| boolean hasOldPkg = oldPkg != null; |
| boolean hasPermissionDefinitionChanges = |
| !CollectionUtils.isEmpty(permissionsWithChangedDefinition); |
| if (hasOldPkg || hasPermissionDefinitionChanges) { |
| // We need to call revokeRuntimePermissionsIfGroupChanged async as permission |
| // revoke callbacks from this method might need to kill apps which need the |
| // mPackages lock on a different thread. This would dead lock. |
| AsyncTask.execute(() -> { |
| if (hasOldPkg) { |
| revokeRuntimePermissionsIfGroupChangedInternal(pkg, oldPkg); |
| revokeStoragePermissionsIfScopeExpandedInternal(pkg, oldPkg); |
| } |
| if (hasPermissionDefinitionChanges) { |
| revokeRuntimePermissionsIfPermissionDefinitionChangedInternal( |
| permissionsWithChangedDefinition); |
| } |
| }); |
| } |
| } |
| |
| private boolean canAdoptPermissionsInternal(@NonNull String oldPackageName, |
| @NonNull AndroidPackage newPkg) { |
| final PackageStateInternal oldPs = |
| mPackageManagerInt.getPackageStateInternal(oldPackageName); |
| if (oldPs == null) { |
| return false; |
| } |
| if (!oldPs.isSystem()) { |
| Slog.w(TAG, "Unable to update from " + oldPs.getPackageName() |
| + " to " + newPkg.getPackageName() |
| + ": old package not in system partition"); |
| return false; |
| } |
| if (mPackageManagerInt.getPackage(oldPs.getPackageName()) != null) { |
| Slog.w(TAG, "Unable to update from " + oldPs.getPackageName() |
| + " to " + newPkg.getPackageName() |
| + ": old package still exists"); |
| return false; |
| } |
| return true; |
| } |
| |
| private boolean isEffectivelyGranted(PermissionState state) { |
| final int flags = state.getFlags(); |
| final int denyMask = FLAG_PERMISSION_REVIEW_REQUIRED |
| | FLAG_PERMISSION_REVOKED_COMPAT |
| | FLAG_PERMISSION_ONE_TIME; |
| |
| if ((flags & FLAG_PERMISSION_SYSTEM_FIXED) != 0) { |
| return true; |
| } else if ((flags & FLAG_PERMISSION_POLICY_FIXED) != 0) { |
| return (flags & FLAG_PERMISSION_REVOKED_COMPAT) == 0 && state.isGranted(); |
| } else if ((flags & denyMask) != 0) { |
| return false; |
| } else { |
| return state.isGranted(); |
| } |
| } |
| |
| /** |
| * Merge srcState into destState. Return [granted, flags]. |
| */ |
| private Pair<Boolean, Integer> mergePermissionState(int appId, |
| PermissionState srcState, PermissionState destState) { |
| // This merging logic prioritizes the shared permission state (destState) over |
| // the current package's state (srcState), because an uninstallation of a previously |
| // unrelated app (the updated system app) should not affect the functionality of |
| // existing apps (other apps in the shared UID group). |
| |
| final int userSettableMask = FLAG_PERMISSION_USER_SET |
| | FLAG_PERMISSION_USER_FIXED |
| | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; |
| |
| final int defaultGrantMask = FLAG_PERMISSION_GRANTED_BY_DEFAULT |
| | FLAG_PERMISSION_GRANTED_BY_ROLE; |
| |
| final int priorityFixedMask = FLAG_PERMISSION_SYSTEM_FIXED |
| | FLAG_PERMISSION_POLICY_FIXED; |
| |
| final int priorityMask = defaultGrantMask | priorityFixedMask; |
| |
| final int destFlags = destState.getFlags(); |
| final boolean destIsGranted = isEffectivelyGranted(destState); |
| |
| final int srcFlags = srcState.getFlags(); |
| final boolean srcIsGranted = isEffectivelyGranted(srcState); |
| |
| final int combinedFlags = destFlags | srcFlags; |
| |
| /* Merge flags */ |
| |
| int newFlags = 0; |
| |
| // Inherit user set flags only from dest as we want to preserve the |
| // user preference of destState, not the one of the current package. |
| newFlags |= (destFlags & userSettableMask); |
| |
| // Inherit all exempt flags |
| newFlags |= (combinedFlags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT); |
| // If no exempt flags are set, set APPLY_RESTRICTION |
| if ((newFlags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) { |
| newFlags |= FLAG_PERMISSION_APPLY_RESTRICTION; |
| } |
| |
| // Inherit all priority flags |
| newFlags |= (combinedFlags & priorityMask); |
| |
| // If no priority flags are set, inherit REVOKE_WHEN_REQUESTED |
| if ((combinedFlags & priorityMask) == 0) { |
| newFlags |= (combinedFlags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED); |
| } |
| |
| // Handle REVIEW_REQUIRED |
| if ((newFlags & priorityFixedMask) == 0) { |
| if ((newFlags & (defaultGrantMask | userSettableMask)) == 0 |
| && NOTIFICATION_PERMISSIONS.contains(srcState.getName())) { |
| // For notification permissions, inherit from both states |
| // if no priority FIXED or DEFAULT_GRANT or USER_SET flags are set |
| newFlags |= (combinedFlags & FLAG_PERMISSION_REVIEW_REQUIRED); |
| } else if ((newFlags & priorityMask) == 0) { |
| // Else inherit from destState if no priority flags are set |
| newFlags |= (destFlags & FLAG_PERMISSION_REVIEW_REQUIRED); |
| } |
| } |
| |
| /* Determine effective grant state */ |
| |
| final boolean effectivelyGranted; |
| if ((newFlags & FLAG_PERMISSION_SYSTEM_FIXED) != 0) { |
| effectivelyGranted = true; |
| } else if ((destFlags & FLAG_PERMISSION_POLICY_FIXED) != 0) { |
| // If this flag comes from destState, preserve its state |
| effectivelyGranted = destIsGranted; |
| } else if ((srcFlags & FLAG_PERMISSION_POLICY_FIXED) != 0) { |
| effectivelyGranted = destIsGranted || srcIsGranted; |
| // If this flag comes from srcState, preserve flag only if |
| // there is no conflict |
| if (destIsGranted != srcIsGranted) { |
| newFlags &= ~FLAG_PERMISSION_POLICY_FIXED; |
| } |
| } else if ((destFlags & defaultGrantMask) != 0) { |
| // If a permission state has default grant flags and is not |
| // granted, this meant user has overridden the grant state. |
| // Respect the user's preference on destState. |
| // Due to this reason, if this flag comes from destState, |
| // preserve its state |
| effectivelyGranted = destIsGranted; |
| } else if ((srcFlags & defaultGrantMask) != 0) { |
| effectivelyGranted = destIsGranted || srcIsGranted; |
| } else if ((destFlags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) { |
| // Similar reason to defaultGrantMask, if this flag comes |
| // from destState, preserve its state |
| effectivelyGranted = destIsGranted; |
| } else if ((srcFlags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) { |
| effectivelyGranted = destIsGranted || srcIsGranted; |
| // If this flag comes from srcState, remove this flag if |
| // destState is already granted to prevent revocation. |
| if (destIsGranted) { |
| newFlags &= ~FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; |
| } |
| } else { |
| // If still not determined, fallback to destState. |
| effectivelyGranted = destIsGranted; |
| } |
| |
| /* Post-processing / fix ups */ |
| |
| if (!effectivelyGranted) { |
| // If not effectively granted, inherit AUTO_REVOKED |
| newFlags |= (combinedFlags & FLAG_PERMISSION_AUTO_REVOKED); |
| |
| // REVOKE_WHEN_REQUESTED make no sense when denied |
| newFlags &= ~FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; |
| } else { |
| // REVIEW_REQUIRED make no sense when granted |
| newFlags &= ~FLAG_PERMISSION_REVIEW_REQUIRED; |
| } |
| |
| if (effectivelyGranted != destIsGranted) { |
| // Remove user set flags if state changes |
| newFlags &= ~userSettableMask; |
| } |
| |
| // Fix permission state based on targetSdk of the shared UID |
| final boolean newGrantState; |
| if (!effectivelyGranted && isPermissionSplitFromNonRuntime( |
| srcState.getName(), |
| mPackageManagerInt.getUidTargetSdkVersion(appId))) { |
| // Even though effectively denied, it has to be set to granted |
| // for backwards compatibility |
| newFlags |= FLAG_PERMISSION_REVOKED_COMPAT; |
| newGrantState = true; |
| } else { |
| // Either it's effectively granted, or it targets a high enough API level |
| // to handle this permission properly |
| newGrantState = effectivelyGranted; |
| } |
| |
| return new Pair<>(newGrantState, newFlags); |
| } |
| |
| /** |
| * This method handles permission migration of packages leaving/joining shared UID |
| */ |
| private void handleAppIdMigration(@NonNull AndroidPackage pkg, int previousAppId) { |
| final PackageStateInternal ps = |
| mPackageManagerInt.getPackageStateInternal(pkg.getPackageName()); |
| |
| if (ps.hasSharedUser()) { |
| // The package is joining a shared user group. This can only happen when a system |
| // app left shared UID with an update, and then the update is uninstalled. |
| // If no apps remain in its original shared UID group, clone the current |
| // permission state to the shared appId; or else, merge the current permission |
| // state into the shared UID state. |
| |
| synchronized (mLock) { |
| for (final int userId : getAllUserIds()) { |
| final UserPermissionState userState = mState.getOrCreateUserState(userId); |
| |
| // This is the permission state the package was using |
| final UidPermissionState uidState = userState.getUidState(previousAppId); |
| if (uidState == null) { |
| continue; |
| } |
| |
| // This is the shared UID permission state the package wants to join |
| final UidPermissionState sharedUidState = userState.getUidState(ps.getAppId()); |
| if (sharedUidState == null) { |
| // No apps remain in the shared UID group, clone permissions |
| userState.createUidStateWithExisting(ps.getAppId(), uidState); |
| } else { |
| final List<PermissionState> states = uidState.getPermissionStates(); |
| final int count = states.size(); |
| for (int i = 0; i < count; ++i) { |
| final PermissionState srcState = states.get(i); |
| final PermissionState destState = |
| sharedUidState.getPermissionState(srcState.getName()); |
| if (destState != null) { |
| // Merge the 2 permission states |
| Pair<Boolean, Integer> newState = |
| mergePermissionState(ps.getAppId(), srcState, destState); |
| sharedUidState.putPermissionState(srcState.getPermission(), |
| newState.first, newState.second); |
| } else { |
| // Simply copy the permission state over |
| sharedUidState.putPermissionState(srcState.getPermission(), |
| srcState.isGranted(), srcState.getFlags()); |
| } |
| } |
| } |
| |
| // Remove permissions for the previous appId |
| userState.removeUidState(previousAppId); |
| } |
| } |
| } else { |
| // The package is migrating out of a shared user group. |
| // Operations we need to do before calling updatePermissions(): |
| // - Retrieve the original uid permission state and create a copy of it as the |
| // new app's uid state. The new permission state will be properly updated in |
| // updatePermissions(). |
| // - Remove the app from the original shared user group. Other apps in the shared |
| // user group will perceive as if the original app is uninstalled. |
| |
| final List<AndroidPackage> origSharedUserPackages = |
| mPackageManagerInt.getPackagesForAppId(previousAppId); |
| |
| synchronized (mLock) { |
| for (final int userId : getAllUserIds()) { |
| // Retrieve the original uid state |
| final UserPermissionState userState = mState.getUserState(userId); |
| if (userState == null) { |
| continue; |
| } |
| final UidPermissionState prevUidState = userState.getUidState(previousAppId); |
| if (prevUidState == null) { |
| continue; |
| } |
| |
| // Insert new uid state by cloning the original one |
| userState.createUidStateWithExisting(ps.getAppId(), prevUidState); |
| |
| // Remove original app ID from original shared user group |
| // Should match the implementation of onPackageUninstalledInternal(...) |
| if (origSharedUserPackages.isEmpty()) { |
| removeUidStateAndResetPackageInstallPermissionsFixed( |
| previousAppId, pkg.getPackageName(), userId); |
| } else { |
| revokeSharedUserPermissionsForLeavingPackageInternal(pkg, previousAppId, |
| origSharedUserPackages, userId); |
| } |
| } |
| } |
| } |
| } |
| |
| private void onPackageInstalledInternal(@NonNull AndroidPackage pkg, int previousAppId, |
| @NonNull PermissionManagerServiceInternal.PackageInstalledParams params, |
| @UserIdInt int[] userIds) { |
| if (previousAppId != INVALID_UID) { |
| handleAppIdMigration(pkg, previousAppId); |
| } |
| updatePermissions(pkg.getPackageName(), pkg); |
| for (final int userId : userIds) { |
| addAllowlistedRestrictedPermissionsInternal(pkg, |
| params.getAllowlistedRestrictedPermissions(), |
| FLAG_PERMISSION_WHITELIST_INSTALLER, userId); |
| grantRequestedRuntimePermissionsInternal(pkg, params.getGrantedPermissions(), userId); |
| } |
| } |
| |
| private void addAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg, |
| @NonNull List<String> allowlistedRestrictedPermissions, |
| @PackageManager.PermissionWhitelistFlags int flags, @UserIdInt int userId) { |
| List<String> permissions = getAllowlistedRestrictedPermissionsInternal(pkg, flags, userId); |
| if (permissions != null) { |
| ArraySet<String> permissionSet = new ArraySet<>(permissions); |
| permissionSet.addAll(allowlistedRestrictedPermissions); |
| permissions = new ArrayList<>(permissionSet); |
| } else { |
| permissions = allowlistedRestrictedPermissions; |
| } |
| setAllowlistedRestrictedPermissionsInternal(pkg, permissions, flags, userId); |
| } |
| |
| private void onPackageRemovedInternal(@NonNull AndroidPackage pkg) { |
| removeAllPermissionsInternal(pkg); |
| } |
| |
| private void onPackageUninstalledInternal(@NonNull String packageName, int appId, |
| @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs, |
| @UserIdInt int[] userIds) { |
| // TODO: Handle the case when a system app upgrade is uninstalled and need to rejoin |
| // a shared UID permission state. |
| |
| // TODO: Move these checks to check PackageState to be more reliable. |
| // System packages should always have an available APK. |
| if (pkg != null && pkg.isSystem() |
| // We may be fully removing invalid system packages during boot, and in that case we |
| // do want to remove their permission state. So make sure that the package is only |
| // being marked as uninstalled instead of fully removed. |
| && mPackageManagerInt.getPackage(packageName) != null) { |
| // If we are only marking a system package as uninstalled, we need to keep its |
| // pregranted permission state so that it still works once it gets reinstalled, thus |
| // only reset the user modifications to its permission state. |
| for (final int userId : userIds) { |
| resetRuntimePermissionsInternal(pkg, userId); |
| } |
| return; |
| } |
| updatePermissions(packageName, null); |
| for (final int userId : userIds) { |
| if (sharedUserPkgs.isEmpty()) { |
| removeUidStateAndResetPackageInstallPermissionsFixed(appId, packageName, userId); |
| } else { |
| // Remove permissions associated with package. Since runtime |
| // permissions are per user we have to kill the removed package |
| // or packages running under the shared user of the removed |
| // package if revoking the permissions requested only by the removed |
| // package is successful and this causes a change in gids. |
| revokeSharedUserPermissionsForLeavingPackageInternal(pkg, appId, |
| sharedUserPkgs, userId); |
| } |
| } |
| } |
| |
| @NonNull |
| @Override |
| public List<LegacyPermission> getLegacyPermissions() { |
| synchronized (mLock) { |
| final List<LegacyPermission> legacyPermissions = new ArrayList<>(); |
| for (final Permission permission : mRegistry.getPermissions()) { |
| final LegacyPermission legacyPermission = new LegacyPermission( |
| permission.getPermissionInfo(), permission.getType(), permission.getUid(), |
| permission.getRawGids()); |
| legacyPermissions.add(legacyPermission); |
| } |
| return legacyPermissions; |
| } |
| } |
| |
| @Override |
| public Map<String, Set<String>> getAllAppOpPermissionPackages() { |
| synchronized (mLock) { |
| final ArrayMap<String, ArraySet<String>> appOpPermissionPackages = |
| mRegistry.getAllAppOpPermissionPackages(); |
| final Map<String, Set<String>> deepClone = new ArrayMap<>(); |
| final int appOpPermissionPackagesSize = appOpPermissionPackages.size(); |
| for (int i = 0; i < appOpPermissionPackagesSize; i++) { |
| final String appOpPermission = appOpPermissionPackages.keyAt(i); |
| final ArraySet<String> packageNames = appOpPermissionPackages.valueAt(i); |
| deepClone.put(appOpPermission, new ArraySet<>(packageNames)); |
| } |
| return deepClone; |
| } |
| } |
| |
| @NonNull |
| @Override |
| public LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId) { |
| final LegacyPermissionState legacyState = new LegacyPermissionState(); |
| synchronized (mLock) { |
| final int[] userIds = mState.getUserIds(); |
| for (final int userId : userIds) { |
| final UidPermissionState uidState = getUidStateLocked(appId, userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID " |
| + userId); |
| continue; |
| } |
| |
| final List<PermissionState> permissionStates = uidState.getPermissionStates(); |
| final int permissionStatesSize = permissionStates.size(); |
| for (int i = 0; i < permissionStatesSize; i++) { |
| final PermissionState permissionState = permissionStates.get(i); |
| |
| final LegacyPermissionState.PermissionState legacyPermissionState = |
| new LegacyPermissionState.PermissionState(permissionState.getName(), |
| permissionState.getPermission().isRuntime(), |
| permissionState.isGranted(), permissionState.getFlags()); |
| legacyState.putPermissionState(legacyPermissionState, userId); |
| } |
| } |
| } |
| return legacyState; |
| } |
| |
| @NonNull |
| @Override |
| public int[] getGidsForUid(int uid) { |
| final int appId = UserHandle.getAppId(uid); |
| final int userId = UserHandle.getUserId(uid); |
| synchronized (mLock) { |
| final UidPermissionState uidState = getUidStateLocked(appId, userId); |
| if (uidState == null) { |
| Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID " |
| + userId); |
| return EMPTY_INT_ARRAY; |
| } |
| return uidState.computeGids(mGlobalGids, userId); |
| } |
| } |
| |
| @Override |
| public boolean isPermissionsReviewRequired(@NonNull String packageName, |
| @UserIdInt int userId) { |
| Objects.requireNonNull(packageName, "packageName"); |
| // TODO(b/173235285): Some caller may pass USER_ALL as userId. |
| //Preconditions.checkArgumentNonnegative(userId, "userId"); |
| return isPermissionsReviewRequiredInternal(packageName, userId); |
| } |
| |
| @NonNull |
| @Override |
| public Set<String> getGrantedPermissions(@NonNull String packageName, |
| @UserIdInt int userId) { |
| Objects.requireNonNull(packageName, "packageName"); |
| Preconditions.checkArgumentNonNegative(userId, "userId"); |
| return getGrantedPermissionsInternal(packageName, userId); |
| } |
| |
| @NonNull |
| @Override |
| public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) { |
| Objects.requireNonNull(permissionName, "permissionName"); |
| Preconditions.checkArgumentNonNegative(userId, "userId"); |
| return getPermissionGidsInternal(permissionName, userId); |
| } |
| |
| @NonNull |
| @Override |
| public String[] getAppOpPermissionPackages(@NonNull String permissionName) { |
| Objects.requireNonNull(permissionName, "permissionName"); |
| return PermissionManagerServiceImpl.this.getAppOpPermissionPackagesInternal(permissionName); |
| } |
| |
| @Override |
| public void onStorageVolumeMounted(@Nullable String volumeUuid, boolean fingerprintChanged) { |
| updateAllPermissions(volumeUuid, fingerprintChanged); |
| } |
| |
| @Override |
| public void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId) { |
| Objects.requireNonNull(pkg, "pkg"); |
| Preconditions.checkArgumentNonNegative(userId, "userId"); |
| resetRuntimePermissionsInternal(pkg, userId); |
| } |
| |
| @Override |
| public Permission getPermissionTEMP(String permName) { |
| synchronized (mLock) { |
| return mRegistry.getPermission(permName); |
| } |
| } |
| |
| @NonNull |
| @Override |
| public ArrayList<PermissionInfo> getAllPermissionsWithProtection( |
| @PermissionInfo.Protection int protection) { |
| ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>(); |
| |
| synchronized (mLock) { |
| for (final Permission permission : mRegistry.getPermissions()) { |
| if (permission.getProtection() == protection) { |
| matchingPermissions.add(permission.generatePermissionInfo(0)); |
| } |
| } |
| } |
| |
| return matchingPermissions; |
| } |
| |
| @NonNull |
| @Override |
| public ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags( |
| @PermissionInfo.ProtectionFlags int protectionFlags) { |
| ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>(); |
| |
| synchronized (mLock) { |
| for (final Permission permission : mRegistry.getPermissions()) { |
| if ((permission.getProtectionFlags() & protectionFlags) == protectionFlags) { |
| matchingPermissions.add(permission.generatePermissionInfo(0)); |
| } |
| } |
| } |
| |
| return matchingPermissions; |
| } |
| |
| @Override |
| public void onUserCreated(@UserIdInt int userId) { |
| Preconditions.checkArgumentNonNegative(userId, "userId"); |
| // NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG |
| updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, true); |
| } |
| |
| @Override |
| public void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp, |
| @Nullable AndroidPackage oldPkg) { |
| Objects.requireNonNull(pkg); |
| onPackageAddedInternal(pkg, isInstantApp, oldPkg); |
| } |
| |
| @Override |
| public void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId, |
| @NonNull PermissionManagerServiceInternal.PackageInstalledParams params, |
| @UserIdInt int userId) { |
| Objects.requireNonNull(pkg, "pkg"); |
| Objects.requireNonNull(params, "params"); |
| Preconditions.checkArgument(userId >= UserHandle.USER_SYSTEM |
| || userId == UserHandle.USER_ALL, "userId"); |
| final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds() |
| : new int[] { userId }; |
| onPackageInstalledInternal(pkg, previousAppId, params, userIds); |
| } |
| |
| @Override |
| public void onPackageRemoved(@NonNull AndroidPackage pkg) { |
| Objects.requireNonNull(pkg); |
| onPackageRemovedInternal(pkg); |
| } |
| |
| @Override |
| public void onPackageUninstalled(@NonNull String packageName, int appId, |
| @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs, |
| @UserIdInt int userId) { |
| Objects.requireNonNull(packageName, "packageName"); |
| Objects.requireNonNull(sharedUserPkgs, "sharedUserPkgs"); |
| Preconditions.checkArgument(userId >= UserHandle.USER_SYSTEM |
| || userId == UserHandle.USER_ALL, "userId"); |
| final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds() |
| : new int[] { userId }; |
| onPackageUninstalledInternal(packageName, appId, pkg, sharedUserPkgs, userIds); |
| } |
| |
| /** |
| * Callbacks invoked when interesting actions have been taken on a permission. |
| * <p> |
| * NOTE: The current arguments are merely to support the existing use cases. This |
| * needs to be properly thought out with appropriate arguments for each of the |
| * callback methods. |
| */ |
| private static class PermissionCallback { |
| public void onGidsChanged(@AppIdInt int appId, @UserIdInt int userId) {} |
| public void onPermissionChanged() {} |
| public void onPermissionGranted(int uid, @UserIdInt int userId) {} |
| public void onInstallPermissionGranted() {} |
| public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason) { |
| onPermissionRevoked(uid, userId, reason, false); |
| } |
| public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason, |
| boolean overrideKill) {} |
| public void onInstallPermissionRevoked() {} |
| public void onPermissionUpdated(@UserIdInt int[] updatedUserIds, boolean sync) {} |
| public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync, |
| int uid) { |
| onPermissionUpdated(updatedUserIds, sync); |
| } |
| public void onPermissionRemoved() {} |
| public void onInstallPermissionUpdated() {} |
| public void onInstallPermissionUpdatedNotifyListener(int uid) { |
| onInstallPermissionUpdated(); |
| } |
| } |
| |
| private static final class OnPermissionChangeListeners extends Handler { |
| private static final int MSG_ON_PERMISSIONS_CHANGED = 1; |
| |
| private final RemoteCallbackList<IOnPermissionsChangeListener> mPermissionListeners = |
| new RemoteCallbackList<>(); |
| |
| OnPermissionChangeListeners(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_ON_PERMISSIONS_CHANGED: { |
| final int uid = msg.arg1; |
| handleOnPermissionsChanged(uid); |
| } break; |
| } |
| } |
| |
| public void addListener(IOnPermissionsChangeListener listener) { |
| mPermissionListeners.register(listener); |
| } |
| |
| public void removeListener(IOnPermissionsChangeListener listener) { |
| mPermissionListeners.unregister(listener); |
| } |
| |
| public void onPermissionsChanged(int uid) { |
| if (mPermissionListeners.getRegisteredCallbackCount() > 0) { |
| obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0).sendToTarget(); |
| } |
| } |
| |
| private void handleOnPermissionsChanged(int uid) { |
| final int count = mPermissionListeners.beginBroadcast(); |
| try { |
| for (int i = 0; i < count; i++) { |
| IOnPermissionsChangeListener callback = mPermissionListeners |
| .getBroadcastItem(i); |
| try { |
| callback.onPermissionsChanged(uid); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Permission listener is dead", e); |
| } |
| } |
| } finally { |
| mPermissionListeners.finishBroadcast(); |
| } |
| } |
| } |
| } |