Merge "[CDM perm sync] Use PC APIs instead of the local version of B/R"

This commit is contained in:
Jeff Sharkey
2022-06-23 18:50:33 +00:00
committed by Android (Google) Code Review
9 changed files with 15 additions and 4135 deletions

View File

@@ -24,8 +24,6 @@ import static android.content.ComponentName.createRelative;
import static com.android.server.companion.Utils.prepareForIpc;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
@@ -41,23 +39,17 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.permission.PermissionControllerManager;
import android.util.Slog;
import android.util.Xml;
import com.android.server.companion.AssociationStore;
import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.PermissionsUtils;
import com.android.server.companion.datatransfer.permbackup.BackupHelper;
import com.android.server.companion.proto.CompanionMessage;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* This processor builds user consent intent for a given SystemDataTransferRequest and processes the
@@ -83,6 +75,8 @@ public class SystemDataTransferProcessor {
private final AssociationStore mAssociationStore;
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
private final CompanionMessageProcessor mCompanionMessageProcessor;
private final PermissionControllerManager mPermissionControllerManager;
private final ExecutorService mExecutor;
public SystemDataTransferProcessor(CompanionDeviceManagerService service,
AssociationStore associationStore,
@@ -93,6 +87,8 @@ public class SystemDataTransferProcessor {
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
mCompanionMessageProcessor = companionMessageProcessor;
mCompanionMessageProcessor.setListener(this::onCompleteMessageReceived);
mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
mExecutor = Executors.newSingleThreadExecutor();
}
/**
@@ -180,23 +176,13 @@ public class SystemDataTransferProcessor {
// TODO: Establish a secure channel
final long callingIdentityToken = Binder.clearCallingIdentity();
// Start permission sync
final long callingIdentityToken = Binder.clearCallingIdentity();
try {
BackupHelper backupHelper = new BackupHelper(mContext, UserHandle.of(userId));
XmlSerializer serializer = Xml.newSerializer();
ByteArrayOutputStream backup = new ByteArrayOutputStream();
serializer.setOutput(backup, UTF_8.name());
backupHelper.writeState(serializer);
serializer.flush();
mCompanionMessageProcessor.paginateAndDispatchMessagesToApp(backup.toByteArray(),
CompanionMessage.PERMISSION_SYNC, packageName, userId, associationId);
} catch (IOException ioe) {
Slog.e(LOG_TAG, "Error while writing permission state.");
mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId),
mExecutor,
backup -> mCompanionMessageProcessor.paginateAndDispatchMessagesToApp(backup,
CompanionMessage.PERMISSION_SYNC, packageName, userId, associationId));
} finally {
Binder.restoreCallingIdentity(callingIdentityToken);
}
@@ -219,21 +205,11 @@ public class SystemDataTransferProcessor {
private void processPermissionSyncMessage(CompanionMessageInfo messageInfo) {
Slog.i(LOG_TAG, "Applying permissions.");
// Start applying permissions
UserHandle user = mContext.getUser();
final long callingIdentityToken = Binder.clearCallingIdentity();
try {
BackupHelper backupHelper = new BackupHelper(mContext, mContext.getUser());
XmlPullParser parser = Xml.newPullParser();
ByteArrayInputStream stream = new ByteArrayInputStream(
messageInfo.getData());
parser.setInput(stream, UTF_8.name());
backupHelper.restoreState(parser);
} catch (IOException e) {
Slog.e(LOG_TAG, "IOException reading message: "
+ new String(messageInfo.getData()));
} catch (XmlPullParserException e) {
Slog.e(LOG_TAG, "Error parsing message: "
+ new String(messageInfo.getData()));
mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(
messageInfo.getData(), user);
} finally {
Slog.i(LOG_TAG, "Permissions applied.");
Binder.restoreCallingIdentity(callingIdentityToken);

View File

@@ -1,782 +0,0 @@
/*
* Copyright (C) 2022 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.companion.datatransfer.permbackup;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.content.pm.SigningInfo;
import android.os.Build;
import android.os.UserHandle;
import android.permission.PermissionManager;
import android.permission.PermissionManager.SplitPermissionInfo;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import com.android.server.companion.datatransfer.permbackup.model.AppPermissionGroup;
import com.android.server.companion.datatransfer.permbackup.model.AppPermissions;
import com.android.server.companion.datatransfer.permbackup.model.Permission;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
/**
* Helper for creating and restoring permission backups.
*/
public class BackupHelper {
private static final String LOG_TAG = BackupHelper.class.getSimpleName();
private static final String TAG_PERMISSION_BACKUP = "perm-grant-backup";
private static final String ATTR_PLATFORM_VERSION = "version";
private static final String TAG_ALL_GRANTS = "rt-grants";
private static final String TAG_GRANT = "grant";
private static final String ATTR_PACKAGE_NAME = "pkg";
private static final String ATTR_HAS_MULTIPLE_SIGNERS = "multi-signers";
private static final String TAG_SIGNATURE = "sig";
private static final String ATTR_SIGNATURE_VALUE = "v";
private static final String TAG_PERMISSION = "perm";
private static final String ATTR_PERMISSION_NAME = "name";
private static final String ATTR_IS_GRANTED = "g";
private static final String ATTR_USER_SET = "set";
private static final String ATTR_USER_FIXED = "fixed";
private static final String ATTR_WAS_REVIEWED = "was-reviewed";
/** Flags of permissions to <u>not</u> back up */
private static final int SYSTEM_RUNTIME_GRANT_MASK = FLAG_PERMISSION_POLICY_FIXED
| FLAG_PERMISSION_SYSTEM_FIXED;
/** Make sure only one user can change the delayed permissions at a time */
private static final Object sLock = new Object();
private final Context mContext;
/**
* Create a new backup utils for a user.
*
* @param context A context to use
* @param user The user that is backed up / restored
*/
public BackupHelper(@NonNull Context context, @NonNull UserHandle user) {
try {
mContext = context.createPackageContextAsUser(context.getPackageName(), 0, user);
} catch (PackageManager.NameNotFoundException doesNotHappen) {
throw new IllegalStateException();
}
}
/**
* Forward parser and skip everything up to the end of the current tag.
*
* @param parser The parser to forward
*/
private static void skipToEndOfTag(@NonNull XmlPullParser parser)
throws IOException, XmlPullParserException {
int numOpenTags = 1;
while (numOpenTags > 0) {
switch (parser.next()) {
case START_TAG:
numOpenTags++;
break;
case END_TAG:
numOpenTags--;
break;
default:
// ignore
}
}
}
/**
* Forward parser to a given direct sub-tag.
*
* @param parser The parser to forward
* @param tag The tag to search for
*/
private void skipToTag(@NonNull XmlPullParser parser, @NonNull String tag)
throws IOException, XmlPullParserException {
int type;
do {
type = parser.next();
switch (type) {
case START_TAG:
if (!parser.getName().equals(tag)) {
skipToEndOfTag(parser);
}
return;
}
} while (type != END_DOCUMENT);
}
/**
* Read a XML file and return the packages stored in it.
*
* @param parser The file to read
*
* @return The packages in this file
*/
private @NonNull ArrayList<BackupPackageState> parseFromXml(@NonNull XmlPullParser parser)
throws IOException, XmlPullParserException {
ArrayList<BackupPackageState> pkgStates = new ArrayList<>();
skipToTag(parser, TAG_PERMISSION_BACKUP);
int backupPlatformVersion;
try {
backupPlatformVersion = Integer.parseInt(
parser.getAttributeValue(null, ATTR_PLATFORM_VERSION));
} catch (NumberFormatException ignored) {
// Platforms P and before did not store the platform version
backupPlatformVersion = Build.VERSION_CODES.P;
}
skipToTag(parser, TAG_ALL_GRANTS);
if (parser.getEventType() != START_TAG && !parser.getName().equals(TAG_ALL_GRANTS)) {
throw new XmlPullParserException("Could not find " + TAG_PERMISSION_BACKUP + " > "
+ TAG_ALL_GRANTS);
}
// Read packages to restore from xml
int type;
do {
type = parser.next();
switch (type) {
case START_TAG:
switch (parser.getName()) {
case TAG_GRANT:
try {
pkgStates.add(BackupPackageState.parseFromXml(parser, mContext,
backupPlatformVersion));
} catch (XmlPullParserException e) {
Log.e(LOG_TAG, "Could not parse permissions ", e);
skipToEndOfTag(parser);
}
break;
default:
// ignore tag
Log.w(LOG_TAG, "Found unexpected tag " + parser.getName()
+ " during restore");
skipToEndOfTag(parser);
}
}
} while (type != END_DOCUMENT);
return pkgStates;
}
/**
* Try to restore the permission state from XML.
*
* @param parser The xml to read
*/
public void restoreState(@NonNull XmlPullParser parser) throws IOException,
XmlPullParserException {
ArrayList<BackupPackageState> pkgStates = parseFromXml(parser);
ArrayList<BackupPackageState> packagesToRestoreLater = new ArrayList<>();
int numPkgStates = pkgStates.size();
if (numPkgStates > 0) {
// Try to restore packages
for (int i = 0; i < numPkgStates; i++) {
BackupPackageState pkgState = pkgStates.get(i);
PackageInfo pkgInfo;
try {
pkgInfo = mContext.getPackageManager().getPackageInfo(pkgState.mPackageName,
GET_PERMISSIONS | GET_SIGNING_CERTIFICATES);
} catch (PackageManager.NameNotFoundException ignored) {
packagesToRestoreLater.add(pkgState);
continue;
}
pkgState.restore(mContext, pkgInfo);
}
}
// synchronized (sLock) {
// writeDelayedStorePkgsLocked(packagesToRestoreLater);
// }
}
/**
* Write a xml file for the given packages.
*
* @param serializer The file to write to
* @param pkgs The packages to write
*/
private static void writePkgsAsXml(@NonNull XmlSerializer serializer,
@NonNull ArrayList<BackupPackageState> pkgs) throws IOException {
serializer.startDocument(null, true);
serializer.startTag(null, TAG_PERMISSION_BACKUP);
// if (SDK_INT >= Build.VERSION_CODES.Q) {
// STOPSHIP: Remove compatibility code once Q SDK level is declared
serializer.attribute(null, ATTR_PLATFORM_VERSION,
Integer.valueOf(Build.VERSION_CODES.Q).toString());
// } else {
// serializer.attribute(null, ATTR_PLATFORM_VERSION,
// Integer.valueOf(SDK_INT).toString());
// }
serializer.startTag(null, TAG_ALL_GRANTS);
int numPkgs = pkgs.size();
for (int i = 0; i < numPkgs; i++) {
BackupPackageState packageState = pkgs.get(i);
if (packageState != null) {
packageState.writeAsXml(serializer);
}
}
serializer.endTag(null, TAG_ALL_GRANTS);
serializer.endTag(null, TAG_PERMISSION_BACKUP);
serializer.endDocument();
}
/**
* Write the state of all packages as XML.
*
* @param serializer The xml to write to
*/
public void writeState(@NonNull XmlSerializer serializer) throws IOException {
List<PackageInfo> pkgs = mContext.getPackageManager().getInstalledPackages(
GET_PERMISSIONS | GET_SIGNING_CERTIFICATES);
ArrayList<BackupPackageState> backupPkgs = new ArrayList<>();
int numPkgs = pkgs.size();
for (int i = 0; i < numPkgs; i++) {
BackupPackageState packageState = BackupPackageState.fromAppPermissions(mContext,
pkgs.get(i));
if (packageState != null) {
backupPkgs.add(packageState);
}
}
writePkgsAsXml(serializer, backupPkgs);
}
/**
* State that needs to be backed up for a permission.
*/
private static class BackupPermissionState {
private final @NonNull String mPermissionName;
private final boolean mIsGranted;
private final boolean mIsUserSet;
private final boolean mIsUserFixed;
private final boolean mWasReviewed;
private BackupPermissionState(@NonNull String permissionName, boolean isGranted,
boolean isUserSet, boolean isUserFixed, boolean wasReviewed) {
mPermissionName = permissionName;
mIsGranted = isGranted;
mIsUserSet = isUserSet;
mIsUserFixed = isUserFixed;
mWasReviewed = wasReviewed;
}
/**
* Parse a package state from XML.
*
* @param parser The data to read
* @param context a context to use
* @param backupPlatformVersion The platform version the backup was created on
*
* @return The state
*/
static @NonNull List<BackupPermissionState> parseFromXml(@NonNull XmlPullParser parser,
@NonNull Context context, int backupPlatformVersion)
throws XmlPullParserException {
String permName = parser.getAttributeValue(null, ATTR_PERMISSION_NAME);
if (permName == null) {
throw new XmlPullParserException("Found " + TAG_PERMISSION + " without "
+ ATTR_PERMISSION_NAME);
}
ArrayList<String> expandedPermissions = new ArrayList<>();
expandedPermissions.add(permName);
List<SplitPermissionInfo> splitPerms = context.getSystemService(
PermissionManager.class).getSplitPermissions();
// Expand the properties to permissions that were split between the platform version the
// backup was taken and the current version.
int numSplitPerms = splitPerms.size();
for (int i = 0; i < numSplitPerms; i++) {
SplitPermissionInfo splitPerm = splitPerms.get(i);
if (backupPlatformVersion < splitPerm.getTargetSdk()
&& permName.equals(splitPerm.getSplitPermission())) {
expandedPermissions.addAll(splitPerm.getNewPermissions());
}
}
ArrayList<BackupPermissionState> parsedPermissions = new ArrayList<>(
expandedPermissions.size());
int numExpandedPerms = expandedPermissions.size();
for (int i = 0; i < numExpandedPerms; i++) {
parsedPermissions.add(new BackupPermissionState(expandedPermissions.get(i),
"true".equals(parser.getAttributeValue(null, ATTR_IS_GRANTED)),
"true".equals(parser.getAttributeValue(null, ATTR_USER_SET)),
"true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED)),
"true".equals(parser.getAttributeValue(null, ATTR_WAS_REVIEWED))));
}
return parsedPermissions;
}
/**
* Is the permission granted, also considering the app-op.
*
* <p>This does not consider the review-required state of the permission.
*
* @param perm The permission that might be granted
*
* @return {@code true} iff the permission and app-op is granted
*/
private static boolean isPermGrantedIncludingAppOp(@NonNull Permission perm) {
return perm.isGranted() && (!perm.affectsAppOp() || perm.isAppOpAllowed());
}
/**
* Get the state of a permission to back up.
*
* @param perm The permission to back up
* @param appSupportsRuntimePermissions If the app supports runtimePermissions
*
* @return The state to back up or {@code null} if the permission does not need to be
* backed up.
*/
private static @Nullable BackupPermissionState fromPermission(@NonNull Permission perm,
boolean appSupportsRuntimePermissions) {
int grantFlags = perm.getFlags();
if ((grantFlags & SYSTEM_RUNTIME_GRANT_MASK) != 0) {
return null;
}
if (!perm.isUserSet() && perm.isGrantedByDefault()) {
return null;
}
boolean permissionWasReviewed;
boolean isNotInDefaultGrantState;
if (appSupportsRuntimePermissions) {
isNotInDefaultGrantState = isPermGrantedIncludingAppOp(perm);
permissionWasReviewed = false;
} else {
isNotInDefaultGrantState = !isPermGrantedIncludingAppOp(perm);
permissionWasReviewed = !perm.isReviewRequired();
}
// if (isNotInDefaultGrantState || perm.isUserSet() || perm.isUserFixed()
// || permissionWasReviewed) {
// return new BackupPermissionState(perm.getName(),
// isPermGrantedIncludingAppOp(perm),
// perm.isUserSet(), perm.isUserFixed(), permissionWasReviewed);
// } else {
// return null;
// }
if (perm.isUserSet() && isPermGrantedIncludingAppOp(perm)) {
return new BackupPermissionState(perm.getName(), /* isGranted */ true,
/* isUserSet */ true, perm.isUserFixed(), permissionWasReviewed);
} else {
return null;
}
}
/**
* Get the states of all permissions of a group to back up.
*
* @param group The group of the permissions to back up
*
* @return The state to back up. Empty list if no permissions in the group need to be backed
* up
*/
static @NonNull ArrayList<BackupPermissionState> fromPermissionGroup(
@NonNull AppPermissionGroup group) {
ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>();
List<Permission> perms = group.getPermissions();
boolean appSupportsRuntimePermissions =
group.getApp().applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M;
int numPerms = perms.size();
for (int i = 0; i < numPerms; i++) {
BackupPermissionState permState = fromPermission(perms.get(i),
appSupportsRuntimePermissions);
if (permState != null) {
permissionsToRestore.add(permState);
}
}
return permissionsToRestore;
}
/**
* Write this state as XML.
*
* @param serializer The file to write to
*/
void writeAsXml(@NonNull XmlSerializer serializer) throws IOException {
serializer.startTag(null, TAG_PERMISSION);
serializer.attribute(null, ATTR_PERMISSION_NAME, mPermissionName);
if (mIsGranted) {
serializer.attribute(null, ATTR_IS_GRANTED, "true");
}
if (mIsUserSet) {
serializer.attribute(null, ATTR_USER_SET, "true");
}
if (mIsUserFixed) {
serializer.attribute(null, ATTR_USER_FIXED, "true");
}
if (mWasReviewed) {
serializer.attribute(null, ATTR_WAS_REVIEWED, "true");
}
serializer.endTag(null, TAG_PERMISSION);
}
/**
* Restore this permission state.
*
* @param appPerms The {@link AppPermissions} to restore the state to
* @param restoreBackgroundPerms if {@code true} only restore background permissions,
* if {@code false} do not restore background permissions
*/
void restore(@NonNull AppPermissions appPerms, boolean restoreBackgroundPerms) {
AppPermissionGroup group = appPerms.getGroupForPermission(mPermissionName);
if (group == null) {
Log.w(LOG_TAG, "Could not find group for " + mPermissionName + " in "
+ appPerms.getPackageInfo().packageName);
return;
}
if (restoreBackgroundPerms != group.isBackgroundGroup()) {
return;
}
Permission perm = group.getPermission(mPermissionName);
if (mWasReviewed) {
perm.unsetReviewRequired();
}
// Don't grant or revoke fixed permission groups
if (group.isSystemFixed() || group.isPolicyFixed()) {
return;
}
if (!perm.isUserSet()) {
if (mIsGranted) {
group.grantRuntimePermissions(false, mIsUserFixed,
new String[]{mPermissionName});
} else {
group.revokeRuntimePermissions(mIsUserFixed,
new String[]{mPermissionName});
}
perm.setUserSet(mIsUserSet);
}
}
}
/**
* State that needs to be backed up for a package.
*/
private static class BackupPackageState {
final @NonNull String mPackageName;
final boolean mHasMultipleSigners;
@NonNull Signature[] mSignatures;
private final @NonNull ArrayList<BackupPermissionState> mPermissionsToRestore;
private BackupPackageState(@NonNull String packageName, boolean hasMultipleSigners,
@NonNull Signature[] signatures,
@NonNull ArrayList<BackupPermissionState> permissionsToRestore) {
mPackageName = packageName;
mHasMultipleSigners = hasMultipleSigners;
mSignatures = signatures;
mPermissionsToRestore = permissionsToRestore;
}
/**
* Parse a package state from XML.
*
* @param parser The data to read
* @param context a context to use
* @param backupPlatformVersion The platform version the backup was created on
*
* @return The state
*/
static @NonNull BackupPackageState parseFromXml(@NonNull XmlPullParser parser,
@NonNull Context context, int backupPlatformVersion)
throws IOException, XmlPullParserException {
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
if (packageName == null) {
throw new XmlPullParserException("Found " + TAG_GRANT + " without "
+ ATTR_PACKAGE_NAME);
}
boolean hasMultipleSigners = Boolean.parseBoolean(
parser.getAttributeValue(null, ATTR_HAS_MULTIPLE_SIGNERS));
ArrayList<Signature> signatureList = new ArrayList<>();
ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>();
while (true) {
switch (parser.next()) {
case START_TAG:
switch (parser.getName()) {
case TAG_PERMISSION:
try {
permissionsToRestore.addAll(
BackupPermissionState.parseFromXml(parser, context,
backupPlatformVersion));
} catch (XmlPullParserException e) {
Log.e(LOG_TAG, "Could not parse permission for "
+ packageName, e);
}
skipToEndOfTag(parser);
break;
case TAG_SIGNATURE:
signatureList.add(new Signature(
parser.getAttributeValue(null, ATTR_SIGNATURE_VALUE)));
skipToEndOfTag(parser);
break;
default:
// ignore tag
Log.w(LOG_TAG, "Found unexpected tag " + parser.getName()
+ " while restoring " + packageName);
skipToEndOfTag(parser);
}
break;
case END_TAG:
Signature[] signatures = new Signature[signatureList.size()];
for (int i = 0; i < signatureList.size(); i++) {
signatures[i] = signatureList.get(i);
}
return new BackupPackageState(packageName, hasMultipleSigners, signatures,
permissionsToRestore);
case END_DOCUMENT:
throw new XmlPullParserException("Could not parse state for "
+ packageName);
}
}
}
/**
* Get the state of a package to back up.
*
* @param context A context to use
* @param pkgInfo The package to back up.
*
* @return The state to back up or {@code null} if no permission of the package need to be
* backed up.
*/
static @Nullable BackupPackageState fromAppPermissions(@NonNull Context context,
@NonNull PackageInfo pkgInfo) {
AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, null);
ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>();
List<AppPermissionGroup> groups = appPerms.getPermissionGroups();
// Check if the package has signatures
SigningInfo signingInfo = pkgInfo.signingInfo;
Signature[] signatures;
boolean hasMultipleSigners;
if (signingInfo.hasMultipleSigners()) {
hasMultipleSigners = true;
signatures = signingInfo.getApkContentsSigners();
} else {
hasMultipleSigners = false;
signatures = signingInfo.getSigningCertificateHistory();
}
if (signatures == null) {
Slog.d(LOG_TAG, "Skipping " + pkgInfo.packageName + ", it's unsigned.");
return null;
}
int numGroups = groups.size();
for (int groupNum = 0; groupNum < numGroups; groupNum++) {
AppPermissionGroup group = groups.get(groupNum);
permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup(group));
// Background permissions are in a subgroup that is not part of
// {@link AppPermission#getPermissionGroups}. Hence add it explicitly here.
if (group.getBackgroundPermissions() != null) {
permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup(
group.getBackgroundPermissions()));
}
}
if (permissionsToRestore.size() == 0) {
return null;
}
return new BackupPackageState(pkgInfo.packageName, hasMultipleSigners, signatures,
permissionsToRestore);
}
/**
* Write this state as XML.
*
* @param serializer The file to write to
*/
void writeAsXml(@NonNull XmlSerializer serializer) throws IOException {
if (mPermissionsToRestore.size() == 0) {
return;
}
serializer.startTag(null, TAG_GRANT);
serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName);
// Add signing info
serializer.attribute(null, ATTR_HAS_MULTIPLE_SIGNERS,
String.valueOf(mHasMultipleSigners));
for (Signature signature : mSignatures) {
serializer.startTag(null, TAG_SIGNATURE);
serializer.attribute(null, ATTR_SIGNATURE_VALUE, signature.toCharsString());
serializer.endTag(null, TAG_SIGNATURE);
}
int numPerms = mPermissionsToRestore.size();
for (int i = 0; i < numPerms; i++) {
mPermissionsToRestore.get(i).writeAsXml(serializer);
}
serializer.endTag(null, TAG_GRANT);
}
/**
* Restore this package state.
*
* @param context A context to use
* @param pkgInfo The package to restore.
*/
void restore(@NonNull Context context, @NonNull PackageInfo pkgInfo) {
Slog.e(LOG_TAG, "Restoring permissions for package [" + mPackageName + "]");
// Verify signature info
try {
if (mHasMultipleSigners && pkgInfo.signingInfo.hasMultipleSigners()) {
// If both packages are signed by multi signers, check if two signature sets are
// effectively matched.
if (!Signature.areEffectiveMatch(mSignatures,
pkgInfo.signingInfo.getApkContentsSigners())) {
Slog.e(LOG_TAG, "Multi-signers signatures don't match for package ["
+ mPackageName + "], skipped.");
return;
}
} else if (!mHasMultipleSigners && !pkgInfo.signingInfo.hasMultipleSigners()) {
// If both packages are not signed by multi signers, check if two signature sets
// have overlaps.
Signature[] signatures = pkgInfo.signingInfo.getSigningCertificateHistory();
if (signatures == null) {
Slog.e(LOG_TAG, "The dest package is unsigned.");
return;
}
boolean isMatched = false;
for (int i = 0; i < mSignatures.length; i++) {
for (int j = 0; j < signatures.length; j++) {
isMatched = Signature.areEffectiveMatch(mSignatures[i], signatures[j]);
}
}
if (!isMatched) {
Slog.e(LOG_TAG, "Single signer signatures don't match for package ["
+ mPackageName + "], skipped.");
return;
}
} else {
Slog.e(LOG_TAG, "Number of signers don't match.");
return;
}
} catch (CertificateException ce) {
Slog.e(LOG_TAG, "Either the source or the dest package's bounced cert length "
+ "looks fishy, skipped package [" + pkgInfo.packageName + "]");
}
AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, true, null);
ArraySet<String> affectedPermissions = new ArraySet<>();
// Restore background permissions after foreground permissions as for pre-M apps bg
// granted and fg revoked cannot be expressed.
int numPerms = mPermissionsToRestore.size();
for (int i = 0; i < numPerms; i++) {
mPermissionsToRestore.get(i).restore(appPerms, false);
affectedPermissions.add(mPermissionsToRestore.get(i).mPermissionName);
}
for (int i = 0; i < numPerms; i++) {
mPermissionsToRestore.get(i).restore(appPerms, true);
}
int numGroups = appPerms.getPermissionGroups().size();
for (int i = 0; i < numGroups; i++) {
AppPermissionGroup group = appPerms.getPermissionGroups().get(i);
// Only denied groups can be user fixed
if (group.areRuntimePermissionsGranted()) {
group.setUserFixed(false);
}
AppPermissionGroup bgGroup = group.getBackgroundPermissions();
if (bgGroup != null) {
// Only denied groups can be user fixed
if (bgGroup.areRuntimePermissionsGranted()) {
bgGroup.setUserFixed(false);
}
}
}
appPerms.persistChanges(true, affectedPermissions);
}
}
}

