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

Commit cfad18ef authored by Automerger Merge Worker's avatar Automerger Merge Worker Committed by Android (Google) Code Review
Browse files

Merge "Merge "Support AttributionSource chains in PermissionUsageHelper" into...

Merge "Merge "Support AttributionSource chains in PermissionUsageHelper" into sc-dev am: 552aa465" into sc-dev-plus-aosp
parents 440d5a7f d7cb7ae2
Loading
Loading
Loading
Loading
+2 −4
Original line number Diff line number Diff line
@@ -158,6 +158,8 @@ public final class PermissionManager {
        mPermissionManager = IPermissionManager.Stub.asInterface(ServiceManager.getServiceOrThrow(
                "permissionmgr"));
        mLegacyPermissionManager = context.getSystemService(LegacyPermissionManager.class);
        //TODO ntmyren: there should be a way to only enable the watcher when requested
        mUsageHelper = new PermissionUsageHelper(context);
    }

    /**
@@ -878,10 +880,6 @@ public final class PermissionManager {
    @NonNull
    @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
    public List<PermGroupUsage> getIndicatorAppOpUsageData() {
        // Lazily initialize the usage helper
        if (mUsageHelper == null) {
            mUsageHelper = new PermissionUsageHelper(mContext);
        }
        return mUsageHelper.getOpUsageData(new AudioManager().isMicrophoneMute());
    }

+116 −4
Original line number Diff line number Diff line
@@ -19,6 +19,10 @@ package android.permission;
import static android.Manifest.permission_group.CAMERA;
import static android.Manifest.permission_group.LOCATION;
import static android.Manifest.permission_group.MICROPHONE;
import static android.app.AppOpsManager.ATTRIBUTION_FLAGS_NONE;
import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER;
import static android.app.AppOpsManager.AttributionFlags;
import static android.app.AppOpsManager.OPSTR_CAMERA;
import static android.app.AppOpsManager.OPSTR_COARSE_LOCATION;
import static android.app.AppOpsManager.OPSTR_FINE_LOCATION;
@@ -30,6 +34,7 @@ import static android.media.AudioSystem.MODE_IN_COMMUNICATION;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -56,7 +61,7 @@ import java.util.Objects;
 *
 * @hide
 */
