blob: 3dc8e992444408433165cb80d6299fdb1be6bc82 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.permission;
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.IActivityManager;
import android.app.PropertyInvalidatedCache;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.annotations.Immutable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* System level service for accessing the permission capabilities of the platform.
*
* @hide
*/
@TestApi
@SystemApi
@SystemService(Context.PERMISSION_SERVICE)
public final class PermissionManager {
private static final String TAG = PermissionManager.class.getName();
/** @hide */
public static final String KILL_APP_REASON_PERMISSIONS_REVOKED =
"permissions revoked";
/** @hide */
public static final String KILL_APP_REASON_GIDS_CHANGED =
"permission grant or revoke changed gids";
private final @NonNull Context mContext;
private final IPackageManager mPackageManager;
private final IPermissionManager mPermissionManager;
private List<SplitPermissionInfo> mSplitPermissionInfos;
/**
* Creates a new instance.
*
* @param context The current context in which to operate.
* @hide
*/
public PermissionManager(@NonNull Context context, IPackageManager packageManager)
throws ServiceManager.ServiceNotFoundException {
this(context, packageManager, IPermissionManager.Stub.asInterface(
ServiceManager.getServiceOrThrow("permissionmgr")));
}
/**
* Creates a new instance with the provided instantiation of the IPermissionManager.
*
* @param context the current context in which to operate
* @param packageManager package manager service to be used for package related permission
* requests
* @param permissionManager injectable permission manager service
* @hide
*/
@VisibleForTesting
public PermissionManager(@NonNull Context context, IPackageManager packageManager,
IPermissionManager permissionManager) {
mContext = context;
mPackageManager = packageManager;
mPermissionManager = permissionManager;
}
/**
* Gets the version of the runtime permission database.
*
* @return The database version, -1 when this is an upgrade from pre-Q, 0 when this is a fresh
* install.
*
* @hide
*/
@TestApi
@SystemApi
@RequiresPermission(anyOf = {
Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS
})
public @IntRange(from = 0) int getRuntimePermissionsVersion() {
try {
return mPackageManager.getRuntimePermissionsVersion(mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Sets the version of the runtime permission database.
*
* @param version The new version.
*
* @hide
*/
@TestApi
@SystemApi
@RequiresPermission(anyOf = {
Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS
})
public void setRuntimePermissionsVersion(@IntRange(from = 0) int version) {
try {
mPackageManager.setRuntimePermissionsVersion(version, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Get set of permissions that have been split into more granular or dependent permissions.
*
* <p>E.g. before {@link android.os.Build.VERSION_CODES#Q} an app that was granted
* {@link Manifest.permission#ACCESS_COARSE_LOCATION} could access he location while it was in
* foreground and background. On platforms after {@link android.os.Build.VERSION_CODES#Q}
* the location permission only grants location access while the app is in foreground. This
* would break apps that target before {@link android.os.Build.VERSION_CODES#Q}. Hence whenever
* such an old app asks for a location permission (i.e. the
* {@link SplitPermissionInfo#getSplitPermission()}), then the
* {@link Manifest.permission#ACCESS_BACKGROUND_LOCATION} permission (inside
* {@link SplitPermissionInfo#getNewPermissions}) is added.
*
* <p>Note: Regular apps do not have to worry about this. The platform and permission controller
* automatically add the new permissions where needed.
*
* @return All permissions that are split.
*/
public @NonNull List<SplitPermissionInfo> getSplitPermissions() {
if (mSplitPermissionInfos != null) {
return mSplitPermissionInfos;
}
List<SplitPermissionInfoParcelable> parcelableList;
try {
parcelableList = ActivityThread.getPermissionManager().getSplitPermissions();
} catch (RemoteException e) {
Slog.e(TAG, "Error getting split permissions", e);
return Collections.emptyList();
}
mSplitPermissionInfos = splitPermissionInfoListToNonParcelableList(parcelableList);
return mSplitPermissionInfos;
}
/**
* Grant default permissions to currently active LUI app
* @param packageName The package name for the LUI app
* @param user The user handle
* @param executor The executor for the callback
* @param callback The callback provided by caller to be notified when grant completes
* @hide
*/
@RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
public void grantDefaultPermissionsToLuiApp(
@NonNull String packageName, @NonNull UserHandle user,
@NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
try {
mPermissionManager.grantDefaultPermissionsToActiveLuiApp(
packageName, user.getIdentifier());
executor.execute(() -> callback.accept(true));
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/**
* Revoke default permissions to currently active LUI app
* @param packageNames The package names for the LUI apps
* @param user The user handle
* @param executor The executor for the callback
* @param callback The callback provided by caller to be notified when grant completes
* @hide
*/
@RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
public void revokeDefaultPermissionsFromLuiApps(
@NonNull String[] packageNames, @NonNull UserHandle user,
@NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
try {
mPermissionManager.revokeDefaultPermissionsFromLuiApps(
packageNames, user.getIdentifier());
executor.execute(() -> callback.accept(true));
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/**
* Grant default permissions to currently active Ims services
* @param packageNames The package names for the Ims services
* @param user The user handle
* @param executor The executor for the callback
* @param callback The callback provided by caller to be notified when grant completes
* @hide
*/
@RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
public void grantDefaultPermissionsToEnabledImsServices(
@NonNull String[] packageNames, @NonNull UserHandle user,
@NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
try {
mPermissionManager.grantDefaultPermissionsToEnabledImsServices(
packageNames, user.getIdentifier());
executor.execute(() -> callback.accept(true));
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/**
* Grant default permissions to currently enabled telephony data services
* @param packageNames The package name for the services
* @param user The user handle
* @param executor The executor for the callback
* @param callback The callback provided by caller to be notified when grant completes
* @hide
*/
@RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
public void grantDefaultPermissionsToEnabledTelephonyDataServices(
@NonNull String[] packageNames, @NonNull UserHandle user,
@NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
try {
mPermissionManager.grantDefaultPermissionsToEnabledTelephonyDataServices(
packageNames, user.getIdentifier());
executor.execute(() -> callback.accept(true));
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/**
* Revoke default permissions to currently active telephony data services
* @param packageNames The package name for the services
* @param user The user handle
* @param executor The executor for the callback
* @param callback The callback provided by caller to be notified when revoke completes
* @hide
*/
@RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
public void revokeDefaultPermissionsFromDisabledTelephonyDataServices(
@NonNull String[] packageNames, @NonNull UserHandle user,
@NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
try {
mPermissionManager.revokeDefaultPermissionsFromDisabledTelephonyDataServices(
packageNames, user.getIdentifier());
executor.execute(() -> callback.accept(true));
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/**
* Grant default permissions to currently enabled carrier apps
* @param packageNames Package names of the apps to be granted permissions
* @param user The user handle
* @param executor The executor for the callback
* @param callback The callback provided by caller to be notified when grant completes
* @hide
*/
@RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
public void grantDefaultPermissionsToEnabledCarrierApps(@NonNull String[] packageNames,
@NonNull UserHandle user, @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<Boolean> callback) {
try {
mPermissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames,
user.getIdentifier());
executor.execute(() -> callback.accept(true));
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/**
* Gets the list of packages that have permissions that specified
* {@code requestDontAutoRevokePermissions=true} in their
* {@code application} manifest declaration.
*
* @return the list of packages for current user
* @hide
*/
@SystemApi
@NonNull
@RequiresPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY)
public Set<String> getAutoRevokeExemptionRequestedPackages() {
try {
return CollectionUtils.toSet(mPermissionManager.getAutoRevokeExemptionRequestedPackages(
mContext.getUser().getIdentifier()));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Gets the list of packages that have permissions that specified
* {@code allowDontAutoRevokePermissions=true} in their
* {@code application} manifest declaration.
*
* @return the list of packages for current user
* @hide
*/
@SystemApi
@NonNull
@RequiresPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY)
public Set<String> getAutoRevokeExemptionGrantedPackages() {
try {
return CollectionUtils.toSet(mPermissionManager.getAutoRevokeExemptionGrantedPackages(
mContext.getUser().getIdentifier()));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private List<SplitPermissionInfo> splitPermissionInfoListToNonParcelableList(
List<SplitPermissionInfoParcelable> parcelableList) {
final int size = parcelableList.size();
List<SplitPermissionInfo> list = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
list.add(new SplitPermissionInfo(parcelableList.get(i)));
}
return list;
}
/**
* Converts a {@link List} of {@link SplitPermissionInfo} into a List of
* {@link SplitPermissionInfoParcelable} and returns it.
* @hide
*/
public static List<SplitPermissionInfoParcelable> splitPermissionInfoListToParcelableList(
List<SplitPermissionInfo> splitPermissionsList) {
final int size = splitPermissionsList.size();
List<SplitPermissionInfoParcelable> outList = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
SplitPermissionInfo info = splitPermissionsList.get(i);
outList.add(new SplitPermissionInfoParcelable(
info.getSplitPermission(), info.getNewPermissions(), info.getTargetSdk()));
}
return outList;
}
/**
* A permission that was added in a previous API level might have split into several
* permissions. This object describes one such split.
*/
@Immutable
public static final class SplitPermissionInfo {
private @NonNull final SplitPermissionInfoParcelable mSplitPermissionInfoParcelable;
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SplitPermissionInfo that = (SplitPermissionInfo) o;
return mSplitPermissionInfoParcelable.equals(that.mSplitPermissionInfoParcelable);
}
@Override
public int hashCode() {
return mSplitPermissionInfoParcelable.hashCode();
}
/**
* Get the permission that is split.
*/
public @NonNull String getSplitPermission() {
return mSplitPermissionInfoParcelable.getSplitPermission();
}
/**
* Get the permissions that are added.
*/
public @NonNull List<String> getNewPermissions() {
return mSplitPermissionInfoParcelable.getNewPermissions();
}
/**
* Get the target API level when the permission was split.
*/
public int getTargetSdk() {
return mSplitPermissionInfoParcelable.getTargetSdk();
}
/**
* Constructs a split permission.
*
* @param splitPerm old permission that will be split
* @param newPerms list of new permissions that {@code rootPerm} will be split into
* @param targetSdk apps targetting SDK versions below this will have {@code rootPerm}
* split into {@code newPerms}
* @hide
*/
public SplitPermissionInfo(@NonNull String splitPerm, @NonNull List<String> newPerms,
int targetSdk) {
this(new SplitPermissionInfoParcelable(splitPerm, newPerms, targetSdk));
}
private SplitPermissionInfo(@NonNull SplitPermissionInfoParcelable parcelable) {
mSplitPermissionInfoParcelable = parcelable;
}
}
/**
* Starts a one-time permission session for a given package. A one-time permission session is
* ended if app becomes inactive. Inactivity is defined as the package's uid importance level
* staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid
* importance level goes <= importanceToResetTimer then the timer is reset and doesn't start
* until going > importanceToResetTimer.
* <p>
* When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive
* then the session is extended until either the importance goes above
* importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which
* will continue the session and reset the timer.
* </p>
* <p>
* Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}.
* </p>
* <p>
* Once the session ends
* {@link PermissionControllerService#onOneTimePermissionSessionTimeout(String)} is invoked.
* </p>
* <p>
* Note that if there is currently an active session for a package a new one isn't created and
* the existing one isn't changed.
* </p>
* @param packageName The package to start a one-time permission session for
* @param timeoutMillis Number of milliseconds for an app to be in an inactive state
* @param importanceToResetTimer The least important level to uid must be to reset the timer
* @param importanceToKeepSessionAlive The least important level the uid must be to keep the
* session alive
*
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS)
public void startOneTimePermissionSession(@NonNull String packageName, long timeoutMillis,
@ActivityManager.RunningAppProcessInfo.Importance int importanceToResetTimer,
@ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) {
try {
mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(),
timeoutMillis, importanceToResetTimer, importanceToKeepSessionAlive);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/**
* Stops the one-time permission session for the package. The callback to the end of session is
* not invoked. If there is no one-time session for the package then nothing happens.
*
* @param packageName Package to stop the one-time permission session for
*
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS)
public void stopOneTimePermissionSession(@NonNull String packageName) {
try {
mPermissionManager.stopOneTimePermissionSession(packageName,
mContext.getUserId());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/**
* Checks whether the package with the given pid/uid can read device identifiers.
*
* @param packageName the name of the package to be checked for identifier access
* @param message the message to be used for logging during identifier access
* verification
* @param callingFeatureId the feature in the package
* @param pid the process id of the package to be checked
* @param uid the uid of the package to be checked
* @return {@link PackageManager#PERMISSION_GRANTED} if the package is allowed identifier
* access, {@link PackageManager#PERMISSION_DENIED} otherwise
* @hide
*/
@SystemApi
public int checkDeviceIdentifierAccess(@Nullable String packageName, @Nullable String message,
@Nullable String callingFeatureId, int pid, int uid) {
try {
return mPermissionManager.checkDeviceIdentifierAccess(packageName, message,
callingFeatureId, pid, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/* @hide */
private static int checkPermissionUncached(@Nullable String permission, int pid, int uid) {
final IActivityManager am = ActivityManager.getService();
if (am == null) {
// Well this is super awkward; we somehow don't have an active ActivityManager
// instance. If we're testing a root or system UID, then they totally have whatever
// permission this is.
final int appId = UserHandle.getAppId(uid);
if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " holds " + permission);
return PackageManager.PERMISSION_GRANTED;
}
Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " does not hold "
+ permission);
return PackageManager.PERMISSION_DENIED;
}
try {
return am.checkPermission(permission, pid, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Identifies a permission query.
*
* N.B. we include the checking pid for tracking purposes but don't include it in the equality
* comparison: we use only uid for the actual security check, so comparing pid would result
* in spurious misses.
*
* @hide
*/
@Immutable
private static final class PermissionQuery {
final String permission;
final int pid;
final int uid;
PermissionQuery(@Nullable String permission, int pid, int uid) {
this.permission = permission;
this.pid = pid;
this.uid = uid;
}
@Override
public String toString() {
return String.format("PermissionQuery(permission=\"%s\", pid=%s, uid=%s)",
permission, pid, uid);
}
@Override
public int hashCode() {
// N.B. pid doesn't count toward equality and therefore shouldn't count for
// hashing either.
int hash = Objects.hashCode(permission);
hash = hash * 13 + Objects.hashCode(uid);
return hash;
}
@Override
public boolean equals(Object rval) {
// N.B. pid doesn't count toward equality!
if (rval == null) {
return false;
}
PermissionQuery other;
try {
other = (PermissionQuery) rval;
} catch (ClassCastException ex) {
return false;
}
return uid == other.uid
&& Objects.equals(permission, other.permission);
}
}
/** @hide */
public static final String CACHE_KEY_PACKAGE_INFO = "cache_key.package_info";
/** @hide */
private static final PropertyInvalidatedCache<PermissionQuery, Integer> sPermissionCache =
new PropertyInvalidatedCache<PermissionQuery, Integer>(
16, CACHE_KEY_PACKAGE_INFO) {
@Override
protected Integer recompute(PermissionQuery query) {
return checkPermissionUncached(query.permission, query.pid, query.uid);
}
};
/** @hide */
public static int checkPermission(@Nullable String permission, int pid, int uid) {
return sPermissionCache.query(new PermissionQuery(permission, pid, uid));
}
/**
* Make checkPermission() above bypass the permission cache in this process.
*
* @hide
*/
public static void disablePermissionCache() {
sPermissionCache.disableLocal();
}
/**
* Like PermissionQuery, but for permission checks based on a package name instead of
* a UID.
*/
@Immutable
private static final class PackageNamePermissionQuery {
final String permName;
final String pkgName;
final int uid;
PackageNamePermissionQuery(@Nullable String permName, @Nullable String pkgName, int uid) {
this.permName = permName;
this.pkgName = pkgName;
this.uid = uid;
}
@Override
public String toString() {
return String.format(
"PackageNamePermissionQuery(pkgName=\"%s\", permName=\"%s, uid=%s\")",
pkgName, permName, uid);
}
@Override
public int hashCode() {
return Objects.hash(permName, pkgName, uid);
}
@Override
public boolean equals(Object rval) {
if (rval == null) {
return false;
}
PackageNamePermissionQuery other;
try {
other = (PackageNamePermissionQuery) rval;
} catch (ClassCastException ex) {
return false;
}
return Objects.equals(permName, other.permName)
&& Objects.equals(pkgName, other.pkgName)
&& uid == other.uid;
}
}
/* @hide */
private static int checkPackageNamePermissionUncached(
String permName, String pkgName, int uid) {
try {
return ActivityThread.getPermissionManager().checkPermission(
permName, pkgName, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/* @hide */
private static PropertyInvalidatedCache<PackageNamePermissionQuery, Integer>
sPackageNamePermissionCache =
new PropertyInvalidatedCache<PackageNamePermissionQuery, Integer>(
16, CACHE_KEY_PACKAGE_INFO) {
@Override
protected Integer recompute(PackageNamePermissionQuery query) {
return checkPackageNamePermissionUncached(
query.permName, query.pkgName, query.uid);
}
};
/**
* Check whether a package has a permission.
*
* @hide
*/
public static int checkPackageNamePermission(String permName, String pkgName, int uid) {
return sPackageNamePermissionCache.query(
new PackageNamePermissionQuery(permName, pkgName, uid));
}
/**
* Make checkPackageNamePermission() bypass the cache in this process.
*
* @hide
*/
public static void disablePackageNamePermissionCache() {
sPackageNamePermissionCache.disableLocal();
}
}