| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.policy; |
| |
| import static android.app.AppOpsManager.MODE_ALLOWED; |
| import static android.app.AppOpsManager.MODE_DEFAULT; |
| import static android.app.AppOpsManager.MODE_ERRORED; |
| import static android.app.AppOpsManager.MODE_FOREGROUND; |
| import static android.app.AppOpsManager.MODE_IGNORED; |
| import static android.app.AppOpsManager.OP_NONE; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; |
| import static android.content.pm.PackageManager.GET_PERMISSIONS; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.UserIdInt; |
| import android.app.AppOpsManager; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.pm.PackageManagerInternal; |
| import android.content.pm.PackageManagerInternal.PackageListObserver; |
| import android.content.pm.PackageParser; |
| import android.content.pm.PermissionInfo; |
| import android.os.Build; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.UserHandle; |
| import android.os.UserManagerInternal; |
| import android.permission.PermissionControllerManager; |
| import android.provider.Telephony; |
| import android.telecom.TelecomManager; |
| import android.util.ArraySet; |
| import android.util.LongSparseLongArray; |
| import android.util.Pair; |
| import android.util.Slog; |
| import android.util.SparseBooleanArray; |
| import android.util.SparseIntArray; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.app.IAppOpsCallback; |
| import com.android.internal.app.IAppOpsService; |
| import com.android.internal.util.IntPair; |
| import com.android.internal.util.function.pooled.PooledLambda; |
| import com.android.server.FgThread; |
| import com.android.server.LocalServices; |
| import com.android.server.SystemService; |
| import com.android.server.pm.permission.PermissionManagerServiceInternal; |
| |
| import java.util.ArrayList; |
| import java.util.concurrent.CountDownLatch; |
| |
| /** |
| * This is a permission policy that governs over all permission mechanism |
| * such as permissions, app ops, etc. For example, the policy ensures that |
| * permission state and app ops is synchronized for cases where there is a |
| * dependency between permission state (permissions or permission flags) |
| * and app ops - and vise versa. |
| */ |
| public final class PermissionPolicyService extends SystemService { |
| private static final String LOG_TAG = PermissionPolicyService.class.getSimpleName(); |
| private static final boolean DEBUG = false; |
| |
| private final Object mLock = new Object(); |
| |
| /** Whether the user is started but not yet stopped */ |
| @GuardedBy("mLock") |
| private final SparseBooleanArray mIsStarted = new SparseBooleanArray(); |
| |
| /** |
| * Whether an async {@link #synchronizePackagePermissionsAndAppOpsForUser} is currently |
| * scheduled for a package/user. |
| */ |
| @GuardedBy("mLock") |
| private final ArraySet<Pair<String, Integer>> mIsPackageSyncsScheduled = new ArraySet<>(); |
| |
| public PermissionPolicyService(@NonNull Context context) { |
| super(context); |
| |
| LocalServices.addService(PermissionPolicyInternal.class, new Internal()); |
| } |
| |
| @Override |
| public void onStart() { |
| final PackageManagerInternal packageManagerInternal = LocalServices.getService( |
| PackageManagerInternal.class); |
| final PermissionManagerServiceInternal permManagerInternal = LocalServices.getService( |
| PermissionManagerServiceInternal.class); |
| final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface( |
| ServiceManager.getService(Context.APP_OPS_SERVICE)); |
| |
| packageManagerInternal.getPackageList(new PackageListObserver() { |
| @Override |
| public void onPackageAdded(String packageName, int uid) { |
| onPackageChanged(packageName, uid); |
| } |
| |
| @Override |
| public void onPackageChanged(String packageName, int uid) { |
| final int userId = UserHandle.getUserId(uid); |
| |
| if (isStarted(userId)) { |
| synchronizePackagePermissionsAndAppOpsForUser(packageName, userId); |
| } |
| } |
| |
| @Override |
| public void onPackageRemoved(String packageName, int uid) { |
| /* do nothing */ |
| } |
| }); |
| |
| permManagerInternal.addOnRuntimePermissionStateChangedListener( |
| this::synchronizePackagePermissionsAndAppOpsAsyncForUser); |
| |
| IAppOpsCallback appOpsListener = new IAppOpsCallback.Stub() { |
| public void opChanged(int op, int uid, String packageName) { |
| synchronizePackagePermissionsAndAppOpsAsyncForUser(packageName, |
| UserHandle.getUserId(uid)); |
| } |
| }; |
| |
| final ArrayList<PermissionInfo> dangerousPerms = |
| permManagerInternal.getAllPermissionWithProtectionLevel( |
| PermissionInfo.PROTECTION_DANGEROUS); |
| |
| try { |
| int numDangerousPerms = dangerousPerms.size(); |
| for (int i = 0; i < numDangerousPerms; i++) { |
| PermissionInfo perm = dangerousPerms.get(i); |
| |
| if (perm.isHardRestricted() || perm.backgroundPermission != null) { |
| appOpsService.startWatchingMode(getSwitchOp(perm.name), null, appOpsListener); |
| } else if (perm.isSoftRestricted()) { |
| appOpsService.startWatchingMode(getSwitchOp(perm.name), null, appOpsListener); |
| |
| SoftRestrictedPermissionPolicy policy = |
| SoftRestrictedPermissionPolicy.forPermission(null, null, null, |
| perm.name); |
| if (policy.resolveAppOp() != OP_NONE) { |
| appOpsService.startWatchingMode(policy.resolveAppOp(), null, |
| appOpsListener); |
| } |
| } |
| } |
| } catch (RemoteException doesNotHappen) { |
| Slog.wtf(LOG_TAG, "Cannot set up app-ops listener"); |
| } |
| } |
| |
| /** |
| * Get op that controls the access related to the permission. |
| * |
| * <p>Usually the permission-op relationship is 1:1 but some permissions (e.g. fine location) |
| * {@link AppOpsManager#sOpToSwitch share an op} to control the access. |
| * |
| * @param permission The permission |
| * |
| * @return The op that controls the access of the permission |
| */ |
| private static int getSwitchOp(@NonNull String permission) { |
| int op = AppOpsManager.permissionToOpCode(permission); |
| if (op == OP_NONE) { |
| return OP_NONE; |
| } |
| |
| return AppOpsManager.opToSwitch(op); |
| } |
| |
| private void synchronizePackagePermissionsAndAppOpsAsyncForUser(@NonNull String packageName, |
| @UserIdInt int changedUserId) { |
| if (isStarted(changedUserId)) { |
| synchronized (mLock) { |
| if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) { |
| FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( |
| PermissionPolicyService |
| ::synchronizePackagePermissionsAndAppOpsForUser, |
| this, packageName, changedUserId)); |
| } else { |
| if (DEBUG) { |
| Slog.v(LOG_TAG, "sync for " + packageName + "/" + changedUserId |
| + " already scheduled"); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void onBootPhase(int phase) { |
| if (DEBUG) Slog.i(LOG_TAG, "onBootPhase(" + phase + ")"); |
| |
| if (phase == PHASE_ACTIVITY_MANAGER_READY) { |
| final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class); |
| |
| // For some users we might not receive a onStartUser, hence force one here |
| for (int userId : um.getUserIds()) { |
| if (um.isUserRunning(userId)) { |
| onStartUser(userId); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @return Whether the user is started but not yet stopped |
| */ |
| private boolean isStarted(@UserIdInt int userId) { |
| synchronized (mLock) { |
| return mIsStarted.get(userId); |
| } |
| } |
| |
| @Override |
| public void onStartUser(@UserIdInt int userId) { |
| if (DEBUG) Slog.i(LOG_TAG, "onStartUser(" + userId + ")"); |
| |
| if (isStarted(userId)) { |
| return; |
| } |
| |
| grantOrUpgradeDefaultRuntimePermissionsIfNeeded(userId); |
| |
| synchronized (mLock) { |
| mIsStarted.put(userId, true); |
| } |
| |
| // Force synchronization as permissions might have changed |
| synchronizePermissionsAndAppOpsForUser(userId); |
| } |
| |
| @Override |
| public void onStopUser(@UserIdInt int userId) { |
| if (DEBUG) Slog.i(LOG_TAG, "onStopUser(" + userId + ")"); |
| |
| synchronized (mLock) { |
| mIsStarted.delete(userId); |
| } |
| } |
| |
| private void grantOrUpgradeDefaultRuntimePermissionsIfNeeded(@UserIdInt int userId) { |
| if (DEBUG) Slog.i(LOG_TAG, "grantOrUpgradeDefaultPermsIfNeeded(" + userId + ")"); |
| |
| final PackageManagerInternal packageManagerInternal = |
| LocalServices.getService(PackageManagerInternal.class); |
| final PermissionManagerServiceInternal permissionManagerInternal = |
| LocalServices.getService(PermissionManagerServiceInternal.class); |
| if (permissionManagerInternal.wereDefaultPermissionsGrantedSinceBoot(userId)) { |
| if (DEBUG) Slog.i(LOG_TAG, "defaultPermsWereGrantedSinceBoot(" + userId + ")"); |
| |
| // Now call into the permission controller to apply policy around permissions |
| final CountDownLatch latch = new CountDownLatch(1); |
| |
| // We need to create a local manager that does not schedule work on the main |
| // there as we are on the main thread and want to block until the work is |
| // completed or we time out. |
| final PermissionControllerManager permissionControllerManager = |
| new PermissionControllerManager( |
| getUserContext(getContext(), UserHandle.of(userId)), |
| FgThread.getHandler()); |
| permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions( |
| FgThread.getExecutor(), |
| (Boolean success) -> { |
| if (!success) { |
| // We are in an undefined state now, let us crash and have |
| // rescue party suggest a wipe to recover to a good one. |
| final String message = "Error granting/upgrading runtime permissions"; |
| Slog.wtf(LOG_TAG, message); |
| throw new IllegalStateException(message); |
| } |
| latch.countDown(); |
| } |
| ); |
| try { |
| latch.await(); |
| } catch (InterruptedException e) { |
| /* ignore */ |
| } |
| |
| packageManagerInternal.setRuntimePermissionsFingerPrint(Build.FINGERPRINT, userId); |
| } |
| } |
| |
| private static @Nullable Context getUserContext(@NonNull Context context, |
| @Nullable UserHandle user) { |
| if (context.getUser().equals(user)) { |
| return context; |
| } else { |
| try { |
| return context.createPackageContextAsUser(context.getPackageName(), 0, user); |
| } catch (NameNotFoundException e) { |
| Slog.e(LOG_TAG, "Cannot create context for user " + user, e); |
| return null; |
| } |
| } |
| } |
| |
| /** |
| * Synchronize a single package. |
| */ |
| private void synchronizePackagePermissionsAndAppOpsForUser(@NonNull String packageName, |
| @UserIdInt int userId) { |
| synchronized (mLock) { |
| mIsPackageSyncsScheduled.remove(new Pair<>(packageName, userId)); |
| } |
| |
| if (DEBUG) { |
| Slog.v(LOG_TAG, |
| "synchronizePackagePermissionsAndAppOpsForUser(" + packageName + ", " |
| + userId + ")"); |
| } |
| |
| final PackageManagerInternal packageManagerInternal = LocalServices.getService( |
| PackageManagerInternal.class); |
| final PackageInfo pkg = packageManagerInternal.getPackageInfo(packageName, 0, |
| Process.SYSTEM_UID, userId); |
| if (pkg == null) { |
| return; |
| } |
| final PermissionToOpSynchroniser synchroniser = new PermissionToOpSynchroniser( |
| getUserContext(getContext(), UserHandle.of(userId))); |
| synchroniser.addPackage(pkg.packageName); |
| final String[] sharedPkgNames = packageManagerInternal.getPackagesForSharedUserId( |
| pkg.sharedUserId, userId); |
| if (sharedPkgNames != null) { |
| for (String sharedPkgName : sharedPkgNames) { |
| final PackageParser.Package sharedPkg = packageManagerInternal |
| .getPackage(sharedPkgName); |
| if (sharedPkg != null) { |
| synchroniser.addPackage(sharedPkg.packageName); |
| } |
| } |
| } |
| synchroniser.syncPackages(); |
| } |
| |
| /** |
| * Synchronize all packages |
| */ |
| private void synchronizePermissionsAndAppOpsForUser(@UserIdInt int userId) { |
| if (DEBUG) Slog.i(LOG_TAG, "synchronizePermissionsAndAppOpsForUser(" + userId + ")"); |
| |
| final PackageManagerInternal packageManagerInternal = LocalServices.getService( |
| PackageManagerInternal.class); |
| final PermissionToOpSynchroniser synchronizer = new PermissionToOpSynchroniser( |
| getUserContext(getContext(), UserHandle.of(userId))); |
| packageManagerInternal.forEachPackage((pkg) -> synchronizer.addPackage(pkg.packageName)); |
| synchronizer.syncPackages(); |
| } |
| |
| /** |
| * Synchronizes permission to app ops. You *must* always sync all packages |
| * in a shared UID at the same time to ensure proper synchronization. |
| */ |
| private static class PermissionToOpSynchroniser { |
| private final @NonNull Context mContext; |
| private final @NonNull PackageManager mPackageManager; |
| private final @NonNull AppOpsManager mAppOpsManager; |
| |
| /** All uid that need to be synchronized */ |
| private final @NonNull SparseIntArray mAllUids = new SparseIntArray(); |
| |
| /** |
| * All ops that need to be set to default |
| * |
| * Currently, only used by the restricted permissions logic. |
| * |
| * @see #syncPackages |
| */ |
| private final @NonNull ArrayList<OpToChange> mOpsToDefault = new ArrayList<>(); |
| |
| /** |
| * All ops that need to be flipped to allow if default. |
| * |
| * Currently, only used by the restricted permissions logic. |
| * |
| * @see #syncPackages |
| */ |
| private final @NonNull ArrayList<OpToChange> mOpsToAllowIfDefault = new ArrayList<>(); |
| |
| /** |
| * All ops that need to be flipped to allow. |
| * |
| * @see #syncPackages |
| */ |
| private final @NonNull ArrayList<OpToChange> mOpsToAllow = new ArrayList<>(); |
| |
| /** |
| * All ops that need to be flipped to ignore if default. |
| * |
| * Currently, only used by the restricted permissions logic. |
| * |
| * @see #syncPackages |
| */ |
| private final @NonNull ArrayList<OpToChange> mOpsToIgnoreIfDefault = new ArrayList<>(); |
| |
| /** |
| * All ops that need to be flipped to ignore. |
| * |
| * @see #syncPackages |
| */ |
| private final @NonNull ArrayList<OpToChange> mOpsToIgnore = new ArrayList<>(); |
| |
| /** |
| * All ops that need to be flipped to foreground. |
| * |
| * Currently, only used by the foreground/background permissions logic. |
| * |
| * @see #syncPackages |
| */ |
| private final @NonNull ArrayList<OpToChange> mOpsToForeground = new ArrayList<>(); |
| |
| /** |
| * All ops that need to be flipped to foreground if allow. |
| * |
| * Currently, only used by the foreground/background permissions logic. |
| * |
| * @see #syncPackages |
| */ |
| private final @NonNull ArrayList<OpToChange> mOpsToForegroundIfAllow = |
| new ArrayList<>(); |
| |
| PermissionToOpSynchroniser(@NonNull Context context) { |
| mContext = context; |
| mPackageManager = context.getPackageManager(); |
| mAppOpsManager = context.getSystemService(AppOpsManager.class); |
| } |
| |
| /** |
| * Set app ops that were added in {@link #addPackage}. |
| * |
| * <p>This processes ops previously added by {@link #addOpIfRestricted} |
| */ |
| private void syncPackages() { |
| // Remember which ops were already set. This makes sure that we always set the most |
| // permissive mode if two OpChanges are scheduled. This can e.g. happen if two |
| // permissions change the same op. See {@link #getSwitchOp}. |
| LongSparseLongArray alreadySetAppOps = new LongSparseLongArray(); |
| |
| final int allowCount = mOpsToAllow.size(); |
| for (int i = 0; i < allowCount; i++) { |
| final OpToChange op = mOpsToAllow.get(i); |
| |
| setUidModeAllowed(op.code, op.uid, op.packageName); |
| alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); |
| } |
| |
| final int allowIfDefaultCount = mOpsToAllowIfDefault.size(); |
| for (int i = 0; i < allowIfDefaultCount; i++) { |
| final OpToChange op = mOpsToAllowIfDefault.get(i); |
| if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) { |
| continue; |
| } |
| |
| boolean wasSet = setUidModeAllowedIfDefault(op.code, op.uid, op.packageName); |
| if (wasSet) { |
| alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); |
| } |
| } |
| |
| final int foregroundIfAllowedCount = mOpsToForegroundIfAllow.size(); |
| for (int i = 0; i < foregroundIfAllowedCount; i++) { |
| final OpToChange op = mOpsToForegroundIfAllow.get(i); |
| if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) { |
| continue; |
| } |
| |
| boolean wasSet = setUidModeForegroundIfAllow(op.code, op.uid, op.packageName); |
| if (wasSet) { |
| alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); |
| } |
| } |
| |
| final int foregroundCount = mOpsToForeground.size(); |
| for (int i = 0; i < foregroundCount; i++) { |
| final OpToChange op = mOpsToForeground.get(i); |
| if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) { |
| continue; |
| } |
| |
| setUidModeForeground(op.code, op.uid, op.packageName); |
| alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); |
| } |
| |
| final int ignoreCount = mOpsToIgnore.size(); |
| for (int i = 0; i < ignoreCount; i++) { |
| final OpToChange op = mOpsToIgnore.get(i); |
| if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) { |
| continue; |
| } |
| |
| setUidModeIgnored(op.code, op.uid, op.packageName); |
| alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); |
| } |
| |
| final int ignoreIfDefaultCount = mOpsToIgnoreIfDefault.size(); |
| for (int i = 0; i < ignoreIfDefaultCount; i++) { |
| final OpToChange op = mOpsToIgnoreIfDefault.get(i); |
| if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) { |
| continue; |
| } |
| |
| boolean wasSet = setUidModeIgnoredIfDefault(op.code, op.uid, op.packageName); |
| if (wasSet) { |
| alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); |
| } |
| } |
| |
| final int defaultCount = mOpsToDefault.size(); |
| for (int i = 0; i < defaultCount; i++) { |
| final OpToChange op = mOpsToDefault.get(i); |
| if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) { |
| continue; |
| } |
| |
| setUidModeDefault(op.code, op.uid, op.packageName); |
| alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); |
| } |
| } |
| |
| /** |
| * Add op that belong to a restricted permission for later processing in |
| * {@link #syncPackages()}. |
| * |
| * <p>Note: Called with the package lock held. Do <u>not</u> call into app-op manager. |
| * |
| * @param permissionInfo The permission that is currently looked at |
| * @param pkg The package looked at |
| */ |
| private void addOpIfRestricted(@NonNull PermissionInfo permissionInfo, |
| @NonNull PackageInfo pkg) { |
| final String permission = permissionInfo.name; |
| final int opCode = getSwitchOp(permission); |
| final int uid = pkg.applicationInfo.uid; |
| |
| if (!permissionInfo.isRestricted()) { |
| return; |
| } |
| |
| final boolean applyRestriction = |
| (mPackageManager.getPermissionFlags(permission, pkg.packageName, |
| mContext.getUser()) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; |
| |
| if (permissionInfo.isHardRestricted()) { |
| if (opCode != OP_NONE) { |
| if (applyRestriction) { |
| mOpsToDefault.add(new OpToChange(uid, pkg.packageName, opCode)); |
| } else { |
| mOpsToAllowIfDefault.add(new OpToChange(uid, pkg.packageName, opCode)); |
| } |
| } |
| } else if (permissionInfo.isSoftRestricted()) { |
| final SoftRestrictedPermissionPolicy policy = |
| SoftRestrictedPermissionPolicy.forPermission(mContext, pkg.applicationInfo, |
| mContext.getUser(), permission); |
| |
| if (opCode != OP_NONE) { |
| if (policy.canBeGranted()) { |
| mOpsToAllowIfDefault.add(new OpToChange(uid, pkg.packageName, opCode)); |
| } else { |
| mOpsToDefault.add(new OpToChange(uid, pkg.packageName, opCode)); |
| } |
| } |
| |
| final int op = policy.resolveAppOp(); |
| if (op != OP_NONE) { |
| switch (policy.getDesiredOpMode()) { |
| case MODE_DEFAULT: |
| mOpsToDefault.add(new OpToChange(uid, pkg.packageName, op)); |
| break; |
| case MODE_ALLOWED: |
| if (policy.shouldSetAppOpIfNotDefault()) { |
| mOpsToAllow.add(new OpToChange(uid, pkg.packageName, op)); |
| } else { |
| mOpsToAllowIfDefault.add( |
| new OpToChange(uid, pkg.packageName, op)); |
| } |
| break; |
| case MODE_FOREGROUND: |
| Slog.wtf(LOG_TAG, |
| "Setting appop to foreground is not implemented"); |
| break; |
| case MODE_IGNORED: |
| if (policy.shouldSetAppOpIfNotDefault()) { |
| mOpsToIgnore.add(new OpToChange(uid, pkg.packageName, op)); |
| } else { |
| mOpsToIgnoreIfDefault.add( |
| new OpToChange(uid, pkg.packageName, |
| op)); |
| } |
| break; |
| case MODE_ERRORED: |
| Slog.wtf(LOG_TAG, "Setting appop to errored is not implemented"); |
| } |
| } |
| } |
| } |
| |
| private boolean isBgPermRestricted(@NonNull String pkg, @NonNull String perm, int uid) { |
| try { |
| final PermissionInfo bgPermInfo = mPackageManager.getPermissionInfo(perm, 0); |
| |
| if (bgPermInfo.isSoftRestricted()) { |
| Slog.wtf(LOG_TAG, "Support for soft restricted background permissions not " |
| + "implemented"); |
| } |
| |
| return bgPermInfo.isHardRestricted() && (mPackageManager.getPermissionFlags( |
| perm, pkg, UserHandle.getUserHandleForUid(uid)) |
| & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; |
| } catch (NameNotFoundException e) { |
| Slog.w(LOG_TAG, "Cannot read permission state of " + perm, e); |
| return false; |
| } |
| } |
| |
| /** |
| * Add op that belong to a foreground permission for later processing in |
| * {@link #syncPackages()}. |
| * |
| * <p>Note: Called with the package lock held. Do <u>not</u> call into app-op manager. |
| * |
| * @param permissionInfo The permission that is currently looked at |
| * @param pkg The package looked at |
| */ |
| private void addOpIfFgPermissions(@NonNull PermissionInfo permissionInfo, |
| @NonNull PackageInfo pkg) { |
| final String bgPermissionName = permissionInfo.backgroundPermission; |
| |
| if (bgPermissionName == null) { |
| return; |
| } |
| |
| final String permission = permissionInfo.name; |
| final int opCode = getSwitchOp(permission); |
| final String pkgName = pkg.packageName; |
| final int uid = pkg.applicationInfo.uid; |
| |
| // App does not support runtime permissions. Hence the state is encoded in the app-op. |
| // To not override unrecoverable state don't change app-op unless bg perm is reviewed. |
| if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { |
| // If the review is required for this permission, the grant state does not |
| // really matter. To have a stable state, don't change the app-op if review is still |
| // pending. |
| int flags = mPackageManager.getPermissionFlags(bgPermissionName, |
| pkg.packageName, UserHandle.getUserHandleForUid(uid)); |
| |
| if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0 |
| && isBgPermRestricted(pkgName, bgPermissionName, uid)) { |
| mOpsToForegroundIfAllow.add(new OpToChange(uid, pkgName, opCode)); |
| } |
| |
| return; |
| } |
| |
| if (mPackageManager.checkPermission(permission, pkgName) |
| == PackageManager.PERMISSION_GRANTED) { |
| final boolean isBgHardRestricted = isBgPermRestricted(pkgName, bgPermissionName, |
| uid); |
| final boolean isBgPermGranted = mPackageManager.checkPermission(bgPermissionName, |
| pkgName) == PackageManager.PERMISSION_GRANTED; |
| |
| if (!isBgHardRestricted && isBgPermGranted) { |
| mOpsToAllow.add(new OpToChange(uid, pkgName, opCode)); |
| } else { |
| mOpsToForeground.add(new OpToChange(uid, pkgName, opCode)); |
| } |
| } else { |
| mOpsToIgnore.add(new OpToChange(uid, pkgName, opCode)); |
| } |
| } |
| |
| /** |
| * Add a package for {@link #syncPackages() processing} later. |
| * |
| * <p>Note: Called with the package lock held. Do <u>not</u> call into app-op manager. |
| * |
| * @param pkgName The package to add for later processing. |
| */ |
| void addPackage(@NonNull String pkgName) { |
| final PackageInfo pkg; |
| try { |
| pkg = mPackageManager.getPackageInfo(pkgName, GET_PERMISSIONS); |
| } catch (NameNotFoundException e) { |
| return; |
| } |
| |
| mAllUids.put(pkg.applicationInfo.uid, pkg.applicationInfo.uid); |
| |
| if (pkg.requestedPermissions == null) { |
| return; |
| } |
| |
| for (String permission : pkg.requestedPermissions) { |
| final int opCode = getSwitchOp(permission); |
| if (opCode == OP_NONE) { |
| continue; |
| } |
| |
| final PermissionInfo permissionInfo; |
| try { |
| permissionInfo = mPackageManager.getPermissionInfo(permission, 0); |
| } catch (PackageManager.NameNotFoundException e) { |
| continue; |
| } |
| |
| addOpIfRestricted(permissionInfo, pkg); |
| addOpIfFgPermissions(permissionInfo, pkg); |
| } |
| } |
| |
| private boolean setUidModeAllowedIfDefault(int opCode, int uid, |
| @NonNull String packageName) { |
| return setUidModeIfMode(opCode, uid, MODE_DEFAULT, MODE_ALLOWED, packageName); |
| } |
| |
| private void setUidModeAllowed(int opCode, int uid, @NonNull String packageName) { |
| setUidMode(opCode, uid, MODE_ALLOWED, packageName); |
| } |
| |
| private boolean setUidModeForegroundIfAllow(int opCode, int uid, |
| @NonNull String packageName) { |
| return setUidModeIfMode(opCode, uid, MODE_ALLOWED, MODE_FOREGROUND, packageName); |
| } |
| |
| private void setUidModeForeground(int opCode, int uid, @NonNull String packageName) { |
| setUidMode(opCode, uid, MODE_FOREGROUND, packageName); |
| } |
| |
| private boolean setUidModeIgnoredIfDefault(int opCode, int uid, |
| @NonNull String packageName) { |
| return setUidModeIfMode(opCode, uid, MODE_DEFAULT, MODE_IGNORED, packageName); |
| } |
| |
| private void setUidModeIgnored(int opCode, int uid, @NonNull String packageName) { |
| setUidMode(opCode, uid, MODE_IGNORED, packageName); |
| } |
| |
| private void setUidMode(int opCode, int uid, int mode, |
| @NonNull String packageName) { |
| final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager |
| .opToPublicName(opCode), uid, packageName); |
| |
| if (currentMode != mode) { |
| mAppOpsManager.setUidMode(opCode, uid, mode); |
| } |
| } |
| |
| private boolean setUidModeIfMode(int opCode, int uid, int requiredModeBefore, int newMode, |
| @NonNull String packageName) { |
| final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager |
| .opToPublicName(opCode), uid, packageName); |
| |
| if (currentMode == requiredModeBefore) { |
| mAppOpsManager.setUidMode(opCode, uid, newMode); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private void setUidModeDefault(int opCode, int uid, String packageName) { |
| setUidMode(opCode, uid, MODE_DEFAULT, packageName); |
| } |
| |
| private class OpToChange { |
| final int uid; |
| final @NonNull String packageName; |
| final int code; |
| |
| OpToChange(int uid, @NonNull String packageName, int code) { |
| this.uid = uid; |
| this.packageName = packageName; |
| this.code = code; |
| } |
| } |
| } |
| |
| private class Internal extends PermissionPolicyInternal { |
| |
| @Override |
| public boolean checkStartActivity(@NonNull Intent intent, int callingUid, |
| @Nullable String callingPackage) { |
| if (callingPackage != null && isActionRemovedForCallingPackage(intent, callingUid, |
| callingPackage)) { |
| Slog.w(LOG_TAG, "Action Removed: starting " + intent.toString() + " from " |
| + callingPackage + " (uid=" + callingUid + ")"); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Check if the intent action is removed for the calling package (often based on target SDK |
| * version). If the action is removed, we'll silently cancel the activity launch. |
| */ |
| private boolean isActionRemovedForCallingPackage(@NonNull Intent intent, int callingUid, |
| @NonNull String callingPackage) { |
| String action = intent.getAction(); |
| if (action == null) { |
| return false; |
| } |
| switch (action) { |
| case TelecomManager.ACTION_CHANGE_DEFAULT_DIALER: |
| case Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT: { |
| ApplicationInfo applicationInfo; |
| try { |
| applicationInfo = getContext().getPackageManager().getApplicationInfoAsUser( |
| callingPackage, 0, UserHandle.getUserId(callingUid)); |
| if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q) { |
| // Applications targeting Q or higher should use |
| // RoleManager.createRequestRoleIntent() instead. |
| return true; |
| } |
| } catch (PackageManager.NameNotFoundException e) { |
| Slog.i(LOG_TAG, "Cannot find application info for " + callingPackage); |
| } |
| // Make sure RequestRoleActivity can know the calling package if we allow it. |
| intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage); |
| return false; |
| } |
| default: |
| return false; |
| } |
| } |
| } |
| } |