Merge "Support AttributionSource chains in PermissionUsageHelper" into sc-dev

This commit is contained in:
Nate Myren
2021-06-09 03:34:57 +00:00
committed by Android (Google) Code Review
3 changed files with 120 additions and 18 deletions

View File

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

View File

@@ -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,8 +479,51 @@ public class PermissionUsageHelper {
}
iterNum++;
}
usagesAndLabels.put(start,
proxyLabelList.isEmpty() ? null : formatLabelList(proxyLabelList));
// 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()) {
@@ -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;
}
}
}

View File

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