Merge "Add most basic aspects of permission usage to PermissionManager"
This commit is contained in:
79
core/java/android/permission/PermGroupUsage.java
Normal file
79
core/java/android/permission/PermGroupUsage.java
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.permission;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the usage of a permission group by an app. Supports package name, user, permission
|
||||
* group, whether or not the access is running or recent, whether the access is tied to a phone
|
||||
* call, and an optional special attribution.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class PermGroupUsage {
|
||||
|
||||
private final String mPackageName;
|
||||
private final int mUid;
|
||||
private final String mPermGroupName;
|
||||
private final boolean mIsActive;
|
||||
private final boolean mIsPhoneCall;
|
||||
private final CharSequence mAttribution;
|
||||
|
||||
PermGroupUsage(@NonNull String packageName, int uid,
|
||||
@NonNull String permGroupName, boolean isActive, boolean isPhoneCall,
|
||||
@Nullable CharSequence attribution) {
|
||||
this.mPackageName = packageName;
|
||||
this.mUid = uid;
|
||||
this.mPermGroupName = permGroupName;
|
||||
this.mIsActive = isActive;
|
||||
this.mIsPhoneCall = isPhoneCall;
|
||||
this.mAttribution = attribution;
|
||||
}
|
||||
|
||||
public @NonNull String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
public int getUid() {
|
||||
return mUid;
|
||||
}
|
||||
|
||||
public @NonNull String getPermGroupName() {
|
||||
return mPermGroupName;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return mIsActive;
|
||||
}
|
||||
|
||||
public boolean isPhoneCall() {
|
||||
return mIsPhoneCall;
|
||||
}
|
||||
|
||||
public @Nullable CharSequence getAttribution() {
|
||||
return mAttribution;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this))
|
||||
+ "packageName: " + mPackageName + ", UID: " + mUid + ", permGroup: "
|
||||
+ mPermGroupName + ", isActive: " + mIsActive + ",attribution: " + mAttribution;
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import android.content.pm.ParceledListSlice;
|
||||
import android.content.pm.PermissionGroupInfo;
|
||||
import android.content.pm.PermissionInfo;
|
||||
import android.content.pm.permission.SplitPermissionInfoParcelable;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
@@ -114,6 +115,7 @@ public final class PermissionManager {
|
||||
|
||||
private final ArrayMap<PackageManager.OnPermissionsChangedListener,
|
||||
IOnPermissionsChangeListener> mPermissionListeners = new ArrayMap<>();
|
||||
private PermissionUsageHelper mUsageHelper;
|
||||
|
||||
private List<SplitPermissionInfo> mSplitPermissionInfos;
|
||||
|
||||
@@ -853,6 +855,22 @@ public final class PermissionManager {
|
||||
return mSplitPermissionInfos;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of permission groups currently or recently used by all apps by all users in
|
||||
* the current profile group.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of packages that have permissions that specified
|
||||
* {@code requestDontAutoRevokePermissions=true} in their
|
||||
|
||||
441
core/java/android/permission/PermissionUsageHelper.java
Normal file
441
core/java/android/permission/PermissionUsageHelper.java
Normal file
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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.OPSTR_CAMERA;
|
||||
import static android.app.AppOpsManager.OPSTR_COARSE_LOCATION;
|
||||
import static android.app.AppOpsManager.OPSTR_FINE_LOCATION;
|
||||
import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA;
|
||||
import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE;
|
||||
import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
|
||||
import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
|
||||
import static android.app.AppOpsManager.opToPermission;
|
||||
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A helper which gets all apps which have used microphone, camera, and possible location
|
||||
* permissions within a certain timeframe, as well as possible special attributions, and if the
|
||||
* usage is a phone call.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class PermissionUsageHelper {
|
||||
|
||||
/** Whether to show the mic and camera icons. */
|
||||
private static final String PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled";
|
||||
|
||||
/** Whether to show the location indicators. */
|
||||
private static final String PROPERTY_LOCATION_INDICATORS_ENABLED =
|
||||
"location_indicators_enabled";
|
||||
|
||||
/** How long after an access to show it as "recent" */
|
||||
private static final String RECENT_ACCESS_TIME_MS = "recent_acccess_time_ms";
|
||||
|
||||
/** 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 long DEFAULT_RUNNING_TIME_MS = 5000L;
|
||||
private static final long DEFAULT_RECENT_TIME_MS = 30000L;
|
||||
|
||||
private static boolean shouldShowIndicators() {
|
||||
return true;
|
||||
// TODO ntmyren: remove true set when device config is configured correctly
|
||||
//DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
|
||||
//PROPERTY_CAMERA_MIC_ICONS_ENABLED, true);
|
||||
}
|
||||
|
||||
private static boolean shouldShowLocationIndicator() {
|
||||
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
|
||||
PROPERTY_LOCATION_INDICATORS_ENABLED, false);
|
||||
}
|
||||
|
||||
private static long getRecentThreshold(Long now) {
|
||||
return now - DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY,
|
||||
RECENT_ACCESS_TIME_MS, DEFAULT_RECENT_TIME_MS);
|
||||
}
|
||||
|
||||
private static long getRunningThreshold(Long now) {
|
||||
return now - DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY,
|
||||
RUNNING_ACCESS_TIME_MS, DEFAULT_RUNNING_TIME_MS);
|
||||
}
|
||||
|
||||
private static final List<String> LOCATION_OPS = List.of(
|
||||
OPSTR_COARSE_LOCATION,
|
||||
OPSTR_FINE_LOCATION
|
||||
);
|
||||
|
||||
private static final List<String> MIC_OPS = List.of(
|
||||
OPSTR_PHONE_CALL_CAMERA,
|
||||
OPSTR_RECORD_AUDIO
|
||||
);
|
||||
|
||||
private static final List<String> CAMERA_OPS = List.of(
|
||||
OPSTR_PHONE_CALL_CAMERA,
|
||||
OPSTR_CAMERA
|
||||
);
|
||||
|
||||
private static @NonNull String getGroupForOp(String op) {
|
||||
switch(op) {
|
||||
case OPSTR_RECORD_AUDIO:
|
||||
return MICROPHONE;
|
||||
case OPSTR_CAMERA:
|
||||
return CAMERA;
|
||||
case OPSTR_PHONE_CALL_MICROPHONE:
|
||||
case OPSTR_PHONE_CALL_CAMERA:
|
||||
return op;
|
||||
case OPSTR_COARSE_LOCATION:
|
||||
case OPSTR_FINE_LOCATION:
|
||||
return LOCATION;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown app op: " + op);
|
||||
}
|
||||
}
|
||||
|
||||
private Context mContext;
|
||||
private Map<UserHandle, Context> mUserContexts;
|
||||
private PackageManager mPkgManager;
|
||||
private AppOpsManager mAppOpsManager;
|
||||
|
||||
/**
|
||||
* Constructor for PermissionUsageHelper
|
||||
* @param context The context from which to derive the package information
|
||||
*/
|
||||
public PermissionUsageHelper(Context context) {
|
||||
mContext = context;
|
||||
mPkgManager = context.getPackageManager();
|
||||
mAppOpsManager = context.getSystemService(AppOpsManager.class);
|
||||
mUserContexts = Map.of(Process.myUserHandle(), mContext);
|
||||
}
|
||||
|
||||
private Context getUserContext(UserHandle user) {
|
||||
if (!(mUserContexts.containsKey(user))) {
|
||||
mUserContexts.put(user, mContext.createContextAsUser(user, 0));
|
||||
}
|
||||
return mUserContexts.get(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PermissionManager.getIndicatorAppOpUsageData
|
||||
*/
|
||||
public List<PermGroupUsage> getOpUsageData(boolean isMicMuted) {
|
||||
if (!shouldShowIndicators()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> ops = CAMERA_OPS;
|
||||
if (shouldShowLocationIndicator()) {
|
||||
ops.addAll(LOCATION_OPS);
|
||||
}
|
||||
if (!isMicMuted) {
|
||||
ops.addAll(MIC_OPS);
|
||||
}
|
||||
|
||||
Map<String, List<OpUsage>> rawUsages = getOpUsages(ops);
|
||||
Map<PackageAttribution, CharSequence> packagesWithAttributionLabels =
|
||||
getTrustedAttributions(rawUsages.get(MICROPHONE));
|
||||
|
||||
List<PermGroupUsage> usages = new ArrayList<>();
|
||||
List<String> usedPermGroups = new ArrayList<>(rawUsages.keySet());
|
||||
for (int permGroupNum = 0; permGroupNum < usedPermGroups.size(); permGroupNum++) {
|
||||
boolean isPhone = false;
|
||||
String permGroup = usedPermGroups.get(permGroupNum);
|
||||
if (permGroup.equals(OPSTR_PHONE_CALL_MICROPHONE)) {
|
||||
isPhone = true;
|
||||
permGroup = MICROPHONE;
|
||||
} else if (permGroup.equals(OPSTR_PHONE_CALL_CAMERA)) {
|
||||
isPhone = true;
|
||||
permGroup = CAMERA;
|
||||
}
|
||||
|
||||
int numUsages = rawUsages.get(permGroup).size();
|
||||
for (int usageNum = 0; usageNum < numUsages; usageNum++) {
|
||||
OpUsage usage = rawUsages.get(permGroup).get(usageNum);
|
||||
usages.add(new PermGroupUsage(usage.packageName, usage.uid, permGroup,
|
||||
usage.isRunning, isPhone,
|
||||
packagesWithAttributionLabels.get(usage.toPackageAttr())));
|
||||
}
|
||||
}
|
||||
|
||||
return usages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw usages from the system, and then parse out the ones that are not recent enough,
|
||||
* determine which permission group each belongs in, and removes duplicates (if the same app
|
||||
* uses multiple permissions of the same group). Stores the package name, attribution tag, user,
|
||||
* running/recent info, if the usage is a phone call, per permission group.
|
||||
*
|
||||
* @param opNames a list of op names to get usage for
|
||||
*
|
||||
* @return A map of permission group -> list of usages that are recent or running
|
||||
*/
|
||||
private Map<String, List<OpUsage>> getOpUsages(List<String> opNames) {
|
||||
List<AppOpsManager.PackageOps> ops;
|
||||
try {
|
||||
ops = mAppOpsManager.getPackagesForOps(opNames.toArray(new String[opNames.size()]));
|
||||
} catch (NullPointerException e) {
|
||||
// older builds might not support all the app-ops requested
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long recentThreshold = getRecentThreshold(now);
|
||||
long runningThreshold = getRunningThreshold(now);
|
||||
int opFlags = OP_FLAGS_ALL_TRUSTED;
|
||||
Map<String, Map<PackageAttribution, OpUsage>> usages = new ArrayMap<>();
|
||||
|
||||
int numPkgOps = ops.size();
|
||||
for (int pkgOpNum = 0; pkgOpNum < numPkgOps; pkgOpNum++) {
|
||||
AppOpsManager.PackageOps pkgOps = ops.get(pkgOpNum);
|
||||
int uid = pkgOps.getUid();
|
||||
UserHandle user = UserHandle.getUserHandleForUid(uid);
|
||||
String packageName = pkgOps.getPackageName();
|
||||
|
||||
int numOpEntries = pkgOps.getOps().size();
|
||||
for (int opEntryNum = 0; opEntryNum < numOpEntries; opEntryNum++) {
|
||||
AppOpsManager.OpEntry opEntry = pkgOps.getOps().get(opEntryNum);
|
||||
String op = opEntry.getOpStr();
|
||||
List<String> attributionTags =
|
||||
new ArrayList<>(opEntry.getAttributedOpEntries().keySet());
|
||||
|
||||
int numAttrEntries = opEntry.getAttributedOpEntries().size();
|
||||
for (int attrOpEntryNum = 0; attrOpEntryNum < numAttrEntries; attrOpEntryNum++) {
|
||||
String attributionTag = attributionTags.get(attrOpEntryNum);
|
||||
AppOpsManager.AttributedOpEntry attrOpEntry =
|
||||
opEntry.getAttributedOpEntries().get(attributionTag);
|
||||
|
||||
long lastAccessTime = attrOpEntry.getLastAccessTime(opFlags);
|
||||
if (lastAccessTime < recentThreshold) {
|
||||
continue;
|
||||
}
|
||||
if (!isUserSensitive(packageName, user, op)
|
||||
&& !isLocationProvider(packageName, user)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isRunning = attrOpEntry.isRunning()
|
||||
|| lastAccessTime >= runningThreshold;
|
||||
|
||||
OpUsage proxyUsage = null;
|
||||
AppOpsManager.OpEventProxyInfo proxy = attrOpEntry.getLastProxyInfo(opFlags);
|
||||
if (proxy != null && proxy.getPackageName() != null) {
|
||||
proxyUsage = new OpUsage(proxy.getPackageName(), proxy.getAttributionTag(),
|
||||
uid, lastAccessTime, isRunning, null);
|
||||
}
|
||||
|
||||
String permGroupName = getGroupForOp(op);
|
||||
OpUsage usage = new OpUsage(packageName, attributionTag, uid,
|
||||
lastAccessTime, isRunning, proxyUsage);
|
||||
|
||||
PackageAttribution packageAttr = usage.toPackageAttr();
|
||||
if (!usages.containsKey(permGroupName)) {
|
||||
ArrayMap<PackageAttribution, OpUsage> map = new ArrayMap<>();
|
||||
map.put(packageAttr, usage);
|
||||
usages.put(permGroupName, map);
|
||||
} else {
|
||||
Map<PackageAttribution, OpUsage> permGroupUsages =
|
||||
usages.get(permGroupName);
|
||||
if (!permGroupUsages.containsKey(packageAttr)) {
|
||||
permGroupUsages.put(packageAttr, usage);
|
||||
} else if (usage.lastAccessTime
|
||||
> permGroupUsages.get(packageAttr).lastAccessTime) {
|
||||
permGroupUsages.put(packageAttr, usage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, List<OpUsage>> flattenedUsages = new ArrayMap<>();
|
||||
List<String> permGroups = new ArrayList<>(usages.keySet());
|
||||
for (int i = 0; i < permGroups.size(); i++) {
|
||||
String permGroupName = permGroups.get(i);
|
||||
flattenedUsages.put(permGroupName, new ArrayList<>(usages.get(permGroupName).values()));
|
||||
}
|
||||
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) {
|
||||
ArrayMap<PackageAttribution, CharSequence> attributions = new ArrayMap<>();
|
||||
if (usages == null) {
|
||||
return attributions;
|
||||
}
|
||||
Set<List<OpUsage>> proxyChains = getProxyChains(usages);
|
||||
Map<Pair<String, UserHandle>, CharSequence> trustedLabels = getTrustedAttributionLabels();
|
||||
|
||||
|
||||
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)
|
||||
for (int usageNum = 0; usageNum < usages.size(); usageNum++) {
|
||||
OpUsage usage = usages.get(usageNum);
|
||||
PackageAttribution usageAttr = usage.toPackageAttr();
|
||||
if (usage.proxy == null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// find all possible starting points for chains
|
||||
for (int i = 0; i < usages.size(); i++) {
|
||||
OpUsage usage = usages.get(i);
|
||||
}
|
||||
|
||||
/*
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO ntmyren: create JavaDoc and copy trustedAttrsLiveData from ReviewOngoingUsageLiveData
|
||||
private Map<Pair<String, UserHandle>, CharSequence> getTrustedAttributionLabels() {
|
||||
return new ArrayMap<>();
|
||||
}
|
||||
|
||||
private boolean isUserSensitive(String packageName, UserHandle user, String op) {
|
||||
if (op.equals(OPSTR_PHONE_CALL_CAMERA) || op.equals(OPSTR_PHONE_CALL_MICROPHONE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (opToPermission(op) == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int permFlags = mPkgManager.getPermissionFlags(opToPermission(op), packageName, user);
|
||||
return (permFlags & FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
|
||||
}
|
||||
|
||||
private boolean isLocationProvider(String packageName, UserHandle user) {
|
||||
return getUserContext(user)
|
||||
.getSystemService(LocationManager.class).isProviderPackage(packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the usage of an App op by a particular package and attribution
|
||||
*/
|
||||
private static class OpUsage {
|
||||
|
||||
public final String packageName;
|
||||
public final String attributionTag;
|
||||
public final int uid;
|
||||
public final long lastAccessTime;
|
||||
public final OpUsage proxy;
|
||||
public final boolean isRunning;
|
||||
|
||||
OpUsage(String packageName, String attributionTag, int uid, long lastAccessTime,
|
||||
boolean isRunning, OpUsage proxy) {
|
||||
this.isRunning = isRunning;
|
||||
this.packageName = packageName;
|
||||
this.attributionTag = attributionTag;
|
||||
this.uid = uid;
|
||||
this.lastAccessTime = lastAccessTime;
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
public PackageAttribution toPackageAttr() {
|
||||
return new PackageAttribution(packageName, attributionTag, uid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A unique identifier for one package attribution, made up of attribution tag, package name
|
||||
* and user
|
||||
*/
|
||||
private static class PackageAttribution {
|
||||
public final String packageName;
|
||||
public final String attributionTag;
|
||||
public final int uid;
|
||||
|
||||
PackageAttribution(String packageName, String attributionTag, int uid) {
|
||||
this.packageName = packageName;
|
||||
this.attributionTag = attributionTag;
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof PackageAttribution)) {
|
||||
return false;
|
||||
}
|
||||
PackageAttribution other = (PackageAttribution) obj;
|
||||
return Objects.equals(packageName, other.packageName) && Objects.equals(attributionTag,
|
||||
other.attributionTag) && Objects.equals(uid, other.uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(packageName, attributionTag, uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user