summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PermissionController/res/values/themes.xml8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/BackupHelper.java369
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreference.java8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreferenceUtils.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/CollectionUtils.java41
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/model/AssistantRoleBehavior.java25
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/SafetyCenterReceiverTest.kt31
-rw-r--r--service/Android.bp1
8 files changed, 436 insertions, 53 deletions
diff --git a/PermissionController/res/values/themes.xml b/PermissionController/res/values/themes.xml
index fe74cf39b..5882c595d 100644
--- a/PermissionController/res/values/themes.xml
+++ b/PermissionController/res/values/themes.xml
@@ -57,8 +57,12 @@
<item name="android:windowIsTranslucent">true</item>
</style>
- <style name="GrantPermissions.Car">
- <item name="carUiActivity">true</item>
+ <style name="GrantPermissions.Car" parent="Theme.CarUi.NoToolbar">
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <!-- The following attributes change the behavior of the dialog, hence they should not be
+ themed -->
+ <item name="android:windowIsTranslucent">true</item>
</style>
<!-- Unused since R but exposed as overlayable. -->
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/BackupHelper.java b/PermissionController/src/com/android/permissioncontroller/permission/service/BackupHelper.java
index 0fc64480e..9082b6931 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/BackupHelper.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/BackupHelper.java
@@ -20,6 +20,7 @@ import static android.content.Context.MODE_PRIVATE;
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 android.util.Xml.newSerializer;
import static com.android.permissioncontroller.Constants.DELAYED_RESTORE_PERMISSIONS_FILE;
@@ -31,13 +32,17 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
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.Base64;
import android.util.Log;
import android.util.Xml;
@@ -49,6 +54,7 @@ import com.android.permissioncontroller.Constants;
import com.android.permissioncontroller.permission.model.AppPermissionGroup;
import com.android.permissioncontroller.permission.model.AppPermissions;
import com.android.permissioncontroller.permission.model.Permission;
+import com.android.permissioncontroller.permission.utils.CollectionUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -57,8 +63,13 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Helper for creating and restoring permission backups.
@@ -74,6 +85,11 @@ public class BackupHelper {
private static final String TAG_GRANT = "grant";
private static final String ATTR_PACKAGE_NAME = "pkg";
+ private static final String TAG_SIGNING_INFO = "sign";
+ private static final String TAG_CURRENT_CERTIFICATE = "curr-cert";
+ private static final String TAG_PAST_CERTIFICATE = "past-cert";
+ private static final String ATTR_CERTIFICATE_DIGEST = "digest";
+
private static final String TAG_PERMISSION = "perm";
private static final String ATTR_PERMISSION_NAME = "name";
private static final String ATTR_IS_GRANTED = "g";
@@ -228,12 +244,16 @@ public class BackupHelper {
PackageInfo pkgInfo;
try {
pkgInfo = mContext.getPackageManager().getPackageInfo(pkgState.mPackageName,
- GET_PERMISSIONS);
+ GET_PERMISSIONS | GET_SIGNING_CERTIFICATES);
} catch (PackageManager.NameNotFoundException ignored) {
packagesToRestoreLater.add(pkgState);
continue;
}
+ if (!checkCertificateDigestsMatch(pkgInfo, pkgState)) {
+ continue;
+ }
+
pkgState.restore(mContext, pkgInfo);
}
}
@@ -244,6 +264,56 @@ public class BackupHelper {
}
/**
+ * Returns whether the backed up package and the package being restored have compatible signing
+ * certificate digests.
+ *
+ * <p> Permissions should only be restored if the backed up package has the same signing
+ * certificate(s) or an ancestor (in the case of certification rotation).
+ *
+ * <p>If no certificates are found stored for the backed up package, we return true anyway as
+ * certificate storage does not exist before {@link Build.VERSION_CODES.TIRAMISU}.
+ */
+ private boolean checkCertificateDigestsMatch(
+ @NonNull PackageInfo packageToRestoreInfo,
+ @NonNull BackupPackageState backupPackageState) {
+ // No signing information was stored for the backed up app.
+ if (backupPackageState.mBackupSigningInfoState == null) {
+ return true;
+ }
+
+ // The backed up app was unsigned.
+ if (backupPackageState.mBackupSigningInfoState.mCurrentCertDigests.isEmpty()) {
+ return false;
+ }
+
+ // We don't have signing information for the restored app, but the backed up app was signed.
+ if (packageToRestoreInfo.signingInfo == null) {
+ return false;
+ }
+
+ // The restored app is unsigned.
+ if (packageToRestoreInfo.signingInfo.getApkContentsSigners() == null
+ || packageToRestoreInfo.signingInfo.getApkContentsSigners().length == 0) {
+ return false;
+ }
+
+ // If the restored app is a system app, we allow permissions to be restored without any
+ // certificate checks.
+ // System apps are signed with the device's platform certificate, so on
+ // different phones the same system app can have different certificates.
+ // We perform this check to be consistent with the Backup and Restore feature logic in
+ // frameworks/base/services/core/java/com/android/server/backup/BackupUtils.java
+ if ((packageToRestoreInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return true;
+ }
+
+ // Both backed up app and restored app have signing information, so we check that these are
+ // compatible for the purpose of restoring permissions to the restored app.
+ return hasCompatibleSignaturesForRestore(packageToRestoreInfo.signingInfo,
+ backupPackageState.mBackupSigningInfoState);
+ }
+
+ /**
* Write a xml file for the given packages.
*
* @param serializer The file to write to
@@ -308,7 +378,7 @@ public class BackupHelper {
*/
void writeState(@NonNull XmlSerializer serializer) throws IOException {
List<PackageInfo> pkgs = mContext.getPackageManager().getInstalledPackages(
- GET_PERMISSIONS);
+ GET_PERMISSIONS | GET_SIGNING_CERTIFICATES);
ArrayList<BackupPackageState> backupPkgs = new ArrayList<>();
int numPkgs = pkgs.size();
@@ -348,7 +418,8 @@ public class BackupHelper {
PackageInfo pkgInfo = null;
try {
- pkgInfo = mContext.getPackageManager().getPackageInfo(packageName, GET_PERMISSIONS);
+ pkgInfo = mContext.getPackageManager().getPackageInfo(
+ packageName, GET_PERMISSIONS | GET_SIGNING_CERTIFICATES);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Could not restore delayed permissions for " + packageName, e);
}
@@ -358,7 +429,8 @@ public class BackupHelper {
for (int i = 0; i < numPkgs; i++) {
BackupPackageState pkgState = packagesToRestoreLater.get(i);
- if (pkgState.mPackageName.equals(packageName)) {
+ if (pkgState.mPackageName.equals(packageName) && checkCertificateDigestsMatch(
+ pkgInfo, pkgState)) {
pkgState.restore(mContext, pkgInfo);
packagesToRestoreLater.remove(i);
@@ -377,7 +449,8 @@ public class BackupHelper {
* State that needs to be backed up for a permission.
*/
private static class BackupPermissionState {
- private final @NonNull String mPermissionName;
+ @NonNull
+ private final String mPermissionName;
private final boolean mIsGranted;
private final boolean mIsUserSet;
private final boolean mIsUserFixed;
@@ -401,7 +474,8 @@ public class BackupHelper {
*
* @return The state
*/
- static @NonNull List<BackupPermissionState> parseFromXml(@NonNull XmlPullParser parser,
+ @NonNull
+ static List<BackupPermissionState> parseFromXml(@NonNull XmlPullParser parser,
@NonNull Context context, int backupPlatformVersion)
throws XmlPullParserException {
String permName = parser.getAttributeValue(null, ATTR_PERMISSION_NAME);
@@ -463,7 +537,8 @@ public class BackupHelper {
* @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,
+ @Nullable
+ private static BackupPermissionState fromPermission(@NonNull Permission perm,
boolean appSupportsRuntimePermissions) {
int grantFlags = perm.getFlags();
@@ -502,7 +577,8 @@ public class BackupHelper {
* @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
+ static ArrayList<BackupPermissionState> fromPermissionGroup(
@NonNull AppPermissionGroup group) {
ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>();
List<Permission> perms = group.getPermissions();
@@ -594,17 +670,153 @@ public class BackupHelper {
}
}
+ /** Signing certificate information for a backed up package. */
+ private static class BackupSigningInfoState {
+ @NonNull
+ private final Set<byte[]> mCurrentCertDigests;
+ @NonNull
+ private final Set<byte[]> mPastCertDigests;
+
+ private BackupSigningInfoState(@NonNull Set<byte[]> currentCertDigests,
+ @NonNull Set<byte[]> pastCertDigests) {
+ mCurrentCertDigests = currentCertDigests;
+ mPastCertDigests = pastCertDigests;
+ }
+
+ /**
+ * Write this state as XML.
+ *
+ * @param serializer the file to write to
+ */
+ void writeAsXml(@NonNull XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, TAG_SIGNING_INFO);
+
+ for (byte[] digest : mCurrentCertDigests) {
+ serializer.startTag(null, TAG_CURRENT_CERTIFICATE);
+ serializer.attribute(
+ null, ATTR_CERTIFICATE_DIGEST,
+ Base64.encodeToString(digest, Base64.NO_WRAP));
+ serializer.endTag(null, TAG_CURRENT_CERTIFICATE);
+ }
+
+ for (byte[] digest : mPastCertDigests) {
+ serializer.startTag(null, TAG_PAST_CERTIFICATE);
+ serializer.attribute(
+ null, ATTR_CERTIFICATE_DIGEST,
+ Base64.encodeToString(digest, Base64.NO_WRAP));
+ serializer.endTag(null, TAG_PAST_CERTIFICATE);
+ }
+
+ serializer.endTag(null, TAG_SIGNING_INFO);
+ }
+
+ /**
+ * Parse the signing information state from XML.
+ *
+ * @param parser the data to read
+ *
+ * @return the signing information state
+ */
+ @NonNull
+ static BackupSigningInfoState parseFromXml(@NonNull XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ Set<byte[]> currentCertDigests = new HashSet<>();
+ Set<byte[]> pastCertDigests = new HashSet<>();
+
+ while (true) {
+ switch (parser.next()) {
+ case START_TAG:
+ switch (parser.getName()) {
+ case TAG_CURRENT_CERTIFICATE:
+ String currentCertDigest =
+ parser.getAttributeValue(
+ null, ATTR_CERTIFICATE_DIGEST);
+ if (currentCertDigest == null) {
+ throw new XmlPullParserException(
+ "Found " + TAG_CURRENT_CERTIFICATE + " without "
+ + ATTR_CERTIFICATE_DIGEST);
+ }
+ currentCertDigests.add(
+ Base64.decode(currentCertDigest, Base64.NO_WRAP));
+ skipToEndOfTag(parser);
+ break;
+ case TAG_PAST_CERTIFICATE:
+ String pastCertDigest =
+ parser.getAttributeValue(
+ null, ATTR_CERTIFICATE_DIGEST);
+ if (pastCertDigest == null) {
+ throw new XmlPullParserException(
+ "Found " + TAG_PAST_CERTIFICATE + " without "
+ + ATTR_CERTIFICATE_DIGEST);
+ }
+ pastCertDigests.add(
+ Base64.decode(pastCertDigest, Base64.NO_WRAP));
+ skipToEndOfTag(parser);
+ break;
+ default:
+ Log.w(LOG_TAG, "Found unexpected tag " + parser.getName());
+ skipToEndOfTag(parser);
+ }
+
+ break;
+ case END_TAG:
+ return new BackupSigningInfoState(
+ currentCertDigests,
+ pastCertDigests);
+ default:
+ throw new XmlPullParserException("Could not parse signing info");
+ }
+ }
+ }
+
+ /**
+ * Construct the signing information state from a {@link SigningInfo} instance.
+ *
+ * @param signingInfo the {@link SigningInfo} instance
+ *
+ * @return the state
+ */
+ @NonNull
+ static BackupSigningInfoState fromSigningInfo(@NonNull SigningInfo signingInfo) {
+ Set<byte[]> currentCertDigests = new HashSet<>();
+ Set<byte[]> pastCertDigests = new HashSet<>();
+
+ Signature[] apkContentsSigners = signingInfo.getApkContentsSigners();
+ for (int i = 0; i < apkContentsSigners.length; i++) {
+ currentCertDigests.add(
+ computeSha256DigestBytes(apkContentsSigners[i].toByteArray()));
+ }
+
+ if (signingInfo.hasPastSigningCertificates()) {
+ Signature[] signingCertificateHistory = signingInfo.getSigningCertificateHistory();
+ for (int i = 0; i < signingCertificateHistory.length; i++) {
+ pastCertDigests.add(
+ computeSha256DigestBytes(signingCertificateHistory[i].toByteArray()));
+ }
+ }
+
+ return new BackupSigningInfoState(currentCertDigests, pastCertDigests);
+ }
+ }
+
/**
* State that needs to be backed up for a package.
*/
private static class BackupPackageState {
- final @NonNull String mPackageName;
- private final @NonNull ArrayList<BackupPermissionState> mPermissionsToRestore;
-
- private BackupPackageState(@NonNull String packageName,
- @NonNull ArrayList<BackupPermissionState> permissionsToRestore) {
+ @NonNull
+ final String mPackageName;
+ @NonNull
+ private final ArrayList<BackupPermissionState> mPermissionsToRestore;
+ @Nullable
+ private final BackupSigningInfoState mBackupSigningInfoState;
+
+ private BackupPackageState(
+ @NonNull String packageName,
+ @NonNull ArrayList<BackupPermissionState> permissionsToRestore,
+ @Nullable BackupSigningInfoState backupSigningInfoState) {
mPackageName = packageName;
mPermissionsToRestore = permissionsToRestore;
+ mBackupSigningInfoState = backupSigningInfoState;
}
/**
@@ -616,7 +828,8 @@ public class BackupHelper {
*
* @return The state
*/
- static @NonNull BackupPackageState parseFromXml(@NonNull XmlPullParser parser,
+ @NonNull
+ static BackupPackageState parseFromXml(@NonNull XmlPullParser parser,
@NonNull Context context, int backupPlatformVersion)
throws IOException, XmlPullParserException {
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
@@ -626,6 +839,7 @@ public class BackupHelper {
}
ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>();
+ BackupSigningInfoState signingInfo = null;
while (true) {
switch (parser.next()) {
@@ -643,6 +857,16 @@ public class BackupHelper {
skipToEndOfTag(parser);
break;
+ case TAG_SIGNING_INFO:
+ try {
+ signingInfo = BackupSigningInfoState.parseFromXml(parser);
+ } catch (XmlPullParserException e) {
+ Log.e(LOG_TAG, "Could not parse signing info for "
+ + packageName, e);
+ skipToEndOfTag(parser);
+ }
+
+ break;
default:
// ignore tag
Log.w(LOG_TAG, "Found unexpected tag " + parser.getName()
@@ -652,7 +876,10 @@ public class BackupHelper {
break;
case END_TAG:
- return new BackupPackageState(packageName, permissionsToRestore);
+ return new BackupPackageState(
+ packageName,
+ permissionsToRestore,
+ signingInfo);
case END_DOCUMENT:
throw new XmlPullParserException("Could not parse state for "
+ packageName);
@@ -669,7 +896,8 @@ public class BackupHelper {
* @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,
+ @Nullable
+ static BackupPackageState fromAppPermissions(@NonNull Context context,
@NonNull PackageInfo pkgInfo) {
AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, null);
@@ -694,7 +922,14 @@ public class BackupHelper {
return null;
}
- return new BackupPackageState(pkgInfo.packageName, permissionsToRestore);
+ BackupSigningInfoState signingInfoState = null;
+
+ if (pkgInfo.signingInfo != null) {
+ signingInfoState = BackupSigningInfoState.fromSigningInfo(pkgInfo.signingInfo);
+ }
+
+ return new BackupPackageState(
+ pkgInfo.packageName, permissionsToRestore, signingInfoState);
}
/**
@@ -715,6 +950,10 @@ public class BackupHelper {
mPermissionsToRestore.get(i).writeAsXml(serializer);
}
+ if (mBackupSigningInfoState != null) {
+ mBackupSigningInfoState.writeAsXml(serializer);
+ }
+
serializer.endTag(null, TAG_GRANT);
}
@@ -760,4 +999,100 @@ public class BackupHelper {
appPerms.persistChanges(true, affectedPermissions);
}
}
+
+ /**
+ * Returns whether the signing certificates of the restored app and backed up app are
+ * compatible for the restored app to be granted the backed up app's permissions.
+ *
+ * <p>This returns true when any one of the following is true:
+ *
+ * <ul>
+ * <li> the backed up app has multiple signing certificates and the restored app
+ * has identical multiple signing certificates
+ * <li> the backed up app has a single signing certificate and it is the current
+ * single signing certificate of the restored app
+ * <li> the backed up app has a single signing certificate and it is present in the
+ * signing certificate history of the restored app
+ * <li> the backed up app has a single signing certificate and signing certificate
+ * history, and the signing certificate of the restored app is present in that history
+ * </ul>*
+ */
+ private boolean hasCompatibleSignaturesForRestore(@NonNull SigningInfo restoredSigningInfo,
+ @NonNull BackupSigningInfoState backupSigningInfoState) {
+ Set<byte[]> backupCertDigests = backupSigningInfoState.mCurrentCertDigests;
+ Set<byte[]> backupPastCertDigests = backupSigningInfoState.mPastCertDigests;
+ Signature[] restoredSignatures = restoredSigningInfo.getApkContentsSigners();
+
+ // Check that both apps have the same number of signing certificates. This will be a
+ // required check for both the single and multiple certificate cases.
+ if (backupCertDigests.size() != restoredSignatures.length) {
+ return false;
+ }
+
+ Set<byte[]> restoredCertDigests = new HashSet<>();
+ for (Signature signature: restoredSignatures) {
+ restoredCertDigests.add(computeSha256DigestBytes(signature.toByteArray()));
+ }
+
+ // If the backed up app has multiple signing certificates, the restored app should be
+ // signed by that exact set of multiple signing certificates.
+ if (backupCertDigests.size() > 1) {
+ // Check that the restored certificates are a subset of the backed up certificates.
+ if (!CollectionUtils.containsSubset(backupCertDigests, restoredCertDigests)) {
+ return false;
+ }
+ // Check that the backed up certificates are a subset of the restored certificates.
+ if (!CollectionUtils.containsSubset(restoredCertDigests, backupCertDigests)) {
+ return false;
+ }
+ return true;
+ }
+
+ // If both apps have a single signing certificate, we check if they are equal or if one
+ // app's certificate is in the signing certificate history of the other.
+ byte[] backupCertDigest = backupCertDigests.iterator().next();
+ byte[] restoredPastCertDigest = restoredCertDigests.iterator().next();
+
+ // Check if the backed up app and restored app have the same signing certificate.
+ if (Arrays.equals(backupCertDigest, restoredPastCertDigest)) {
+ return true;
+ }
+
+ // Check if the restored app's certificate is in the backed up app's signing certificate
+ // history.
+ if (CollectionUtils.contains(backupPastCertDigests, restoredPastCertDigest)) {
+ return true;
+ }
+
+ // Check if the backed up app's certificate is in the restored app's signing certificate
+ // history.
+ if (restoredSigningInfo.hasPastSigningCertificates()) {
+ // The last element in the pastSigningCertificates array is the current signer;
+ // since that was verified above, just check all the signers in the lineage.
+ for (int i = 0; i < restoredSigningInfo.getSigningCertificateHistory().length - 1;
+ i++) {
+ restoredPastCertDigest = computeSha256DigestBytes(
+ restoredSigningInfo.getSigningCertificateHistory()[i].toByteArray());
+ if (Arrays.equals(backupCertDigest, restoredPastCertDigest)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /** Computes the SHA256 digest of the provided {@code byte} array. */
+ @Nullable
+ private static byte[] computeSha256DigestBytes(@NonNull byte[] data) {
+ MessageDigest messageDigest;
+ try {
+ messageDigest = MessageDigest.getInstance("SHA256");
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ }
+
+ messageDigest.update(data);
+
+ return messageDigest.digest();
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreference.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreference.java
index b69fb66a7..4006bcfd3 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreference.java
@@ -44,8 +44,12 @@ class PermissionGroupPreference extends Preference {
setTitle(label);
setIcon(tintedIcon);
setIntent(managePgIntent);
- updateSummary(permissionGroupInfo.getNonSystemGranted(),
- permissionGroupInfo.getNonSystemUserSetOrPreGranted());
+ updateSummary(permissionGroupInfo);
+ }
+
+ void updateSummary(PermGroupPackagesUiInfo info) {
+ updateSummary(info.getNonSystemGranted(),
+ info.getNonSystemTotal());
}
void updateSummary(int granted, int used) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreferenceUtils.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreferenceUtils.java
index 49a01efaf..3541edead 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreferenceUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreferenceUtils.java
@@ -60,8 +60,7 @@ public final class PermissionGroupPreferenceUtils {
if (preference == null) {
preference = new PermissionGroupPreference(context, info);
} else {
- preference.updateSummary(info.getNonSystemGranted(),
- info.getNonSystemUserSetOrPreGranted());
+ preference.updateSummary(info);
// Reset the ordering back to default, so that when we add it back it falls into the
// right place, and the preferences are ordered as we add them.
preference.setOrder(Preference.DEFAULT_ORDER);
@@ -99,8 +98,7 @@ public final class PermissionGroupPreferenceUtils {
final PermissionGroupPreference preference =
(PermissionGroupPreference) preferenceGroup.getPreference(i);
final PermGroupPackagesUiInfo info = permissionGroups.get(i);
- preference.updateSummary(info.getNonSystemGranted(),
- info.getNonSystemUserSetOrPreGranted());
+ preference.updateSummary(info);
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/CollectionUtils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/CollectionUtils.java
index 4581f4e80..6f2a3079d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/CollectionUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/CollectionUtils.java
@@ -21,9 +21,11 @@ import android.util.ArraySet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
/**
* Utility methods for dealing with {@link java.util.Collection}s.
@@ -93,4 +95,43 @@ public final class CollectionUtils {
public static <T> List<T> singletonOrEmpty(@Nullable T element) {
return element != null ? Collections.singletonList(element) : Collections.emptyList();
}
+
+ /**
+ * Returns whether a byte array is contained within a {@link Set} of byte arrays. Equality is
+ * not compared by reference, but by comparing the elements contained in the arrays.
+ *
+ * @param byteArrays a {@link Set} of byte arrays that will be searched
+ * @param otherByteArray byte array to be searched
+ * @return {@code true} if {@code byteArrays} contains a byte array with identical elements as
+ * {@code otherByteArray}.
+ */
+ public static boolean contains(@NonNull Set<byte[]> byteArrays,
+ @NonNull byte[] otherByteArray) {
+ for (byte[] byteArray : byteArrays) {
+ if (Arrays.equals(byteArray, otherByteArray)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether a {@link Set} of byte arrays is contained within a {@link Set} of byte arrays
+ * as a subset. Equality for arrays is not compared by reference, but by comparing the elements
+ * contained in the arrays.
+ *
+ * @param byteArrays a {@link Set} of byte arrays which will be checked as a superset
+ * @param otherByteArrays a {@link Set} of byte arrays which be checked as a subset
+ * @return {@code true} if {@code byteArrays} contains all the arrays in {@code
+ * otherByteArrays}.
+ */
+ public static boolean containsSubset(
+ @NonNull Set<byte[]> byteArrays, @NonNull Set<byte[]> otherByteArrays) {
+ for (byte[] byteArray : otherByteArrays) {
+ if (!contains(byteArrays, byteArray)) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/AssistantRoleBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/model/AssistantRoleBehavior.java
index 66dd8ccb9..c8286f9ca 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/AssistantRoleBehavior.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/model/AssistantRoleBehavior.java
@@ -88,10 +88,8 @@ public class AssistantRoleBehavior implements RoleBehavior {
@Override
public Intent getManageIntentAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
- boolean isAutomotive =
- context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
- if (isAutomotive) {
+ if (isAutomotive(context)){
return null;
}
@@ -102,6 +100,19 @@ public class AssistantRoleBehavior implements RoleBehavior {
@Override
public CharSequence getConfirmationMessage(@NonNull Role role, @NonNull String packageName,
@NonNull Context context) {
+
+ // see (b/216746393) for details
+ if(isAutomotive(context)){
+ boolean contextEnabled = Settings.Secure.getInt(context.getContentResolver(),
+ "assist_structure_enabled", 1) != 0;
+ boolean screenshotEnabled = Settings.Secure.getInt(context.getContentResolver(),
+ "assist_screenshot_enabled", 1) != 0;
+
+ if(!contextEnabled && !screenshotEnabled){
+ return null;
+ }
+ }
+
return context.getString(R.string.assistant_confirmation_message);
}
@@ -186,6 +197,14 @@ public class AssistantRoleBehavior implements RoleBehavior {
public void revoke(@NonNull Role role, @NonNull String packageName, @NonNull Context context) {
}
+ /**
+ * Returns true if the device is an Automotive device
+ */
+ private boolean isAutomotive(@NonNull Context context) {
+ return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ }
+
+
private boolean isAssistantVoiceInteractionService(@NonNull PackageManager pm,
@NonNull ServiceInfo si) {
if (!android.Manifest.permission.BIND_VOICE_INTERACTION.equals(si.permission)) {
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/SafetyCenterReceiverTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/SafetyCenterReceiverTest.kt
index 8f8bf2b02..bd6d500df 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/SafetyCenterReceiverTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/SafetyCenterReceiverTest.kt
@@ -34,6 +34,11 @@ import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.Refr
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Ignore
@@ -48,22 +53,6 @@ import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
-private fun runBlockingTest(testBlock: suspend () -> Unit): Unit =
- error("runBlockingTest unavailable")
-
-private fun Dispatchers.setMain(dispatcher: Any): Unit =
- error("setMain unavailable")
-
-private fun Dispatchers.resetMain(): Unit =
- error("resetMain unavailable")
-
-private fun advanceUntilIdle(): Unit =
- error("advanceUntilIdle unavailable")
-
-typealias TestCoroutineDispatcher = CoroutineDispatcher
-fun TestCoroutineDispatcher.cleanupTestCoroutines(): Unit =
- error("cleanupTestCoroutines unavailable")
-
/**
* Unit tests for [SafetyCenterReceiver]
*/
@@ -79,8 +68,7 @@ class SafetyCenterReceiverTest {
val application = Mockito.mock(PermissionControllerApplication::class.java)
}
- private val testCoroutineDispatcher: TestCoroutineDispatcher =
- error("TestCoroutineDispatcher is unavailable")
+ private val testCoroutineDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
@Mock
lateinit var mockSafetyCenterManager: SafetyCenterManager
@@ -125,7 +113,6 @@ class SafetyCenterReceiverTest {
}
@Test
- @Ignore("b/239834928")
fun onReceive_actionSafetyCenterEnabledChanged() = runBlockingTest {
safetyCenterReceiver.onReceive(application, Intent(ACTION_SAFETY_CENTER_ENABLED_CHANGED))
@@ -134,7 +121,6 @@ class SafetyCenterReceiverTest {
}
@Test
- @Ignore("b/239834928")
fun onReceive_actionSafetyCenterEnabledChanged_safetyCenterDisabled() = runBlockingTest {
whenever(mockSafetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
@@ -146,7 +132,6 @@ class SafetyCenterReceiverTest {
}
@Test
- @Ignore("b/239834928")
fun onReceive_actionBootCompleted() = runBlockingTest {
val intent = Intent(ACTION_BOOT_COMPLETED)
@@ -160,7 +145,6 @@ class SafetyCenterReceiverTest {
}
@Test
- @Ignore("b/239834928")
fun onReceive_actionBootCompleted_safetyCenterDisabled() = runBlockingTest {
whenever(mockSafetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
val intent = Intent(ACTION_BOOT_COMPLETED)
@@ -173,7 +157,6 @@ class SafetyCenterReceiverTest {
}
@Test
- @Ignore("b/239834928")
fun onReceive_actionRefreshSafetySources() = runBlockingTest {
val intent = Intent(ACTION_REFRESH_SAFETY_SOURCES)
intent.putExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS, arrayOf(TEST_PRIVACY_SOURCE_ID))
@@ -187,7 +170,6 @@ class SafetyCenterReceiverTest {
}
@Test
- @Ignore("b/239834928")
fun onReceive_actionRefreshSafetySources_noSourcesSpecified() = runBlockingTest {
val intent = Intent(ACTION_REFRESH_SAFETY_SOURCES)
@@ -199,7 +181,6 @@ class SafetyCenterReceiverTest {
}
@Test
- @Ignore("b/239834928")
fun onReceive_actionRefreshSafetySources_safetyCenterDisabled() = runBlockingTest {
whenever(mockSafetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
val intent = Intent(ACTION_REFRESH_SAFETY_SOURCES)
diff --git a/service/Android.bp b/service/Android.bp
index 5f3cfa5f9..8c6d63985 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -78,6 +78,7 @@ java_sdk_library {
impl_library_visibility: [
"//frameworks/base/apex/permission/tests",
"//frameworks/base/services/tests/mockingservicestests",
+ "//frameworks/base/services/tests/PackageManagerServiceTests/server",
"//frameworks/base/services/tests/servicestests",
"//packages/modules/Permission/tests/apex",
],