View File

@@ -1,227 +0,0 @@
/*
* Copyright (C) 2022 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.companion.datatransfer.permbackup.model;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.ArrayMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* An app that requests permissions.
*
* <p>Allows to query all permission groups of the app and which permission belongs to which group.
*/
public final class AppPermissions {
/**
* All permission groups the app requests. Background permission groups are attached to their
* foreground groups.
*/
private final ArrayList<AppPermissionGroup> mGroups = new ArrayList<>();
/** Cache: group name -> group */
private final ArrayMap<String, AppPermissionGroup> mGroupNameToGroup = new ArrayMap<>();
/** Cache: permission name -> group. Might point to background group */
private final ArrayMap<String, AppPermissionGroup> mPermissionNameToGroup = new ArrayMap<>();
private final Context mContext;
private final CharSequence mAppLabel;
private final Runnable mOnErrorCallback;
private final boolean mSortGroups;
/** Do not actually commit changes to the platform until {@link #persistChanges} is called */
private final boolean mDelayChanges;
private PackageInfo mPackageInfo;
public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups,
Runnable onErrorCallback) {
this(context, packageInfo, sortGroups, false, onErrorCallback);
}
public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups,
boolean delayChanges, Runnable onErrorCallback) {
mContext = context;
mPackageInfo = packageInfo;
mAppLabel = null; // doesn't matter for CDM
mSortGroups = sortGroups;
mDelayChanges = delayChanges;
mOnErrorCallback = onErrorCallback;
loadPermissionGroups();
}
public PackageInfo getPackageInfo() {
return mPackageInfo;
}
/**
* Refresh package info and permission groups.
*/
public void refresh() {
loadPackageInfo();
loadPermissionGroups();
}
public CharSequence getAppLabel() {
return mAppLabel;
}
/**
* Get permission group by name.
*/
public AppPermissionGroup getPermissionGroup(String name) {
return mGroupNameToGroup.get(name);
}
public List<AppPermissionGroup> getPermissionGroups() {
return mGroups;
}
/**
* Check if the group is review required.
*/
public boolean isReviewRequired() {
final int groupCount = mGroups.size();
for (int i = 0; i < groupCount; i++) {
AppPermissionGroup group = mGroups.get(i);
if (group.isReviewRequired()) {
return true;
}
}
return false;
}
private void loadPackageInfo() {
try {
mPackageInfo = mContext.createPackageContextAsUser(mPackageInfo.packageName, 0,
UserHandle.getUserHandleForUid(mPackageInfo.applicationInfo.uid))
.getPackageManager().getPackageInfo(mPackageInfo.packageName,
PackageManager.GET_PERMISSIONS);
} catch (PackageManager.NameNotFoundException e) {
if (mOnErrorCallback != null) {
mOnErrorCallback.run();
}
}
}
/**
* Add all individual permissions of the {@code group} to the {@link #mPermissionNameToGroup}
* lookup table.
*
* @param group The group of permissions to add
*/
private void addAllPermissions(AppPermissionGroup group) {
ArrayList<Permission> perms = group.getPermissions();
int numPerms = perms.size();
for (int permNum = 0; permNum < numPerms; permNum++) {
mPermissionNameToGroup.put(perms.get(permNum).getName(), group);
}
}
private void loadPermissionGroups() {
mGroups.clear();
mGroupNameToGroup.clear();
mPermissionNameToGroup.clear();
if (mPackageInfo.requestedPermissions != null) {
for (String requestedPerm : mPackageInfo.requestedPermissions) {
if (getGroupForPermission(requestedPerm) == null) {
AppPermissionGroup group = AppPermissionGroup.create(mContext, mPackageInfo,
requestedPerm, mDelayChanges);
if (group == null) {
continue;
}
mGroups.add(group);
mGroupNameToGroup.put(group.getName(), group);
addAllPermissions(group);
AppPermissionGroup backgroundGroup = group.getBackgroundPermissions();
if (backgroundGroup != null) {
addAllPermissions(backgroundGroup);
}
}
}
if (mSortGroups) {
Collections.sort(mGroups);
}
}
}
/**
* Find the group a permission belongs to.
*
* <p>The group found might be a background group.
*
* @param permission The name of the permission
*
* @return The group the permission belongs to
*/
public AppPermissionGroup getGroupForPermission(String permission) {
return mPermissionNameToGroup.get(permission);
}
/**
* If the changes to the permission groups were delayed, persist them now.
*
* @param mayKillBecauseOfAppOpsChange If the app may be killed if app ops change. If this is
* set to {@code false} the caller has to make sure to kill
* the app if needed.
*/
public void persistChanges(boolean mayKillBecauseOfAppOpsChange) {
persistChanges(mayKillBecauseOfAppOpsChange, null);
}
/**
* If the changes to the permission groups were delayed, persist them now.
*
* @param mayKillBecauseOfAppOpsChange If the app may be killed if app ops change. If this is
* set to {@code false} the caller has to make sure to kill
* the app if needed.
* @param filterPermissions If provided, only persist state for the given permissions
*/
public void persistChanges(boolean mayKillBecauseOfAppOpsChange,
Set<String> filterPermissions) {
if (mDelayChanges) {
int numGroups = mGroups.size();
for (int i = 0; i < numGroups; i++) {
AppPermissionGroup group = mGroups.get(i);
group.persistChanges(mayKillBecauseOfAppOpsChange, null, filterPermissions);
AppPermissionGroup backgroundGroup = group.getBackgroundPermissions();
if (backgroundGroup != null) {
backgroundGroup.persistChanges(mayKillBecauseOfAppOpsChange, null,
filterPermissions);
}
}
}
}
}

