/*
|
* 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 com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback;
|
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();
|
|
/** Callbacks for when a user is initialized */
|
@GuardedBy("mLock")
|
private OnInitializedCallback mOnInitializedCallback;
|
|
/**
|
* 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);
|
|
final OnInitializedCallback callback;
|
|
synchronized (mLock) {
|
mIsStarted.put(userId, true);
|
callback = mOnInitializedCallback;
|
}
|
|
// Force synchronization as permissions might have changed
|
synchronizePermissionsAndAppOpsForUser(userId);
|
|
// Tell observers we are initialized for this user.
|
if (callback != null) {
|
callback.onInitialized(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);
|
if (packageManagerInternal.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;
|
}
|
|
@Override
|
public boolean isInitialized(int userId) {
|
return isStarted(userId);
|
}
|
|
@Override
|
public void setOnInitializedCallback(@NonNull OnInitializedCallback callback) {
|
synchronized (mLock) {
|
mOnInitializedCallback = callback;
|
}
|
}
|
|
/**
|
* 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;
|
}
|
}
|
}
|
}
|