Add special attribution cases to PermissionUsageHelper

Porting the proxy usage and mic special attribution cases from the
PermissionController to the PermissionManager

Bug: 172868375
Test: manual
Change-Id: I2af850c9cd709541abf862377b67153a1c477c94
This commit is contained in:
Nate Myren
2021-01-07 13:09:02 -08:00
parent b898264ff2
commit 1099641699
2 changed files with 337 additions and 44 deletions

View File

@@ -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;
}
}

View File

@@ -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;
break;
}
}
if (!proxyExists) {
inProgressChains.put(usageAttr, List.of(usage));
remainingUsages.remove(usage);
PackageAttribution proxyAttr = usage.proxy.toPackageAttr();
if (!remainingUsages.containsKey(proxyAttr)) {
remainingUsages.remove(usageAttr);
}
}
// find all possible starting points for chains
for (int i = 0; i < usages.size(); i++) {
OpUsage usage = usages.get(i);
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));
}
}
/*
// 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)
// 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 this usage is a chain start, or no usage have this usage as a proxy, remove it
if (usage.proxyAccess == null) {
remainingProxyChainUsages.remove(usage)
if (invalidState) {
break;
}
proxyChain.add(currPackageAttr);
}
}
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++) {
UserHandle user = UserHandle.getUserHandleForUid(usages.get(i).uid);
if (!users.contains(user)) {
users.add(user);
}
}
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()));
}
}
return null;
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;
}
// TODO ntmyren: create JavaDoc and copy trustedAttrsLiveData from ReviewOngoingUsageLiveData
private Map<Pair<String, UserHandle>, CharSequence> getTrustedAttributionLabels() {
return new ArrayMap<>();
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;
}
CharSequence label = getAttributionLabel(usage);
if (trustedMap.get(usage.packageName).equals(label)) {
toSetMap.put(usage.toPackageAttr(), label);
}
}
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);
}
}
}