blob: a3215a47fd62d9f8c57f5148dd62f87f2b6c4e1f [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.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.annotations.Immutable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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 {
mContext = context;
mPackageManager = packageManager;
mPermissionManager = IPermissionManager.Stub.asInterface(
ServiceManager.getServiceOrThrow("permissionmgr"));
}
/**
* 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(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY)
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(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY)
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 callback The callback provided by caller to be notified when grant completes
* @hide
*/
@SystemApi
@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 callback The callback provided by caller to be notified when grant completes
* @hide
*/
@SystemApi
@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();
}
}
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(allOf = {Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
Manifest.permission.PACKAGE_USAGE_STATS})
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(allOf = {Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
Manifest.permission.PACKAGE_USAGE_STATS})
public void stopOneTimePermissionSession(@NonNull String packageName) {
try {
mPermissionManager.stopOneTimePermissionSession(packageName,
mContext.getUserId());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
}