public class PermissionUsageHelper {
public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener {

    /** Whether to show the mic and camera icons.  */
    private static final String PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled";
@@ -140,6 +145,7 @@ public class PermissionUsageHelper {
    private ArrayMap<UserHandle, Context> mUserContexts;
    private PackageManager mPkgManager;
    private AppOpsManager mAppOpsManager;
    private ArrayMap<Integer, ArrayList<AccessChainLink>> mAttributionChains = new ArrayMap<>();

    /**
     * Constructor for PermissionUsageHelper
@@ -151,6 +157,10 @@ public class PermissionUsageHelper {
        mAppOpsManager = context.getSystemService(AppOpsManager.class);
        mUserContexts = new ArrayMap<>();
        mUserContexts.put(Process.myUserHandle(), mContext);
        // TODO ntmyren: make this listen for flag enable/disable changes
        String[] ops = { OPSTR_CAMERA, OPSTR_RECORD_AUDIO };
        mContext.getSystemService(AppOpsManager.class).startWatchingActive(ops,
                context.getMainExecutor(), this);
    }

    private Context getUserContext(UserHandle user) {
@@ -160,6 +170,45 @@ public class PermissionUsageHelper {
        return mUserContexts.get(user);
    }

    @Override
    public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
            boolean active) {
        // not part of an attribution chain. Do nothing
    }

    @Override
    public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
            @Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags,
            int attributionChainId) {
        if ((attributionFlags & ATTRIBUTION_FLAGS_NONE) != 0) {
            return;
        }

        if (!active) {
            // if any link in the chain is finished, remove the chain.
            // TODO ntmyren: be smarter about this
            mAttributionChains.remove(attributionChainId);
            return;
        }

        ArrayList<AccessChainLink> currentChain = mAttributionChains.computeIfAbsent(
                attributionChainId, k -> new ArrayList<>());
        AccessChainLink link = new AccessChainLink(op, packageName, attributionTag, uid,
                attributionFlags);

        int currSize = currentChain.size();
        if (currSize == 0 || link.isEnd() || !currentChain.get(currSize - 1).isEnd()) {
            // if the list is empty, this link is the end, or the last link in the current chain
            // isn't the end, add it to the end
            currentChain.add(link);
        } else if (link.isStart()) {
            currentChain.add(0, link);
        } else if (currentChain.get(currentChain.size() - 1).isEnd()) {
            // we already have the end, and this is a mid node, so insert before the end
            currentChain.add(currSize - 1, link);
        }
    }

    /**
     * @see PermissionManager.getIndicatorAppOpUsageData
     */
@@ -331,7 +380,7 @@ public class PermissionUsageHelper {
    private ArrayMap<OpUsage, CharSequence> getUniqueUsagesWithLabels(List<OpUsage> usages) {
        ArrayMap<OpUsage, CharSequence> usagesAndLabels = new ArrayMap<>();

        if (usages == null) {
        if (usages == null || usages.isEmpty()) {
            return usagesAndLabels;
        }

@@ -430,9 +479,52 @@ public class PermissionUsageHelper {
                }
                iterNum++;
            }

            // TODO ntmyren: remove this proxy logic once camera is converted to AttributionSource
            // For now: don't add mic proxy usages
            if (!start.op.equals(OPSTR_RECORD_AUDIO)) {
                usagesAndLabels.put(start,
                        proxyLabelList.isEmpty() ? null : formatLabelList(proxyLabelList));
            }
        }

        for (int i = 0; i < mAttributionChains.size(); i++) {
            List<AccessChainLink> usageList = mAttributionChains.valueAt(i);
            int lastVisible = usageList.size() - 1;
            // TODO ntmyren: remove this mic code once camera is converted to AttributionSource
            // if the list is empty or incomplete, do not show it.
            if (usageList.isEmpty() || !usageList.get(lastVisible).isEnd()
                    || !usageList.get(0).isStart()
                    || !usageList.get(lastVisible).usage.op.equals(OPSTR_RECORD_AUDIO)) {
                continue;
            }

            //TODO ntmyren: remove once camera etc. etc.
            for (AccessChainLink link: usageList) {
                proxyPackages.add(link.usage.getPackageIdHash());
            }

            AccessChainLink start = usageList.get(0);
            AccessChainLink lastVisibleLink = usageList.get(lastVisible);
            while (lastVisible > 0 && !shouldShowPackage(lastVisibleLink.usage.packageName)) {
                lastVisible--;
                lastVisibleLink = usageList.get(lastVisible);
            }
            String proxyLabel = null;
            if (!lastVisibleLink.usage.packageName.equals(start.usage.packageName)) {
                try {
                    PackageManager userPkgManager =
                            getUserContext(lastVisibleLink.usage.getUser()).getPackageManager();
                    ApplicationInfo appInfo = userPkgManager.getApplicationInfo(
                            lastVisibleLink.usage.packageName, 0);
                    proxyLabel = appInfo.loadLabel(userPkgManager).toString();
                } catch (PackageManager.NameNotFoundException e) {
                    // do nothing
                }

            }
            usagesAndLabels.put(start.usage, proxyLabel);
        }

        for (int packageHash : mostRecentUsages.keySet()) {
            if (!proxyPackages.contains(packageHash)) {
@@ -495,4 +587,24 @@ public class PermissionUsageHelper {
                    && lastAccessTime == other.lastAccessTime && isRunning == other.isRunning;
        }
    }

    private static class AccessChainLink {
        public final OpUsage usage;
        public final @AttributionFlags int flags;

        AccessChainLink(String op, String packageName, String attributionTag, int uid,
                int flags) {
            this.usage = new OpUsage(packageName, attributionTag, op, uid,
                    System.currentTimeMillis(), true, null);
            this.flags = flags;
        }

        public boolean isEnd() {
            return (flags & ATTRIBUTION_FLAG_ACCESSOR) != 0;
        }

        public boolean isStart() {
            return (flags & ATTRIBUTION_FLAG_RECEIVER) != 0;
        }
    }
}
+2 −10
Original line number Diff line number Diff line
@@ -84,8 +84,8 @@ import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManager.AttributionFlags;
import android.app.AppOpsManager.AttributedOpEntry;
import android.app.AppOpsManager.AttributionFlags;
import android.app.AppOpsManager.HistoricalOps;
import android.app.AppOpsManager.Mode;
import android.app.AppOpsManager.OpEntry;
@@ -3255,13 +3255,6 @@ public class AppOpsService extends IAppOpsService.Stub {
                shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation);
    }

    // TODO b/184963112: remove once full blaming is implemented
    private boolean isRecognitionServiceTemp(int code, String packageName) {
        return code == OP_RECORD_AUDIO
                && (packageName.equals("com.google.android.googlequicksearchbox")
                || packageName.equals("com.google.android.tts"));
    }

    private SyncNotedAppOp noteProxyOperationImpl(int code, AttributionSource attributionSource,
            boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
            boolean skipProxyOperation) {
@@ -3289,8 +3282,7 @@ public class AppOpsService extends IAppOpsService.Stub {
        final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
        final boolean isProxyTrusted = mContext.checkPermission(
                Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
                == PackageManager.PERMISSION_GRANTED || isSelfBlame
                || isRecognitionServiceTemp(code, proxyPackageName);
                == PackageManager.PERMISSION_GRANTED || isSelfBlame;

        if (!skipProxyOperation) {
            final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY