Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 10996416 authored by Nate Myren's avatar Nate Myren
Browse files

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
parent b898264f
Loading
Loading
Loading
Loading
+332 −39
Original line number Original line Diff line number Diff line
@@ -31,14 +31,26 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_W


import android.annotation.NonNull;
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
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.PackageManager;
import android.content.pm.ResolveInfo;
import android.icu.text.ListFormatter;
import android.location.LocationManager;
import android.location.LocationManager;
import android.os.Process;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.speech.RecognitionService;
import android.speech.RecognizerIntent;
import android.util.ArrayMap;
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.ArrayList;
import java.util.Collections;
import java.util.Collections;
@@ -69,6 +81,9 @@ public class PermissionUsageHelper {
    /** How long after an access to show it as "running" */
    /** How long after an access to show it as "running" */
    private static final String RUNNING_ACCESS_TIME_MS = "running_acccess_time_ms";
    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_RUNNING_TIME_MS = 5000L;
    private static final long DEFAULT_RECENT_TIME_MS = 30000L;
    private static final long DEFAULT_RECENT_TIME_MS = 30000L;


@@ -175,6 +190,13 @@ public class PermissionUsageHelper {
        for (int permGroupNum = 0; permGroupNum < usedPermGroups.size(); permGroupNum++) {
        for (int permGroupNum = 0; permGroupNum < usedPermGroups.size(); permGroupNum++) {
            boolean isPhone = false;
            boolean isPhone = false;
            String permGroup = usedPermGroups.get(permGroupNum);
            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)) {
            if (permGroup.equals(OPSTR_PHONE_CALL_MICROPHONE)) {
                isPhone = true;
                isPhone = true;
                permGroup = MICROPHONE;
                permGroup = MICROPHONE;
@@ -291,77 +313,340 @@ public class PermissionUsageHelper {
        return flattenedUsages;
        return flattenedUsages;
    }
    }


    // TODO ntmyren: create JavaDoc and copy merging of proxy chains and trusted labels from
    /**
    //  "usages" livedata in ReviewOngoingUsageLiveData
     * Take the list of all usages, figure out any proxy chains, get all possible special
    private Map<PackageAttribution, CharSequence> getTrustedAttributions(List<OpUsage> usages) {
     * 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<>();
        ArrayMap<PackageAttribution, CharSequence> attributions = new ArrayMap<>();
        if (usages == null) {
        if (usages == null) {
            return attributions;
            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;
        return attributions;
    }
    }


    // TODO ntmyren: create JavaDoc and copy proxyChainsLiveData from ReviewOngoingUsageLiveData
    private CharSequence formatLabelList(List<CharSequence> labels) {
    private Set<List<OpUsage>> getProxyChains(List<OpUsage> usages) {
        return ListFormatter.getInstance().format(labels);
        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)
     * 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++) {
        for (int usageNum = 0; usageNum < usages.size(); usageNum++) {
            OpUsage usage = usages.get(usageNum);
            OpUsage usage = usages.get(usageNum);
            PackageAttribution usageAttr = usage.toPackageAttr();
            PackageAttribution usageAttr = usage.toPackageAttr();
            if (usage.proxy == null) {
            if (usage.proxy == null) {
                if (!proxies.containsKey(usageAttr)) {
                    remainingUsages.remove(usageAttr);
                }
                continue;
                continue;
            }
            }

            PackageAttribution proxyAttr = usage.proxy.toPackageAttr();
            PackageAttribution proxyAttr = usage.proxy.toPackageAttr();
            boolean proxyExists = false;
            if (!remainingUsages.containsKey(proxyAttr)) {
            for (int otherUsageNum = 0; otherUsageNum < usages.size(); otherUsageNum++) {
                remainingUsages.remove(usageAttr);
                if (usages.get(otherUsageNum).toPackageAttr().equals(proxyAttr)) {
            }
                    proxyExists = true;
        }

        // 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;
                        break;
                    }
                    }
                }
                }


            if (!proxyExists) {
                if (invalidState) {
                inProgressChains.put(usageAttr, List.of(usage));
                    break;
                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++) {
        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);
            }
        }

        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
            // find all possible starting points for chains
            String recognitionPackageName = Settings.Secure.getString(
            for (usage in remainingProxyChainUsages.toList()) {
                    userContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
                // if this usage has no proxy, but proxies another usage, it is the start of a chain
            if (recognitionPackageName == null) {
                val usageAttr = getPackageAttr(usage)
                continue;
                if (usage.proxyAccess == null && remainingProxyChainUsages.any {
            }
                    it.proxyAccess != null && getPackageAttr(it.proxyAccess) == usageAttr
            recognitionPackageName =
                }) {
                    ComponentName.unflattenFromString(recognitionPackageName).getPackageName();
                    inProgressChains[usageAttr] = mutableListOf(usage)
            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()));
                }
            }

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


                // if this usage is a chain start, or no usage have this usage as a proxy, remove it
        return trustedLabels;
                if (usage.proxyAccess == null) {
                    remainingProxyChainUsages.remove(usage)
    }
    }

    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;
            return null;
        }
        }
    }


    // TODO ntmyren: create JavaDoc and copy trustedAttrsLiveData from ReviewOngoingUsageLiveData
    private void removeDuplicates(List<OpUsage> rawUsages,
    private Map<Pair<String, UserHandle>, CharSequence> getTrustedAttributionLabels() {
            Set<PackageAttribution> specialAttributions) {
        return new ArrayMap<>();
        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) {
    private boolean isUserSensitive(String packageName, UserHandle user, String op) {
@@ -407,6 +692,10 @@ public class PermissionUsageHelper {
        public PackageAttribution toPackageAttr() {
        public PackageAttribution toPackageAttr() {
            return new PackageAttribution(packageName, attributionTag, uid);
            return new PackageAttribution(packageName, attributionTag, uid);
        }
        }

        public UserHandle getUser() {
            return UserHandle.getUserHandleForUid(uid);
        }
    }
    }


    /**
    /**
@@ -438,5 +727,9 @@ public class PermissionUsageHelper {
        public int hashCode() {
        public int hashCode() {
            return Objects.hash(packageName, attributionTag, uid);
            return Objects.hash(packageName, attributionTag, uid);
        }
        }

        public UserHandle getUser() {
            return UserHandle.getUserHandleForUid(uid);
        }
    }
    }
}
}
+2 −2

File changed.

Contains only whitespace changes.