Merge "Support AttributionSource chains in PermissionUsageHelper" into sc-dev
This commit is contained in:
@@ -158,6 +158,8 @@ public final class PermissionManager {
|
|||||||
mPermissionManager = IPermissionManager.Stub.asInterface(ServiceManager.getServiceOrThrow(
|
mPermissionManager = IPermissionManager.Stub.asInterface(ServiceManager.getServiceOrThrow(
|
||||||
"permissionmgr"));
|
"permissionmgr"));
|
||||||
mLegacyPermissionManager = context.getSystemService(LegacyPermissionManager.class);
|
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
|
@NonNull
|
||||||
@RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
|
@RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
|
||||||
public List<PermGroupUsage> getIndicatorAppOpUsageData() {
|
public List<PermGroupUsage> getIndicatorAppOpUsageData() {
|
||||||
// Lazily initialize the usage helper
|
|
||||||
if (mUsageHelper == null) {
|
|
||||||
mUsageHelper = new PermissionUsageHelper(mContext);
|
|
||||||
}
|
|
||||||
return mUsageHelper.getOpUsageData(new AudioManager().isMicrophoneMute());
|
return mUsageHelper.getOpUsageData(new AudioManager().isMicrophoneMute());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ package android.permission;
|
|||||||
import static android.Manifest.permission_group.CAMERA;
|
import static android.Manifest.permission_group.CAMERA;
|
||||||
import static android.Manifest.permission_group.LOCATION;
|
import static android.Manifest.permission_group.LOCATION;
|
||||||
import static android.Manifest.permission_group.MICROPHONE;
|
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_CAMERA;
|
||||||
import static android.app.AppOpsManager.OPSTR_COARSE_LOCATION;
|
import static android.app.AppOpsManager.OPSTR_COARSE_LOCATION;
|
||||||
import static android.app.AppOpsManager.OPSTR_FINE_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 static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.app.AppOpsManager;
|
import android.app.AppOpsManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
@@ -56,7 +61,7 @@ import java.util.Objects;
|
|||||||
*
|
*
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public class PermissionUsageHelper {
|
public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener {
|
||||||
|
|
||||||
/** Whether to show the mic and camera icons. */
|
/** Whether to show the mic and camera icons. */
|
||||||
private static final String PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled";
|
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 ArrayMap<UserHandle, Context> mUserContexts;
|
||||||
private PackageManager mPkgManager;
|
private PackageManager mPkgManager;
|
||||||
private AppOpsManager mAppOpsManager;
|
private AppOpsManager mAppOpsManager;
|
||||||
|
private ArrayMap<Integer, ArrayList<AccessChainLink>> mAttributionChains = new ArrayMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for PermissionUsageHelper
|
* Constructor for PermissionUsageHelper
|
||||||
@@ -151,6 +157,10 @@ public class PermissionUsageHelper {
|
|||||||
mAppOpsManager = context.getSystemService(AppOpsManager.class);
|
mAppOpsManager = context.getSystemService(AppOpsManager.class);
|
||||||
mUserContexts = new ArrayMap<>();
|
mUserContexts = new ArrayMap<>();
|
||||||
mUserContexts.put(Process.myUserHandle(), mContext);
|
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) {
|
private Context getUserContext(UserHandle user) {
|
||||||
@@ -160,6 +170,45 @@ public class PermissionUsageHelper {
|
|||||||
return mUserContexts.get(user);
|
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
|
* @see PermissionManager.getIndicatorAppOpUsageData
|
||||||
*/
|
*/
|
||||||
@@ -331,7 +380,7 @@ public class PermissionUsageHelper {
|
|||||||
private ArrayMap<OpUsage, CharSequence> getUniqueUsagesWithLabels(List<OpUsage> usages) {
|
private ArrayMap<OpUsage, CharSequence> getUniqueUsagesWithLabels(List<OpUsage> usages) {
|
||||||
ArrayMap<OpUsage, CharSequence> usagesAndLabels = new ArrayMap<>();
|
ArrayMap<OpUsage, CharSequence> usagesAndLabels = new ArrayMap<>();
|
||||||
|
|
||||||
if (usages == null) {
|
if (usages == null || usages.isEmpty()) {
|
||||||
return usagesAndLabels;
|
return usagesAndLabels;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,8 +479,51 @@ public class PermissionUsageHelper {
|
|||||||
}
|
}
|
||||||
iterNum++;
|
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()) {
|
for (int packageHash : mostRecentUsages.keySet()) {
|
||||||
@@ -495,4 +587,24 @@ public class PermissionUsageHelper {
|
|||||||
&& lastAccessTime == other.lastAccessTime && isRunning == other.isRunning;
|
&& 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ import android.app.ActivityManager;
|
|||||||
import android.app.ActivityManagerInternal;
|
import android.app.ActivityManagerInternal;
|
||||||
import android.app.AppGlobals;
|
import android.app.AppGlobals;
|
||||||
import android.app.AppOpsManager;
|
import android.app.AppOpsManager;
|
||||||
import android.app.AppOpsManager.AttributionFlags;
|
|
||||||
import android.app.AppOpsManager.AttributedOpEntry;
|
import android.app.AppOpsManager.AttributedOpEntry;
|
||||||
|
import android.app.AppOpsManager.AttributionFlags;
|
||||||
import android.app.AppOpsManager.HistoricalOps;
|
import android.app.AppOpsManager.HistoricalOps;
|
||||||
import android.app.AppOpsManager.Mode;
|
import android.app.AppOpsManager.Mode;
|
||||||
import android.app.AppOpsManager.OpEntry;
|
import android.app.AppOpsManager.OpEntry;
|
||||||
@@ -3255,13 +3255,6 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation);
|
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,
|
private SyncNotedAppOp noteProxyOperationImpl(int code, AttributionSource attributionSource,
|
||||||
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
|
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
|
||||||
boolean skipProxyOperation) {
|
boolean skipProxyOperation) {
|
||||||
@@ -3289,8 +3282,7 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
|
final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
|
||||||
final boolean isProxyTrusted = mContext.checkPermission(
|
final boolean isProxyTrusted = mContext.checkPermission(
|
||||||
Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
|
Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
|
||||||
== PackageManager.PERMISSION_GRANTED || isSelfBlame
|
== PackageManager.PERMISSION_GRANTED || isSelfBlame;
|
||||||
|| isRecognitionServiceTemp(code, proxyPackageName);
|
|
||||||
|
|
||||||
if (!skipProxyOperation) {
|
if (!skipProxyOperation) {
|
||||||
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
|
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
|
||||||
|
|||||||
Reference in New Issue
Block a user