[CDM perm sync] Use PC APIs instead of the local version of B/R
Bug: 231474219 Test: tested using the perm sync test app, verified it works end to end. Change-Id: I096dbf9d534b975f5121209bf6203d0f523b5156
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user