summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/permission/PermGroupUsage.java4
-rw-r--r--core/java/android/permission/PermissionUsageHelper.java371
2 files changed, 334 insertions, 41 deletions
diff --git a/core/java/android/permission/PermGroupUsage.java b/core/java/android/permission/PermGroupUsage.java
index 7335e002e3b7..c94c0ffd4652 100644
--- a/core/java/android/permission/PermGroupUsage.java
+++ b/core/java/android/permission/PermGroupUsage.java
@@ -79,7 +79,7 @@ public final class PermGroupUsage {
@Override
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this))
- + "packageName: " + mPackageName + ", UID: " + mUid + ", permGroup: "
- + mPermGroupName + ", isActive: " + mIsActive + ",attribution: " + mAttribution;
+ + " packageName: " + mPackageName + ", UID: " + mUid + ", permGroup: "
+ + mPermGroupName + ", isActive: " + mIsActive + ", attribution: " + mAttribution;
}
}
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 00ba86732e55..e5997764ce43 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -31,14 +31,26 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_W
import android.annotation.NonNull;
import android.app.AppOpsManager;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.Attribution;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.icu.text.ListFormatter;
import android.location.LocationManager;
import android.os.Process;
import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.speech.RecognitionService;
+import android.speech.RecognizerIntent;
import android.util.ArrayMap;
-import android.util.Pair;
+import android.util.ArraySet;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
import java.util.ArrayList;
import java.util.Collections;
@@ -69,6 +81,9 @@ public class PermissionUsageHelper {
/** How long after an access to show it as "running" */
private static final String RUNNING_ACCESS_TIME_MS = "running_acccess_time_ms";
+ /** The name of the expected voice IME subtype */
+ private static final String VOICE_IME_SUBTYPE = "voice";
+
private static final long DEFAULT_RUNNING_TIME_MS = 5000L;
private static final long DEFAULT_RECENT_TIME_MS = 30000L;
@@ -175,6 +190,13 @@ public class PermissionUsageHelper {
for (int permGroupNum = 0; permGroupNum < usedPermGroups.size(); permGroupNum++) {
boolean isPhone = false;
String permGroup = usedPermGroups.get(permGroupNum);
+
+ Map<PackageAttribution, CharSequence> pkgAttrLabels = packagesWithAttributionLabels;
+ if (!MICROPHONE.equals(permGroup)) {
+ pkgAttrLabels = new ArrayMap<>();
+ }
+ removeDuplicates(rawUsages.get(permGroup), pkgAttrLabels.keySet());
+
if (permGroup.equals(OPSTR_PHONE_CALL_MICROPHONE)) {
isPhone = true;
permGroup = MICROPHONE;
@@ -291,77 +313,340 @@ public class PermissionUsageHelper {
return flattenedUsages;
}
- // TODO ntmyren: create JavaDoc and copy merging of proxy chains and trusted labels from
- // "usages" livedata in ReviewOngoingUsageLiveData
- private Map<PackageAttribution, CharSequence> getTrustedAttributions(List<OpUsage> usages) {
+ /**
+ * Take the list of all usages, figure out any proxy chains, get all possible special
+ * attribution labels, and figure out which usages need to show a special label, if any.
+ *
+ * @param usages The raw permission usages
+ *
+ * @return A map of package + attribution (in the form of a PackageAttribution object) to
+ * trusted attribution label, if there is one
+ */
+ private ArrayMap<PackageAttribution, CharSequence> getTrustedAttributions(
+ List<OpUsage> usages) {
ArrayMap<PackageAttribution, CharSequence> attributions = new ArrayMap<>();
if (usages == null) {
return attributions;
}
- Set<List<OpUsage>> proxyChains = getProxyChains(usages);
- Map<Pair<String, UserHandle>, CharSequence> trustedLabels = getTrustedAttributionLabels();
+ Set<List<PackageAttribution>> proxyChains = getProxyChains(usages);
+ Map<PackageAttribution, CharSequence> trustedLabels =
+ getTrustedAttributionLabels(usages);
+
+
+ for (List<PackageAttribution> chain : proxyChains) {
+ // If this chain is empty, or has only one link, then do not show any special labels
+ if (chain.size() <= 1) {
+ continue;
+ }
+
+ // If the last link in the chain is not user sensitive, do not show it.
+ boolean lastLinkIsUserSensitive = false;
+ for (int i = 0; i < usages.size(); i++) {
+ PackageAttribution lastLink = chain.get(chain.size() - 1);
+ if (lastLink.equals(usages.get(i).toPackageAttr())) {
+ lastLinkIsUserSensitive = true;
+ break;
+ }
+ }
+ if (!lastLinkIsUserSensitive) {
+ continue;
+ }
+
+ List<CharSequence> labels = new ArrayList<>();
+ for (int i = 0; i < chain.size(); i++) {
+ // If this is the last link in the proxy chain, assign it the series of labels
+ // Else, if it has a special label, add that label
+ // Else, if there are no other apps in the remaining part of the chain which
+ // have the same package name, add the app label
+ // If it is not the last link in the chain, remove its attribution
+ PackageAttribution attr = chain.get(i);
+ CharSequence trustedLabel = trustedLabels.get(attr);
+ if (i == chain.size() - 1) {
+ attributions.put(attr, formatLabelList(labels));
+ } else if (trustedLabel != null && !labels.contains(trustedLabel)) {
+ labels.add(trustedLabel);
+ trustedLabels.remove(attr);
+ } else {
+ boolean remainingChainHasPackage = false;
+ for (int attrNum = i + 1; attrNum < chain.size() - 1; attrNum++) {
+ if (chain.get(i).packageName.equals(attr.packageName)) {
+ remainingChainHasPackage = true;
+ break;
+ }
+ }
+ if (!remainingChainHasPackage) {
+ try {
+ ApplicationInfo appInfo = mPkgManager.getApplicationInfoAsUser(
+ attr.packageName, 0, attr.getUser());
+ CharSequence appLabel = appInfo.loadLabel(
+ getUserContext(attr.getUser()).getPackageManager());
+ labels.add(appLabel);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Do nothing
+ }
+ }
+ }
+ }
+ }
+
+ for (PackageAttribution attr : trustedLabels.keySet()) {
+ attributions.put(attr, trustedLabels.get(attr));
+ }
return attributions;
}
- // TODO ntmyren: create JavaDoc and copy proxyChainsLiveData from ReviewOngoingUsageLiveData
- private Set<List<OpUsage>> getProxyChains(List<OpUsage> usages) {
- Map<PackageAttribution, List<OpUsage>> inProgressChains = new ArrayMap<>();
- List<OpUsage> remainingUsages = new ArrayList<>(usages);
- // find all one-link chains (that is, all proxied apps whose proxy is not included in
- // the usage list)
+ private CharSequence formatLabelList(List<CharSequence> labels) {
+ return ListFormatter.getInstance().format(labels);
+ }
+
+ /**
+ * Get all chains of proxy usages. A proxy chain is defined as one usage at the root, then
+ * further proxy usages, where the app and attribution tag of the proxy in the proxy usage
+ * matches the previous usage in the chain.
+ *
+ * @param usages The permission usages
+ *
+ * @return A set of lists of package attributions. One list represents a chain of proxy usages,
+ * with the start of the chain (the usage without a proxy) at position 0, and each usage down
+ * the chain has the previous one listed as a proxy usage.
+ */
+ private Set<List<PackageAttribution>> getProxyChains(List<OpUsage> usages) {
+ ArrayMap<PackageAttribution, List<PackageAttribution>> proxyChains = new ArrayMap<>();
+ // map of usages that still need to be removed, or added to a chain
+ ArrayMap<PackageAttribution, OpUsage> remainingUsages = new ArrayMap<>();
+ // map of usage.proxy -> usage, telling us if a usage is a proxy
+ ArrayMap<PackageAttribution, PackageAttribution> proxies = new ArrayMap<>();
+ for (int i = 0; i < remainingUsages.size(); i++) {
+ OpUsage usage = usages.get(i);
+ remainingUsages.put(usage.toPackageAttr(), usage);
+ if (usage.proxy != null) {
+ proxies.put(usage.proxy.toPackageAttr(), usage.toPackageAttr());
+ }
+ }
+ // find and remove all one-link chains (that is, all proxied apps whose proxy is not
+ // included in the usage list), and apps that are neither proxy nor proxied.
for (int usageNum = 0; usageNum < usages.size(); usageNum++) {
OpUsage usage = usages.get(usageNum);
PackageAttribution usageAttr = usage.toPackageAttr();
if (usage.proxy == null) {
+ if (!proxies.containsKey(usageAttr)) {
+ remainingUsages.remove(usageAttr);
+ }
continue;
}
+
PackageAttribution proxyAttr = usage.proxy.toPackageAttr();
- boolean proxyExists = false;
- for (int otherUsageNum = 0; otherUsageNum < usages.size(); otherUsageNum++) {
- if (usages.get(otherUsageNum).toPackageAttr().equals(proxyAttr)) {
- proxyExists = true;
+ if (!remainingUsages.containsKey(proxyAttr)) {
+ remainingUsages.remove(usageAttr);
+ }
+ }
+
+ // find all possible starting points for chains
+ List<PackageAttribution> keys = new ArrayList<>(remainingUsages.keySet());
+ for (int usageNum = 0; usageNum < remainingUsages.size(); usageNum++) {
+ OpUsage usage = remainingUsages.get(keys.get(usageNum));
+ if (usage == null) {
+ continue;
+ }
+ PackageAttribution usageAttr = usage.toPackageAttr();
+ // If this usage has a proxy, but is not a proxy, it is the start of a chain.
+ if (!proxies.containsKey(usageAttr) && usage.proxy != null) {
+ proxyChains.put(usageAttr, List.of(usageAttr));
+ }
+ }
+
+ // assemble the chains
+ for (int numStart = 0; numStart < proxyChains.size(); numStart++) {
+ PackageAttribution currPackageAttr = proxyChains.keyAt(numStart);
+ List<PackageAttribution> proxyChain = proxyChains.get(currPackageAttr);
+ OpUsage currentUsage = remainingUsages.get(currPackageAttr);
+ if (currentUsage == null || proxyChain == null) {
+ continue;
+ }
+ while (currentUsage.proxy != null) {
+ currPackageAttr = currentUsage.proxy.toPackageAttr();
+ currentUsage = remainingUsages.get(currPackageAttr);
+
+ boolean invalidState = false;
+ for (int chainNum = 0; chainNum < proxyChain.size(); chainNum++) {
+ if (currentUsage == null || proxyChain.get(chainNum).equals(currPackageAttr)) {
+ // either our current value is not in the usage list, or we have a cycle
+ invalidState = true;
+ break;
+ }
+ }
+
+ if (invalidState) {
break;
}
- }
- if (!proxyExists) {
- inProgressChains.put(usageAttr, List.of(usage));
- remainingUsages.remove(usage);
+ proxyChain.add(currPackageAttr);
}
}
- // find all possible starting points for chains
+ return new ArraySet<>(proxyChains.values());
+ }
+
+ /**
+ * Gets all trusted proxied voice IME and voice recognition microphone uses, and get the
+ * label needed to display with it, as well as information about the proxy whose label is being
+ * shown, if applicable.
+ *
+ * @param usages The permission usages
+ *
+ * @return A map of package attribution -> the attribution label for that package attribution,
+ * if applicable
+ */
+ private Map<PackageAttribution, CharSequence> getTrustedAttributionLabels(
+ List<OpUsage> usages) {
+ List<UserHandle> users = new ArrayList<>();
for (int i = 0; i < usages.size(); i++) {
- OpUsage usage = usages.get(i);
+ UserHandle user = UserHandle.getUserHandleForUid(usages.get(i).uid);
+ if (!users.contains(user)) {
+ users.add(user);
+ }
}
- /*
- // find all possible starting points for chains
- for (usage in remainingProxyChainUsages.toList()) {
- // if this usage has no proxy, but proxies another usage, it is the start of a chain
- val usageAttr = getPackageAttr(usage)
- if (usage.proxyAccess == null && remainingProxyChainUsages.any {
- it.proxyAccess != null && getPackageAttr(it.proxyAccess) == usageAttr
- }) {
- inProgressChains[usageAttr] = mutableListOf(usage)
+ Map<PackageAttribution, CharSequence> trustedLabels = new ArrayMap<>();
+ for (int userNum = 0; userNum < users.size(); userNum++) {
+ UserHandle user = users.get(userNum);
+ Context userContext = mContext.createContextAsUser(user, 0);
+
+ // Get all voice IME labels
+ Map<String, CharSequence> voiceInputs = new ArrayMap<>();
+ List<InputMethodInfo> inputs = userContext.getSystemService(InputMethodManager.class)
+ .getEnabledInputMethodList();
+ for (int inputNum = 0; inputNum < inputs.size(); inputNum++) {
+ InputMethodInfo input = inputs.get(inputNum);
+ for (int subtypeNum = 0; subtypeNum < input.getSubtypeCount(); subtypeNum++) {
+ if (VOICE_IME_SUBTYPE.equals(input.getSubtypeAt(subtypeNum).getMode())) {
+ voiceInputs.put(input.getPackageName(), input.getServiceInfo()
+ .loadUnsafeLabel(userContext.getPackageManager()));
+ break;
+ }
+ }
+ }
+
+ // Get the currently selected recognizer from the secure setting
+ String recognitionPackageName = Settings.Secure.getString(
+ userContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
+ if (recognitionPackageName == null) {
+ continue;
+ }
+ recognitionPackageName =
+ ComponentName.unflattenFromString(recognitionPackageName).getPackageName();
+ Map<String, CharSequence> recognizers = new ArrayMap<>();
+ List<ResolveInfo> availableRecognizers = mPkgManager.queryIntentServicesAsUser(
+ new Intent(RecognitionService.SERVICE_INTERFACE), PackageManager.GET_META_DATA,
+ user.getIdentifier());
+ for (int recogNum = 0; recogNum < availableRecognizers.size(); recogNum++) {
+ ResolveInfo info = availableRecognizers.get(recogNum);
+ if (recognitionPackageName.equals(info.serviceInfo.packageName)) {
+ recognizers.put(recognitionPackageName, info.serviceInfo.loadUnsafeLabel(
+ userContext.getPackageManager()));
}
+ }
- // if this usage is a chain start, or no usage have this usage as a proxy, remove it
- if (usage.proxyAccess == null) {
- remainingProxyChainUsages.remove(usage)
+ Map<String, CharSequence> recognizerIntents = new ArrayMap<>();
+ List<ResolveInfo> availableRecognizerIntents = mPkgManager.queryIntentActivitiesAsUser(
+ new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
+ PackageManager.GET_META_DATA, user);
+ for (int recogNum = 0; recogNum < availableRecognizerIntents.size(); recogNum++) {
+ ResolveInfo info = availableRecognizerIntents.get(recogNum);
+ if (info.activityInfo == null) {
+ continue;
+ }
+ String pkgName = info.activityInfo.packageName;
+ if (recognitionPackageName.equals(pkgName) && recognizers.containsKey(pkgName)) {
+ recognizerIntents.put(pkgName, recognizers.get(pkgName));
}
}
+ for (int usageNum = 0; usageNum < usages.size(); usageNum++) {
+ setTrustedAttrsForAccess(usages.get(usageNum), user, false, voiceInputs,
+ trustedLabels);
+ setTrustedAttrsForAccess(usages.get(usageNum), user, false, recognizerIntents,
+ trustedLabels);
+ setTrustedAttrsForAccess(usages.get(usageNum), user, true, recognizers,
+ trustedLabels);
+ }
+ }
+
+ return trustedLabels;
+ }
+
+ private void setTrustedAttrsForAccess(OpUsage opUsage, UserHandle currUser, boolean getProxy,
+ Map<String, CharSequence> trustedMap, Map<PackageAttribution, CharSequence> toSetMap) {
+ OpUsage usage = opUsage;
+ if (getProxy) {
+ usage = opUsage.proxy;
+ }
- */
+ if (usage == null || !usage.getUser().equals(currUser)
+ || !trustedMap.containsKey(usage.packageName)) {
+ return;
+ }
- return null;
+ CharSequence label = getAttributionLabel(usage);
+ if (trustedMap.get(usage.packageName).equals(label)) {
+ toSetMap.put(usage.toPackageAttr(), label);
+ }
}
- // TODO ntmyren: create JavaDoc and copy trustedAttrsLiveData from ReviewOngoingUsageLiveData
- private Map<Pair<String, UserHandle>, CharSequence> getTrustedAttributionLabels() {
- return new ArrayMap<>();
+ private CharSequence getAttributionLabel(OpUsage usage) {
+ if (usage.attributionTag == null) {
+ return null;
+ }
+
+ PackageInfo pkgInfo;
+ try {
+ pkgInfo = mPkgManager.getPackageInfoAsUser(usage.packageName,
+ PackageManager.GET_ATTRIBUTIONS, usage.getUser().getIdentifier());
+ if (pkgInfo.attributions == null || pkgInfo.attributions.length == 0) {
+ return null;
+ }
+ for (int attrNum = 0; attrNum < pkgInfo.attributions.length; attrNum++) {
+ Attribution attr = pkgInfo.attributions[attrNum];
+ if (usage.attributionTag.equals(attr.getTag())) {
+ return mContext.createPackageContextAsUser(usage.packageName, 0,
+ usage.getUser()).getString(attr.getLabel());
+ }
+ }
+ return null;
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ private void removeDuplicates(List<OpUsage> rawUsages,
+ Set<PackageAttribution> specialAttributions) {
+ List<OpUsage> toRemove = new ArrayList<>();
+ if (rawUsages == null) {
+ return;
+ }
+
+ for (int usageNum = 0; usageNum < rawUsages.size(); usageNum++) {
+ PackageAttribution usageAttr = rawUsages.get(usageNum).toPackageAttr();
+ // If this attribution has a special attribution, do not remove it
+ if (specialAttributions.contains(usageAttr)) {
+ continue;
+ }
+
+ // Search the rest of the list for apps with the same uid. If there is one, mark this
+ // usage for removal.
+ for (int otherUsageNum = usageNum + 1; otherUsageNum < rawUsages.size();
+ otherUsageNum++) {
+ if (rawUsages.get(otherUsageNum).uid == usageAttr.uid) {
+ toRemove.add(rawUsages.get(usageNum));
+ break;
+ }
+ }
+ }
+
+ for (int i = 0; i < toRemove.size(); i++) {
+ rawUsages.remove(toRemove.get(i));
+ }
}
private boolean isUserSensitive(String packageName, UserHandle user, String op) {
@@ -407,6 +692,10 @@ public class PermissionUsageHelper {
public PackageAttribution toPackageAttr() {
return new PackageAttribution(packageName, attributionTag, uid);
}
+
+ public UserHandle getUser() {
+ return UserHandle.getUserHandleForUid(uid);
+ }
}
/**
@@ -438,5 +727,9 @@ public class PermissionUsageHelper {
public int hashCode() {
return Objects.hash(packageName, attributionTag, uid);
}
+
+ public UserHandle getUser() {
+ return UserHandle.getUserHandleForUid(uid);
+ }
}
}