View File

@@ -1,414 +0,0 @@
/*
* Copyright (C) 2022 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.companion.datatransfer.permbackup.model;
import android.annotation.NonNull;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import java.util.ArrayList;
import java.util.Objects;
/**
* A permission and its properties.
*
* @see AppPermissionGroup
*/
public final class Permission {
private final @NonNull PermissionInfo mPermissionInfo;
private final String mName;
private final String mBackgroundPermissionName;
private final String mAppOp;
private boolean mGranted;
private boolean mAppOpAllowed;
private int mFlags;
private boolean mIsEphemeral;
private boolean mIsRuntimeOnly;
private Permission mBackgroundPermission;
private ArrayList<Permission> mForegroundPermissions;
private boolean mWhitelisted;
public Permission(String name, @NonNull PermissionInfo permissionInfo, boolean granted,
String appOp, boolean appOpAllowed, int flags) {
mPermissionInfo = permissionInfo;
mName = name;
mBackgroundPermissionName = permissionInfo.backgroundPermission;
mGranted = granted;
mAppOp = appOp;
mAppOpAllowed = appOpAllowed;
mFlags = flags;
mIsEphemeral =
(permissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0;
mIsRuntimeOnly =
(permissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
}
/**
* Mark this permission as background permission for {@code foregroundPermissions}.
*
* @param foregroundPermission The foreground permission
*/
public void addForegroundPermissions(Permission foregroundPermission) {
if (mForegroundPermissions == null) {
mForegroundPermissions = new ArrayList<>(1);
}
mForegroundPermissions.add(foregroundPermission);
}
/**
* Mark this permission as foreground permission for {@code backgroundPermission}.
*
* @param backgroundPermission The background permission
*/
public void setBackgroundPermission(Permission backgroundPermission) {
mBackgroundPermission = backgroundPermission;
}
public PermissionInfo getPermissionInfo() {
return mPermissionInfo;
}
public String getName() {
return mName;
}
public String getAppOp() {
return mAppOp;
}
public int getFlags() {
return mFlags;
}
boolean isHardRestricted() {
return (mPermissionInfo.flags & PermissionInfo.FLAG_HARD_RESTRICTED) != 0;
}
boolean isSoftRestricted() {
return (mPermissionInfo.flags & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0;
}
/**
* Does this permission affect app ops.
*
* <p>I.e. does this permission have a matching app op or is this a background permission. All
* background permissions affect the app op of its assigned foreground permission.
*
* @return {@code true} if this permission affects app ops
*/
public boolean affectsAppOp() {
return mAppOp != null || isBackgroundPermission();
}
/**
* Check if the permission is granted.
*
* <p>This ignores the state of the app-op. I.e. for apps not handling runtime permissions, this
* always returns {@code true}.
*
* @return If the permission is granted
*/
public boolean isGranted() {
return mGranted;
}
/**
* Check if the permission is granted, also considering the state of the app-op.
*
* <p>For the UI, check the grant state of the whole group via
* {@link AppPermissionGroup#areRuntimePermissionsGranted}.
*
* @return {@code true} if the permission (and the app-op) is granted.
*/
public boolean isGrantedIncludingAppOp() {
return mGranted && (!affectsAppOp() || isAppOpAllowed()) && !isReviewRequired();
}
public boolean isReviewRequired() {
return (mFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
}
/**
* Unset review required flag.
*/
public void unsetReviewRequired() {
mFlags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
}
public void setGranted(boolean mGranted) {
this.mGranted = mGranted;
}
public boolean isAppOpAllowed() {
return mAppOpAllowed;
}
/**
* Check if it's user fixed.
*/
public boolean isUserFixed() {
return (mFlags & PackageManager.FLAG_PERMISSION_USER_FIXED) != 0;
}
/**
* Set user fixed flag.
*/
public void setUserFixed(boolean userFixed) {
if (userFixed) {
mFlags |= PackageManager.FLAG_PERMISSION_USER_FIXED;
} else {
mFlags &= ~PackageManager.FLAG_PERMISSION_USER_FIXED;
}
}
/**
* Sets the one-time permission flag
* @param oneTime true to set the flag, false to unset it
*/
public void setOneTime(boolean oneTime) {
if (oneTime) {
mFlags |= PackageManager.FLAG_PERMISSION_ONE_TIME;
} else {
mFlags &= ~PackageManager.FLAG_PERMISSION_ONE_TIME;
}
}
public boolean isSelectedLocationAccuracy() {
return (mFlags & PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY) != 0;
}
/**
* Sets the selected-location-accuracy permission flag
* @param selectedLocationAccuracy true to set the flag, false to unset it
*/
public void setSelectedLocationAccuracy(boolean selectedLocationAccuracy) {
if (selectedLocationAccuracy) {
mFlags |= PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
} else {
mFlags &= ~PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
}
}
public boolean isSystemFixed() {
return (mFlags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0;
}
public boolean isPolicyFixed() {
return (mFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
}
public boolean isUserSet() {
return (mFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
}
public boolean isGrantedByDefault() {
return (mFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0;
}
/**
* Is the permission user sensitive, i.e. should it always be shown to the user.
*
* <p>Non-sensitive permission are usually hidden behind a setting in an overflow menu or
* some other kind of flag.
*
* @return {@code true} if the permission is user sensitive.
*/
public boolean isUserSensitive() {
if (isGrantedIncludingAppOp()) {
return (mFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
} else {
return (mFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) != 0;
}
}
/**
* If this permission is split into a foreground and background permission, this is the name
* of the background permission.
*
* @return The name of the background permission or {@code null} if the permission is not split
*/
public String getBackgroundPermissionName() {
return mBackgroundPermissionName;
}
/**
* @return If this permission is split into a foreground and background permission,
* returns the background permission
*/
public Permission getBackgroundPermission() {
return mBackgroundPermission;
}
/**
* @return If this permission is split into a foreground and background permission,
* returns the foreground permission
*/
public ArrayList<Permission> getForegroundPermissions() {
return mForegroundPermissions;
}
/**
* @return {@code true} iff this is the foreground permission of a background-foreground-split
* permission
*/
public boolean hasBackgroundPermission() {
return mBackgroundPermissionName != null;
}
/**
* @return {@code true} iff this is the background permission of a background-foreground-split
* permission
*/
public boolean isBackgroundPermission() {
return mForegroundPermissions != null;
}
/**
* @see PackageManager#FLAG_PERMISSION_ONE_TIME
*/
public boolean isOneTime() {
return (mFlags & PackageManager.FLAG_PERMISSION_ONE_TIME) != 0;
}
/**
* Set userSet flag.
*/
public void setUserSet(boolean userSet) {
if (userSet) {
mFlags |= PackageManager.FLAG_PERMISSION_USER_SET;
} else {
mFlags &= ~PackageManager.FLAG_PERMISSION_USER_SET;
}
}
/**
* Set policy fixed flag.
*/
public void setPolicyFixed(boolean policyFixed) {
if (policyFixed) {
mFlags |= PackageManager.FLAG_PERMISSION_POLICY_FIXED;
} else {
mFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED;
}
}
/**
* Check if the permission is revoke compat.
*/
public boolean isRevokedCompat() {
return (mFlags & PackageManager.FLAG_PERMISSION_REVOKED_COMPAT) != 0;
}
/**
* Set revoke compat flag.
*/
public void setRevokedCompat(boolean revokedCompat) {
if (revokedCompat) {
mFlags |= PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
} else {
mFlags &= ~PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
}
}
/**
* Set app op allowed flag.
*/
public void setAppOpAllowed(boolean mAppOpAllowed) {
this.mAppOpAllowed = mAppOpAllowed;
}
/**
* Check if it's ephemeral.
*/
public boolean isEphemeral() {
return mIsEphemeral;
}
/**
* Check if it's runtime only.
*/
public boolean isRuntimeOnly() {
return mIsRuntimeOnly;
}
/**
* Check if it's granting allowed.
*/
public boolean isGrantingAllowed(boolean isEphemeralApp, boolean supportsRuntimePermissions) {
return (!isEphemeralApp || isEphemeral())
&& (supportsRuntimePermissions || !isRuntimeOnly());
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Permission)) {
return false;
}
Permission other = (Permission) o;
if (!Objects.equals(getName(), other.getName()) || getFlags() != other.getFlags()
|| isGranted() != other.isGranted()) {
return false;
}
// Only compare permission names, in order to avoid recursion
if (getBackgroundPermission() != null && other.getBackgroundPermission() != null) {
if (!Objects.equals(getBackgroundPermissionName(),
other.getBackgroundPermissionName())) {
return false;
}
} else if (getBackgroundPermission() != other.getBackgroundPermission()) {
return false;
}
if (getForegroundPermissions() != null && other.getForegroundPermissions() != null) {
ArrayList<Permission> others = other.getForegroundPermissions();
if (getForegroundPermissions().size() != others.size()) {
return false;
}
for (int i = 0; i < others.size(); i++) {
if (!getForegroundPermissions().get(i).getName().equals(others.get(i).getName())) {
return false;
}
}
} else if (getForegroundPermissions() != null || other.getForegroundPermissions() != null) {
return false;
}
return Objects.equals(getAppOp(), other.getAppOp())
&& isAppOpAllowed() == other.isAppOpAllowed();
}
@Override
public int hashCode() {
ArrayList<String> linkedPermissionNames = new ArrayList<>();
if (mBackgroundPermission != null) {
linkedPermissionNames.add(mBackgroundPermission.getName());
}
if (mForegroundPermissions != null) {
for (Permission linkedPermission: mForegroundPermissions) {
if (linkedPermission != null) {
linkedPermissionNames.add(linkedPermission.getName());
}
}
}
return Objects.hash(mName, mFlags, mGranted, mAppOp, mAppOpAllowed, linkedPermissionNames);
}
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright (C) 2022 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.companion.datatransfer.permbackup.utils;
import android.annotation.Nullable;
import java.util.Objects;
/**
* Utils for array manipulation.
*/
public final class ArrayUtils {
private ArrayUtils() { /* cannot be instantiated */ }
/**
* Checks if an array is null or has no elements.
*
* @param array the array to check for
*
* @return whether the array is null or has no elements.
*/
public static <T> boolean isEmpty(@Nullable T[] array) {
return array == null || array.length == 0;
}
/**
* Checks that value is present as at least one of the elements of the array.
* @param array the array to check in
* @param value the value to check for
* @return true if the value is present in the array
*/
public static <T> boolean contains(T[] array, T value) {
return indexOf(array, value) != -1;
}
/**
* Return first index of {@code value} in {@code array}, or {@code -1} if
* not found.
*/
public static <T> int indexOf(T[] array, T value) {
if (array == null) return -1;
for (int i = 0; i < array.length; i++) {
if (Objects.equals(array[i], value)) return i;
}
return -1;
}
}

View File

@@ -1,135 +0,0 @@
/*
* Copyright (C) 2022 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.companion.datatransfer.permbackup.utils;
import static android.location.LocationManager.EXTRA_LOCATION_ENABLED;
import android.Manifest;
import android.annotation.NonNull;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.location.LocationManager;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import java.util.ArrayList;
/**
* Utils for location service.
*/
public class LocationUtils {
public static final String LOCATION_PERMISSION = Manifest.permission_group.LOCATION;
public static final String ACTIVITY_RECOGNITION_PERMISSION =
Manifest.permission_group.ACTIVITY_RECOGNITION;
private static final String TAG = LocationUtils.class.getSimpleName();
private static final long LOCATION_UPDATE_DELAY_MS = 1000;
private static final Handler sMainHandler = new Handler(Looper.getMainLooper());
/** Start the settings page for the location controller extra package. */
public static void startLocationControllerExtraPackageSettings(@NonNull Context context,
@NonNull UserHandle user) {
try {
context.startActivityAsUser(new Intent(
Settings.ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS), user);
} catch (ActivityNotFoundException e) {
// In rare cases where location controller extra package is set, but
// no activity exists to handle the location controller extra package settings
// intent, log an error instead of crashing permission controller.
Log.e(TAG, "No activity to handle "
+ "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS");
}
}
/**
* Check if location is enabled.
*/
public static boolean isLocationEnabled(Context context) {
return context.getSystemService(LocationManager.class).isLocationEnabled();
}
/** Checks if the provided package is a location provider. */
public static boolean isLocationProvider(Context context, String packageName) {
return context.getSystemService(LocationManager.class).isProviderPackage(packageName);
}
/**
* Check if group is location and the package is a location provider.
*/
public static boolean isLocationGroupAndProvider(Context context, String groupName,
String packageName) {
return LOCATION_PERMISSION.equals(groupName) && isLocationProvider(context, packageName);
}
/**
* Check if group is location and package is extra location controller.
*/
public static boolean isLocationGroupAndControllerExtraPackage(@NonNull Context context,
@NonNull String groupName, @NonNull String packageName) {
return (LOCATION_PERMISSION.equals(groupName)
|| ACTIVITY_RECOGNITION_PERMISSION.equals(groupName))
&& packageName.equals(context.getSystemService(LocationManager.class)
.getExtraLocationControllerPackage());
}
/** Returns whether the location controller extra package is enabled. */
public static boolean isExtraLocationControllerPackageEnabled(Context context) {
try {
return context.getSystemService(LocationManager.class)
.isExtraLocationControllerPackageEnabled();
} catch (Exception e) {
return false;
}
}
/**
* A Listener which responds to enabling or disabling of location on the device
*/
public interface LocationListener {
/**
* A callback run any time we receive a broadcast stating the location enable state has
* changed.
* @param enabled Whether or not location is enabled
*/
void onLocationStateChange(boolean enabled);
}
private static final ArrayList<LocationListener> sLocationListeners = new ArrayList<>();
private static BroadcastReceiver sLocationBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
boolean isEnabled = intent.getBooleanExtra(EXTRA_LOCATION_ENABLED, true);
sMainHandler.postDelayed(() -> {
synchronized (sLocationListeners) {
for (LocationListener l : sLocationListeners) {
l.onLocationStateChange(isEnabled);
}
}
}, LOCATION_UPDATE_DELAY_MS);
}
};
}

View File

@@ -1,84 +0,0 @@
/*
* Copyright (C) 2022 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.companion.datatransfer.permbackup.utils;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import android.annotation.NonNull;
import android.content.pm.PackageInfo;
import android.os.Build;
import com.android.server.companion.datatransfer.permbackup.model.Permission;
/**
* The behavior of soft restricted permissions is different for each permission. This class collects
* the policies in one place.
*
* This is the twin of {@link com.android.server.policy.SoftRestrictedPermissionPolicy}
*/
public abstract class SoftRestrictedPermissionPolicy {
/**
* Check if the permission should be shown in the UI.
*
* @param pkg the package the permission belongs to
* @param permission the permission
*
* @return {@code true} iff the permission should be shown in the UI.
*/
public static boolean shouldShow(@NonNull PackageInfo pkg, @NonNull Permission permission) {
switch (permission.getName()) {
case READ_EXTERNAL_STORAGE:
case WRITE_EXTERNAL_STORAGE: {
boolean isWhiteListed =
(permission.getFlags() & Utils.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)
!= 0;
int targetSDK = pkg.applicationInfo.targetSdkVersion;
return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
}
default:
return true;
}
}
/**
* Check if the permission should be shown in the UI.
*
* @param pkg the LightPackageInfo the permission belongs to
* @param permissionName the name of the permission
* @param permissionFlags the PermissionController flags (not the PermissionInfo flags) for
* the permission
*
* @return {@code true} iff the permission should be shown in the UI.
*/
public static boolean shouldShow(@NonNull PackageInfo pkg, @NonNull String permissionName,
int permissionFlags) {
switch (permissionName) {
case READ_EXTERNAL_STORAGE:
case WRITE_EXTERNAL_STORAGE: {
boolean isWhiteListed =
(permissionFlags & Utils.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
return isWhiteListed || pkg.applicationInfo.targetSdkVersion
>= Build.VERSION_CODES.Q;
}
default:
return true;
}
}
}

View File

@@ -1,819 +0,0 @@
/*
* Copyright (C) 2022 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.companion.datatransfer.permbackup.utils;
import static android.Manifest.permission_group.ACTIVITY_RECOGNITION;
import static android.Manifest.permission_group.CALENDAR;
import static android.Manifest.permission_group.CALL_LOG;
import static android.Manifest.permission_group.CAMERA;
import static android.Manifest.permission_group.CONTACTS;
import static android.Manifest.permission_group.LOCATION;
import static android.Manifest.permission_group.MICROPHONE;
import static android.Manifest.permission_group.NEARBY_DEVICES;
import static android.Manifest.permission_group.NOTIFICATIONS;
import static android.Manifest.permission_group.PHONE;
import static android.Manifest.permission_group.READ_MEDIA_AURAL;
import static android.Manifest.permission_group.READ_MEDIA_VISUAL;
import static android.Manifest.permission_group.SENSORS;
import static android.Manifest.permission_group.SMS;
import static android.Manifest.permission_group.STORAGE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.Application;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.hardware.SensorPrivacyManager;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import com.android.server.companion.datatransfer.permbackup.model.AppPermissionGroup;
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
/**
* Util class for BackupHelper
*/
public final class Utils {
@Retention(SOURCE)
@IntDef(value = {LAST_24H_SENSOR_TODAY, LAST_24H_SENSOR_YESTERDAY,
LAST_24H_CONTENT_PROVIDER, NOT_IN_LAST_7D})
public @interface AppPermsLastAccessType {}
public static final int LAST_24H_SENSOR_TODAY = 1;
public static final int LAST_24H_SENSOR_YESTERDAY = 2;
public static final int LAST_24H_CONTENT_PROVIDER = 3;
public static final int LAST_7D_SENSOR = 4;
public static final int LAST_7D_CONTENT_PROVIDER = 5;
public static final int NOT_IN_LAST_7D = 6;
private static final List<String> SENSOR_DATA_PERMISSIONS = List.of(
Manifest.permission_group.LOCATION,
Manifest.permission_group.CAMERA,
Manifest.permission_group.MICROPHONE
);
public static final List<String> STORAGE_SUPERGROUP_PERMISSIONS =
// (SDK_INT < Build.VERSION_CODES.TIRAMISU) ? List.of() :
List.of(
Manifest.permission_group.STORAGE,
Manifest.permission_group.READ_MEDIA_AURAL,
Manifest.permission_group.READ_MEDIA_VISUAL
);
private static final String LOG_TAG = "Utils";
public static final String OS_PKG = "android";
public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f;
/** The time an app needs to be unused in order to be hibernated */
public static final String PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS =
"auto_revoke_unused_threshold_millis2";
/** The frequency of running the job for hibernating apps */
public static final String PROPERTY_HIBERNATION_CHECK_FREQUENCY_MILLIS =
"auto_revoke_check_frequency_millis";
/** Whether hibernation targets apps that target a pre-S SDK */
public static final String PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS =
"app_hibernation_targets_pre_s_apps";
/** Whether or not app hibernation is enabled on the device **/
public static final String PROPERTY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled";
/** Whether to show the Permissions Hub. */
private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
/** The timeout for one-time permissions */
private static final String PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS =
"one_time_permissions_timeout_millis";
/** The delay before ending a one-time permission session when all processes are dead */
private static final String PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS =
"one_time_permissions_killed_delay_millis";
/** Whether to show location access check notifications. */
private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED =
"location_access_check_enabled";
/** The time an app needs to be unused in order to be hibernated */
public static final String PROPERTY_PERMISSION_DECISIONS_CHECK_OLD_FREQUENCY_MILLIS =
"permission_decisions_check_old_frequency_millis";
/** The time an app needs to be unused in order to be hibernated */
public static final String PROPERTY_PERMISSION_DECISIONS_MAX_DATA_AGE_MILLIS =
"permission_decisions_max_data_age_millis";
/** Whether or not warning banner is displayed when device sensors are off **/
public static final String PROPERTY_WARNING_BANNER_DISPLAY_ENABLED = "warning_banner_enabled";
/** All permission whitelists. */
public static final int FLAGS_PERMISSION_WHITELIST_ALL =
PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
| PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
| PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER;
/** All permission restriction exemptions. */
public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
| FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
| FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
/**
* The default length of the timeout for one-time permissions
*/
public static final long ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS = 1 * 60 * 1000; // 1 minute
/**
* The default length to wait before ending a one-time permission session after all processes
* are dead.
*/
public static final long ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS = 5 * 1000;
/** Mapping permission -> group for all dangerous platform permissions */
private static final ArrayMap<String, String> PLATFORM_PERMISSIONS;
/** Mapping group -> permissions for all dangerous platform permissions */
private static final ArrayMap<String, ArrayList<String>> PLATFORM_PERMISSION_GROUPS;
/** Set of groups that will be able to receive one-time grant */
private static final ArraySet<String> ONE_TIME_PERMISSION_GROUPS;
/** Permission -> Sensor codes */
private static final ArrayMap<String, Integer> PERM_SENSOR_CODES;
public static final int FLAGS_ALWAYS_USER_SENSITIVE =
FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
| FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
private static final String SYSTEM_PKG = "android";
private static final String SYSTEM_AMBIENT_AUDIO_INTELLIGENCE =
"android.app.role.SYSTEM_AMBIENT_AUDIO_INTELLIGENCE";
private static final String SYSTEM_UI_INTELLIGENCE =
"android.app.role.SYSTEM_UI_INTELLIGENCE";
private static final String SYSTEM_AUDIO_INTELLIGENCE =
"android.app.role.SYSTEM_AUDIO_INTELLIGENCE";
private static final String SYSTEM_NOTIFICATION_INTELLIGENCE =
"android.app.role.SYSTEM_NOTIFICATION_INTELLIGENCE";
private static final String SYSTEM_TEXT_INTELLIGENCE =
"android.app.role.SYSTEM_TEXT_INTELLIGENCE";
private static final String SYSTEM_VISUAL_INTELLIGENCE =
"android.app.role.SYSTEM_VISUAL_INTELLIGENCE";
// TODO: theianchen Using hardcoded values here as a WIP solution for now.
private static final String[] EXEMPTED_ROLES = {
SYSTEM_AMBIENT_AUDIO_INTELLIGENCE,
SYSTEM_UI_INTELLIGENCE,
SYSTEM_AUDIO_INTELLIGENCE,
SYSTEM_NOTIFICATION_INTELLIGENCE,
SYSTEM_TEXT_INTELLIGENCE,
SYSTEM_VISUAL_INTELLIGENCE,
};
static {
PLATFORM_PERMISSIONS = new ArrayMap<>();
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CONTACTS, CONTACTS);
PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CONTACTS, CONTACTS);
PLATFORM_PERMISSIONS.put(Manifest.permission.GET_ACCOUNTS, CONTACTS);
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALENDAR, CALENDAR);
PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALENDAR, CALENDAR);
PLATFORM_PERMISSIONS.put(Manifest.permission.SEND_SMS, SMS);
PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_SMS, SMS);
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_SMS, SMS);
PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_MMS, SMS);
PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_WAP_PUSH, SMS);
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CELL_BROADCASTS, SMS);
// If permissions are added to the Storage group, they must be added to the
// STORAGE_PERMISSIONS list in PermissionManagerService in frameworks/base
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_EXTERNAL_STORAGE, STORAGE);
PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, STORAGE);
// if (SDK_INT < Build.VERSION_CODES.TIRAMISU) {
// PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_MEDIA_LOCATION, STORAGE);
// }
// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_AUDIO, READ_MEDIA_AURAL);
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_IMAGES, READ_MEDIA_VISUAL);
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_VIDEO, READ_MEDIA_VISUAL);
PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_MEDIA_LOCATION, READ_MEDIA_VISUAL);
// }
PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION);
PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_COARSE_LOCATION, LOCATION);
PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_BACKGROUND_LOCATION, LOCATION);
// if (SDK_INT >= Build.VERSION_CODES.S) {
PLATFORM_PERMISSIONS.put(Manifest.permission.BLUETOOTH_ADVERTISE, NEARBY_DEVICES);
PLATFORM_PERMISSIONS.put(Manifest.permission.BLUETOOTH_CONNECT, NEARBY_DEVICES);
PLATFORM_PERMISSIONS.put(Manifest.permission.BLUETOOTH_SCAN, NEARBY_DEVICES);
PLATFORM_PERMISSIONS.put(Manifest.permission.UWB_RANGING, NEARBY_DEVICES);
// }
// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
PLATFORM_PERMISSIONS.put(Manifest.permission.NEARBY_WIFI_DEVICES, NEARBY_DEVICES);
// }
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALL_LOG, CALL_LOG);
PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALL_LOG, CALL_LOG);
PLATFORM_PERMISSIONS.put(Manifest.permission.PROCESS_OUTGOING_CALLS, CALL_LOG);
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_STATE, PHONE);
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_NUMBERS, PHONE);
PLATFORM_PERMISSIONS.put(Manifest.permission.CALL_PHONE, PHONE);
PLATFORM_PERMISSIONS.put(Manifest.permission.ADD_VOICEMAIL, PHONE);
PLATFORM_PERMISSIONS.put(Manifest.permission.USE_SIP, PHONE);
PLATFORM_PERMISSIONS.put(Manifest.permission.ANSWER_PHONE_CALLS, PHONE);
PLATFORM_PERMISSIONS.put(Manifest.permission.ACCEPT_HANDOVER, PHONE);
PLATFORM_PERMISSIONS.put(Manifest.permission.RECORD_AUDIO, MICROPHONE);
// if (SDK_INT >= Build.VERSION_CODES.S) {
PLATFORM_PERMISSIONS.put(Manifest.permission.RECORD_BACKGROUND_AUDIO, MICROPHONE);
// }
PLATFORM_PERMISSIONS.put(Manifest.permission.ACTIVITY_RECOGNITION, ACTIVITY_RECOGNITION);
PLATFORM_PERMISSIONS.put(Manifest.permission.CAMERA, CAMERA);
// if (SDK_INT >= Build.VERSION_CODES.S) {
PLATFORM_PERMISSIONS.put(Manifest.permission.BACKGROUND_CAMERA, CAMERA);
// }
PLATFORM_PERMISSIONS.put(Manifest.permission.BODY_SENSORS, SENSORS);
// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
PLATFORM_PERMISSIONS.put(Manifest.permission.POST_NOTIFICATIONS, NOTIFICATIONS);
PLATFORM_PERMISSIONS.put(Manifest.permission.BODY_SENSORS_BACKGROUND, SENSORS);
// }
PLATFORM_PERMISSION_GROUPS = new ArrayMap<>();
int numPlatformPermissions = PLATFORM_PERMISSIONS.size();
for (int i = 0; i < numPlatformPermissions; i++) {
String permission = PLATFORM_PERMISSIONS.keyAt(i);
String permissionGroup = PLATFORM_PERMISSIONS.valueAt(i);
ArrayList<String> permissionsOfThisGroup = PLATFORM_PERMISSION_GROUPS.get(
permissionGroup);
if (permissionsOfThisGroup == null) {
permissionsOfThisGroup = new ArrayList<>();
PLATFORM_PERMISSION_GROUPS.put(permissionGroup, permissionsOfThisGroup);
}
permissionsOfThisGroup.add(permission);
}
ONE_TIME_PERMISSION_GROUPS = new ArraySet<>();
ONE_TIME_PERMISSION_GROUPS.add(LOCATION);
ONE_TIME_PERMISSION_GROUPS.add(CAMERA);
ONE_TIME_PERMISSION_GROUPS.add(MICROPHONE);
PERM_SENSOR_CODES = new ArrayMap<>();
// if (SDK_INT >= Build.VERSION_CODES.S) {
PERM_SENSOR_CODES.put(CAMERA, SensorPrivacyManager.Sensors.CAMERA);
PERM_SENSOR_CODES.put(MICROPHONE, SensorPrivacyManager.Sensors.MICROPHONE);
// }
}
private Utils() {
/* do nothing - hide constructor */
}
private static ArrayMap<UserHandle, Context> sUserContexts = new ArrayMap<>();
/**
* Creates and caches a PackageContext for the requested user, or returns the previously cached
* value. The package of the PackageContext is the application's package.
*
* @param app The currently running application
* @param user The desired user for the context
*
* @return The generated or cached Context for the requested user
*
* @throws PackageManager.NameNotFoundException If the app has no package name attached
*/
public static @NonNull Context getUserContext(Application app, UserHandle user) throws
PackageManager.NameNotFoundException {
if (!sUserContexts.containsKey(user)) {
sUserContexts.put(user, app.getApplicationContext()
.createPackageContextAsUser(app.getPackageName(), 0, user));
}
return sUserContexts.get(user);
}
/**
* Returns true if a permission is dangerous, installed, and not removed
* @param permissionInfo The permission we wish to check
* @return If all of the conditions are met
*/
public static boolean isPermissionDangerousInstalledNotRemoved(PermissionInfo permissionInfo) {
return permissionInfo != null
&& permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS
&& (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0
&& (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0;
}
/**
* Get permission group a platform permission belongs to, or null if the permission is not a
* platform permission.
*
* @param permission the permission to resolve
*
* @return The group the permission belongs to
*/
public static @Nullable String getGroupOfPlatformPermission(@NonNull String permission) {
return PLATFORM_PERMISSIONS.get(permission);
}
/**
* Get name of the permission group a permission belongs to.
*
* @param permission the {@link PermissionInfo info} of the permission to resolve
*
* @return The group the permission belongs to
*/
public static @Nullable String getGroupOfPermission(@NonNull PermissionInfo permission) {
String groupName = Utils.getGroupOfPlatformPermission(permission.name);
if (groupName == null) {
groupName = permission.group;
}
return groupName;
}
/**
* Get the names for all platform permissions belonging to a group.
*
* @param group the group
*
* @return The permission names or an empty list if the
* group is not does not have platform runtime permissions
*/
public static @NonNull List<String> getPlatformPermissionNamesOfGroup(@NonNull String group) {
final ArrayList<String> permissions = PLATFORM_PERMISSION_GROUPS.get(group);
return (permissions != null) ? permissions : Collections.emptyList();
}
/**
* Get the {@link PermissionInfo infos} for all platform permissions belonging to a group.
*
* @param pm Package manager to use to resolve permission infos
* @param group the group
*
* @return The infos for platform permissions belonging to the group or an empty list if the
* group is not does not have platform runtime permissions
*/
public static @NonNull List<PermissionInfo> getPlatformPermissionsOfGroup(
@NonNull PackageManager pm, @NonNull String group) {
ArrayList<PermissionInfo> permInfos = new ArrayList<>();
ArrayList<String> permissions = PLATFORM_PERMISSION_GROUPS.get(group);
if (permissions == null) {
return Collections.emptyList();
}
int numPermissions = permissions.size();
for (int i = 0; i < numPermissions; i++) {
String permName = permissions.get(i);
PermissionInfo permInfo;
try {
permInfo = pm.getPermissionInfo(permName, 0);
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalStateException(permName + " not defined by platform", e);
}
permInfos.add(permInfo);
}
return permInfos;
}
/**
* Get the {@link PermissionInfo infos} for all permission infos belonging to a group.
*
* @param pm Package manager to use to resolve permission infos
* @param group the group
*
* @return The infos of permissions belonging to the group or an empty list if the group
* does not have runtime permissions
*/
public static @NonNull List<PermissionInfo> getPermissionInfosForGroup(
@NonNull PackageManager pm, @NonNull String group)
throws PackageManager.NameNotFoundException {
List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0);
permissions.addAll(getPlatformPermissionsOfGroup(pm, group));
/*
* If the undefined group is requested, the package manager will return all platform
* permissions, since they are marked as Undefined in the manifest. Do not return these
* permissions.
*/
if (group.equals(Manifest.permission_group.UNDEFINED)) {
List<PermissionInfo> undefinedPerms = new ArrayList<>();
for (PermissionInfo permissionInfo : permissions) {
String permGroup = getGroupOfPlatformPermission(permissionInfo.name);
if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) {
undefinedPerms.add(permissionInfo);
}
}
return undefinedPerms;
}
return permissions;
}
/**
* Get the {@link PermissionInfo infos} for all runtime installed permission infos belonging to
* a group.
*
* @param pm Package manager to use to resolve permission infos
* @param group the group
*
* @return The infos of installed runtime permissions belonging to the group or an empty list
* if the group does not have runtime permissions
*/
public static @NonNull List<PermissionInfo> getInstalledRuntimePermissionInfosForGroup(
@NonNull PackageManager pm, @NonNull String group)
throws PackageManager.NameNotFoundException {
List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0);
permissions.addAll(getPlatformPermissionsOfGroup(pm, group));
List<PermissionInfo> installedRuntime = new ArrayList<>();
for (PermissionInfo permissionInfo: permissions) {
if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS
&& (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0
&& (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0) {
installedRuntime.add(permissionInfo);
}
}
/*
* If the undefined group is requested, the package manager will return all platform
* permissions, since they are marked as Undefined in the manifest. Do not return these
* permissions.
*/
if (group.equals(Manifest.permission_group.UNDEFINED)) {
List<PermissionInfo> undefinedPerms = new ArrayList<>();
for (PermissionInfo permissionInfo : installedRuntime) {
String permGroup = getGroupOfPlatformPermission(permissionInfo.name);
if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) {
undefinedPerms.add(permissionInfo);
}
}
return undefinedPerms;
}
return installedRuntime;
}
/**
* Get the {@link PackageItemInfo infos} for the given permission group.
*
* @param groupName the group
* @param context the {@code Context} to retrieve {@code PackageManager}
*
* @return The info of permission group or null if the group does not have runtime permissions.
*/
public static @Nullable PackageItemInfo getGroupInfo(@NonNull String groupName,
@NonNull Context context) {
try {
return context.getPackageManager().getPermissionGroupInfo(groupName, 0);
} catch (NameNotFoundException e) {
/* ignore */
}
try {
return context.getPackageManager().getPermissionInfo(groupName, 0);
} catch (NameNotFoundException e) {
/* ignore */
}
return null;
}
/**
* Get the {@link PermissionInfo infos} for all permission infos belonging to a group.
*
* @param groupName the group
* @param context the {@code Context} to retrieve {@code PackageManager}
*
* @return The infos of permissions belonging to the group or null if the group does not have
* runtime permissions.
*/
public static @Nullable List<PermissionInfo> getGroupPermissionInfos(@NonNull String groupName,
@NonNull Context context) {
try {
return Utils.getPermissionInfosForGroup(context.getPackageManager(), groupName);
} catch (NameNotFoundException e) {
/* ignore */
}
try {
PermissionInfo permissionInfo = context.getPackageManager()
.getPermissionInfo(groupName, 0);
List<PermissionInfo> permissions = new ArrayList<>();
permissions.add(permissionInfo);
return permissions;
} catch (NameNotFoundException e) {
/* ignore */
}
return null;
}
/**
* Get the names of the platform permission groups.
*
* @return the names of the platform permission groups.
*/
public static List<String> getPlatformPermissionGroups() {
return new ArrayList<>(PLATFORM_PERMISSION_GROUPS.keySet());
}
/**
* Get the names of the runtime platform permissions
*
* @return the names of the runtime platform permissions.
*/
public static List<String> getRuntimePlatformPermissionNames() {
return new ArrayList<>(PLATFORM_PERMISSIONS.keySet());
}
/**
* Is the permissions a platform runtime permission
*
* @return the names of the runtime platform permissions.
*/
public static boolean isRuntimePlatformPermission(@NonNull String permission) {
return PLATFORM_PERMISSIONS.containsKey(permission);
}
/**
* Is the group or background group user sensitive?
*
* @param group The group that might be user sensitive
*
* @return {@code true} if the group (or it's subgroup) is user sensitive.
*/
public static boolean isGroupOrBgGroupUserSensitive(AppPermissionGroup group) {
return group.isUserSensitive() || (group.getBackgroundPermissions() != null
&& group.getBackgroundPermissions().isUserSensitive());
}
/**
* Whether or not the given package has non-isolated storage permissions
* @param context The current context
* @param packageName The package name to check
* @return True if the package has access to non-isolated storage, false otherwise
* @throws NameNotFoundException
*/
public static boolean isNonIsolatedStorage(@NonNull Context context,
@NonNull String packageName) throws NameNotFoundException {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
AppOpsManager manager = context.getSystemService(AppOpsManager.class);
return packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.P
|| (packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R
&& manager.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE,
packageInfo.applicationInfo.uid, packageInfo.packageName) == MODE_ALLOWED);
}
/**
* Build a string representing the given time if it happened on the current day and the date
* otherwise.
*
* @param context the context.
* @param lastAccessTime the time in milliseconds.
*
* @return a string representing the time or date of the given time or null if the time is 0.
*/
public static @Nullable String getAbsoluteTimeString(@NonNull Context context,
long lastAccessTime) {
if (lastAccessTime == 0) {
return null;
}
if (isToday(lastAccessTime)) {
return DateFormat.getTimeFormat(context).format(lastAccessTime);
} else {
return DateFormat.getMediumDateFormat(context).format(lastAccessTime);
}
}
/**
* Check whether the given time (in milliseconds) is in the current day.
*
* @param time the time in milliseconds
*
* @return whether the given time is in the current day.
*/
private static boolean isToday(long time) {
Calendar today = Calendar.getInstance(Locale.getDefault());
today.setTimeInMillis(System.currentTimeMillis());
today.set(Calendar.HOUR_OF_DAY, 0);
today.set(Calendar.MINUTE, 0);
today.set(Calendar.SECOND, 0);
today.set(Calendar.MILLISECOND, 0);
Calendar date = Calendar.getInstance(Locale.getDefault());
date.setTimeInMillis(time);
return !date.before(today);
}
/**
* Whether the Location Access Check is enabled.
*
* @return {@code true} iff the Location Access Check is enabled.
*/
public static boolean isLocationAccessCheckEnabled() {
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, true);
}
/**
* Get one time permissions timeout
*/
public static long getOneTimePermissionsTimeout() {
return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS, ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS);
}
/**
* Returns the delay in milliseconds before revoking permissions at the end of a one-time
* permission session if all processes have been killed.
* If the session was triggered by a self-revocation, then revocation should happen
* immediately. For a regular one-time permission session, a grace period allows a quick
* app restart without losing the permission.
* @param isSelfRevoked If true, return the delay for a self-revocation session. Otherwise,
* return delay for a regular one-time permission session.
*/
public static long getOneTimePermissionsKilledDelay(boolean isSelfRevoked) {
if (isSelfRevoked) {
// For a self-revoked session, we revoke immediately when the process dies.
return 0;
}
return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS,
ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS);
}
/**
* Whether the permission group supports one-time
* @param permissionGroup The permission group to check
* @return {@code true} iff the group supports one-time
*/
public static boolean supportsOneTimeGrant(String permissionGroup) {
return ONE_TIME_PERMISSION_GROUPS.contains(permissionGroup);
}
/**
* Checks whether a package has an active one-time permission according to the system server's
* flags
*
* @param context the {@code Context} to retrieve {@code PackageManager}
* @param packageName The package to check for
* @return Whether a package has an active one-time permission
*/
public static boolean hasOneTimePermissions(Context context, String packageName) {
String[] permissions;
PackageManager pm = context.getPackageManager();
try {
permissions = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
.requestedPermissions;
} catch (NameNotFoundException e) {
Log.w(LOG_TAG, "Checking for one-time permissions in nonexistent package");
return false;
}
if (permissions == null) {
return false;
}
for (String permissionName : permissions) {
if ((pm.getPermissionFlags(permissionName, packageName, Process.myUserHandle())
& PackageManager.FLAG_PERMISSION_ONE_TIME) != 0
&& pm.checkPermission(permissionName, packageName)
== PackageManager.PERMISSION_GRANTED) {
return true;
}
}
return false;
}
/**
* Gets the label of the Settings application
*
* @param pm The packageManager used to get the activity resolution
*
* @return The CharSequence title of the settings app
*/
@Nullable
public static CharSequence getSettingsLabelForNotifications(PackageManager pm) {
// We pretend we're the Settings app sending the notification, so figure out its name.
Intent openSettingsIntent = new Intent(Settings.ACTION_SETTINGS);
ResolveInfo resolveInfo = pm.resolveActivity(openSettingsIntent, MATCH_SYSTEM_ONLY);
if (resolveInfo == null) {
return null;
}
return pm.getApplicationLabel(resolveInfo.activityInfo.applicationInfo);
}
/**
* Get all the exempted packages.
*/
public static Set<String> getExemptedPackages(@NonNull RoleManager roleManager) {
Set<String> exemptedPackages = new HashSet<>();
exemptedPackages.add(SYSTEM_PKG);
for (int i = 0; i < EXEMPTED_ROLES.length; i++) {
exemptedPackages.addAll(roleManager.getRoleHolders(EXEMPTED_ROLES[i]));
}
return exemptedPackages;
}
/**
* Returns if the permission group is Camera or Microphone (status bar indicators).
**/
public static boolean isStatusBarIndicatorPermission(@NonNull String permissionGroupName) {
return CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName);
}
/**
* Navigate to notification settings for all apps
* @param context The current Context
*/
public static void navigateToNotificationSettings(@NonNull Context context) {
Intent notificationIntent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS);
context.startActivity(notificationIntent);
}
/**
* Navigate to notification settings for an app
* @param context The current Context
* @param packageName The package to navigate to
* @param user Specifies the user of the package which should be navigated to. If null, the
* current user is used.
*/
public static void navigateToAppNotificationSettings(@NonNull Context context,
@NonNull String packageName, @NonNull UserHandle user) {
Intent notificationIntent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
notificationIntent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
context.startActivityAsUser(notificationIntent, user);
}
/**
* Returns if a card should be shown if the sensor is blocked
**/
public static boolean shouldDisplayCardIfBlocked(@NonNull String permissionGroupName) {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_WARNING_BANNER_DISPLAY_ENABLED, true) && (
CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName)
|| LOCATION.equals(permissionGroupName));
}
}