Stack Trace sampling and reporting.

Sampling is done in cycles where length of single cycle is
configurable server side.
During one sampling cycle only one stack trace / message is reported.

Two sampling strategies are used in this CL.

1. Sampling uniformly packages and appops, and waiting only for
messages coming from sampled package (all other packages are
silenced to presereve system resources). All appops are recorded,
but sampled appop has preference and replaces any previous stack
trace / message. This strategy is created to focus on all packages
and all apps uniformly.

2. Creating list of rarely used packages - packages which were not
using any dangerous permissions for more than a week. Any newly
installed app is automatically added to this list, including apps
which were updated. Whenever message is received for rarely used
package, there is 50% probability that this message will replace
whatever is being sampled in current sampling cycle. This strategy
is created to focus on newly installed apps, on apps which are used
infrequently and on apps which use dangerous permissions infrequently.

Bug:136134050
Test: atest android.app.appops.cts.RuntimeMessageCollectionTest
Change-Id: I3109b38bf0482481acf945d5441a26bfe704c9b5
This commit is contained in:
Stanislav Zholnin
2020-01-20 14:03:06 +00:00
parent f11642dbde
commit 90516b9200
12 changed files with 1075 additions and 27 deletions

View File

@@ -6426,9 +6426,13 @@ package android.app {
public class StatusBarManager {
}
public final class SyncNotedAppOp {
public final class SyncNotedAppOp implements android.os.Parcelable {
ctor public SyncNotedAppOp(@IntRange(from=0L) int, @Nullable String);
method public int describeContents();
method @Nullable public String getFeatureId();
method @NonNull public String getOp();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.SyncNotedAppOp> CREATOR;
}
@Deprecated public class TabActivity extends android.app.ActivityGroup {

View File

@@ -364,6 +364,7 @@ package android.app {
}
public class AppOpsManager {
method @Nullable @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public android.app.RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage();
method @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public void getHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method public static String[] getOpStrs();
method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, @NonNull String, @Nullable java.lang.String...);
@@ -680,6 +681,19 @@ package android.app {
method public void setNotificationAssistantAccessGranted(@Nullable android.content.ComponentName, boolean);
}
public final class RuntimeAppOpAccessMessage implements android.os.Parcelable {
ctor public RuntimeAppOpAccessMessage(@IntRange(from=0L) int, @IntRange(from=0L) int, @NonNull String, @Nullable String, @NonNull String, int);
method public int describeContents();
method @Nullable public String getFeatureId();
method @NonNull public String getMessage();
method @NonNull public String getOp();
method @NonNull public String getPackageName();
method public int getSamplingStrategy();
method @IntRange(from=0L) public int getUid();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.RuntimeAppOpAccessMessage> CREATOR;
}
public class SearchManager implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
method public void launchAssist(@Nullable android.os.Bundle);
}

View File

@@ -164,6 +164,7 @@ package android.app {
public class AppOpsManager {
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void addHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOps);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void clearHistory();
method @Nullable @RequiresPermission("android.permission.GET_APP_OPS_STATS") public android.app.RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage();
method @RequiresPermission("android.permission.GET_APP_OPS_STATS") public void getHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void getHistoricalOpsFromDiskRaw(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method public static int getNumOps();
@@ -447,6 +448,19 @@ package android.app {
method public android.graphics.Rect getSourceRectHint();
}
public final class RuntimeAppOpAccessMessage implements android.os.Parcelable {
ctor public RuntimeAppOpAccessMessage(@IntRange(from=0L) int, @IntRange(from=0L) int, @NonNull String, @Nullable String, @NonNull String, int);
method public int describeContents();
method @Nullable public String getFeatureId();
method @NonNull public String getMessage();
method @NonNull public String getOp();
method @NonNull public String getPackageName();
method public int getSamplingStrategy();
method @IntRange(from=0L) public int getUid();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.RuntimeAppOpAccessMessage> CREATOR;
}
public class StatusBarManager {
method public void collapsePanels();
method public void expandNotificationsPanel();

View File

@@ -16,6 +16,10 @@
package android.app;
import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__DEFAULT;
import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED;
import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM;
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
@@ -48,6 +52,8 @@ import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -63,6 +69,7 @@ import com.android.internal.app.IAppOpsAsyncNotedCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.MessageSamplingConfig;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.ZygoteInit;
import com.android.internal.util.ArrayUtils;
@@ -141,6 +148,13 @@ public class AppOpsManager {
@UnsupportedAppUsage
final IAppOpsService mService;
/**
* Service for the application context, to be used by static methods via
* {@link #getService()}
*/
@GuardedBy("sLock")
static IAppOpsService sService;
@GuardedBy("mModeWatchers")
private final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers =
new ArrayMap<>();
@@ -159,6 +173,50 @@ public class AppOpsManager {
@GuardedBy("sLock")
private static @Nullable AppOpsCollector sNotedAppOpsCollector;
/**
* Additional collector that collect accesses and forwards a few of them them via
* {@link IAppOpsService#reportRuntimeAppOpAccessMessageAndGetConfig}.
*/
private static AppOpsCollector sMessageCollector =
new AppOpsCollector() {
@Override
public void onNoted(@NonNull SyncNotedAppOp op) {
reportStackTraceIfNeeded(op);
}
@Override
public void onAsyncNoted(@NonNull AsyncNotedAppOp asyncOp) {
// collected directly in AppOpsService
}
@Override
public void onSelfNoted(@NonNull SyncNotedAppOp op) {
reportStackTraceIfNeeded(op);
}
private void reportStackTraceIfNeeded(@NonNull SyncNotedAppOp op) {
if (sConfig.getSampledOpCode() == OP_NONE
&& sConfig.getExpirationTimeSinceBootMillis()
>= SystemClock.elapsedRealtime()) {
return;
}
MessageSamplingConfig config = sConfig;
if (leftCircularDistance(strOpToOp(op.getOp()), config.getSampledOpCode(),
_NUM_OP) <= config.getAcceptableLeftDistance()
|| config.getExpirationTimeSinceBootMillis()
< SystemClock.elapsedRealtime()) {
String stackTrace = getFormattedStackTrace();
try {
sConfig = getService().reportRuntimeAppOpAccessMessageAndGetConfig(
ActivityThread.currentOpPackageName(), op, stackTrace);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
}
};
static IBinder sClientId;
/**
@@ -550,7 +608,6 @@ public class AppOpsManager {
})
public @interface OpFlags {}
/** @hide */
public static final String getFlagName(@OpFlags int flag) {
switch (flag) {
@@ -569,6 +626,18 @@ public class AppOpsManager {
}
}
/**
* Strategies used for message sampling
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"RUNTIME_APP_OPS_ACCESS__SAMPLING_STRATEGY__"}, value = {
RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__DEFAULT,
RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM,
RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED
})
public @interface SamplingStrategy {}
private static final int UID_STATE_OFFSET = 31;
private static final int FLAGS_MASK = 0xFFFFFFFF;
@@ -2225,6 +2294,10 @@ public class AppOpsManager {
}
}
/** Config used to control app ops access messages sampling */
private static MessageSamplingConfig sConfig =
new MessageSamplingConfig(OP_NONE, 0, 0);
/** @hide */
public static final String KEY_HISTORICAL_OPS = "historical_ops";
@@ -7268,6 +7341,17 @@ public class AppOpsManager {
}
}
/** @hide */
private static IAppOpsService getService() {
synchronized (sLock) {
if (sService == null) {
sService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
}
return sService;
}
}
/**
* @deprecated use {@link #startOp(String, int, String, String, String)} instead
*/
@@ -7614,6 +7698,7 @@ public class AppOpsManager {
sNotedAppOpsCollector.onSelfNoted(new SyncNotedAppOp(op, featureId));
}
}
sMessageCollector.onSelfNoted(new SyncNotedAppOp(op, featureId));
}
/**
@@ -7764,6 +7849,10 @@ public class AppOpsManager {
}
}
}
for (int code = notedAppOps.nextSetBit(0); code != -1;
code = notedAppOps.nextSetBit(code + 1)) {
sMessageCollector.onNoted(new SyncNotedAppOp(code, featureId));
}
}
}
}
@@ -7958,10 +8047,13 @@ public class AppOpsManager {
StringBuilder sb = new StringBuilder();
for (int i = firstInteresting; i <= lastInteresting; i++) {
sb.append(trace[i]);
if (i != lastInteresting) {
if (i != firstInteresting) {
sb.append('\n');
}
if (sb.length() + trace[i].toString().length() > 600) {
break;
}
sb.append(trace[i]);
}
return sb.toString();
@@ -8088,6 +8180,22 @@ public class AppOpsManager {
}
}
/**
* Pulls current AppOps access report and picks package and op to watch for next access report
*
* @hide
*/
@SystemApi
@TestApi
@RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
try {
return mService.collectRuntimeAppOpAccessMessage();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns all supported operation names.
* @hide
@@ -8297,4 +8405,12 @@ public class AppOpsManager {
return AppOpsManager.MODE_DEFAULT;
}
/**
* Calculate left circular distance for two numbers modulo size.
* @hide
*/
public static int leftCircularDistance(int from, int to, int size) {
return (to + size - from) % size;
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2019 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.app;
parcelable RuntimeAppOpAccessMessage;

View File

@@ -0,0 +1,245 @@
/*
* Copyright (C) 2019 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.app;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.annotations.Immutable;
import com.android.internal.util.DataClass;
/**
* Message for noted runtime permission access.
* @hide
*/
@Immutable
@TestApi
@SystemApi
/*@DataClass(genConstructor = false)
@DataClass.Suppress("getOpCode")*/
public final class RuntimeAppOpAccessMessage implements Parcelable {
/** Uid of package for which runtime app op access message was collected */
private final @IntRange(from = 0L) int mUid;
/** Op code of operation access which was collected */
private final @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int mOpCode;
/** Name of package for which runtime app op access message was collected */
private final @NonNull String mPackageName;
/** Feature of package for which runtime app op access message was collected */
private final @Nullable String mFeatureId;
/** Message collected (including stacktrace for synchronous ops) */
private final @NonNull String mMessage;
/** Sampling strategy used to collect this message. */
private final @AppOpsManager.SamplingStrategy int mSamplingStrategy;
public @NonNull String getOp() {
return AppOpsManager.opToPublicName(mOpCode);
}
/**
* Creates a new RuntimeAppOpAccessMessage.
*
* @param uid
* Uid of package for which runtime app op access message was collected
* @param opCode
* Op code of operation access which was collected
* @param packageName
* Name of package for which runtime app op access message was collected
* @param featureId
* Feature of package for which runtime app op access message was collected
* @param message
* Message collected (including stacktrace for synchronous ops)
* @param samplingStrategy
* Sampling strategy used to collect this message.
*/
@DataClass.Generated.Member
public RuntimeAppOpAccessMessage(
@IntRange(from = 0L) int uid,
@IntRange(from = 0L) int opCode,
@NonNull String packageName,
@Nullable String featureId,
@NonNull String message,
@AppOpsManager.SamplingStrategy int samplingStrategy) {
this.mUid = uid;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mUid,
"from", 0L);
this.mOpCode = opCode;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mOpCode,
"from", 0L,
"to", AppOpsManager._NUM_OP - 1);
this.mPackageName = packageName;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mPackageName);
this.mFeatureId = featureId;
this.mMessage = message;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mMessage);
this.mSamplingStrategy = samplingStrategy;
com.android.internal.util.AnnotationValidations.validate(
AppOpsManager.SamplingStrategy.class, null, mSamplingStrategy);
// onConstructed(); // You can define this method to get a callback
}
// Code below generated by codegen v1.0.14.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/RuntimeAppOpAccessMessage.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
/**
* Uid of package for which runtime app op access message was collected
*/
@DataClass.Generated.Member
public @IntRange(from = 0L) int getUid() {
return mUid;
}
/**
* Name of package for which runtime app op access message was collected
*/
@DataClass.Generated.Member
public @NonNull String getPackageName() {
return mPackageName;
}
/**
* Feature of package for which runtime app op access message was collected
*/
@DataClass.Generated.Member
public @Nullable String getFeatureId() {
return mFeatureId;
}
/**
* Message collected (including stacktrace for synchronous ops)
*/
@DataClass.Generated.Member
public @NonNull String getMessage() {
return mMessage;
}
/**
* Sampling strategy used to collect this message.
*/
@DataClass.Generated.Member
public @AppOpsManager.SamplingStrategy int getSamplingStrategy() {
return mSamplingStrategy;
}
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
if (mFeatureId != null) flg |= 0x8;
dest.writeByte(flg);
dest.writeInt(mUid);
dest.writeInt(mOpCode);
dest.writeString(mPackageName);
if (mFeatureId != null) dest.writeString(mFeatureId);
dest.writeString(mMessage);
dest.writeInt(mSamplingStrategy);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
/* package-private */ RuntimeAppOpAccessMessage(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
int uid = in.readInt();
int opCode = in.readInt();
String packageName = in.readString();
String featureId = (flg & 0x8) == 0 ? null : in.readString();
String message = in.readString();
int samplingStrategy = in.readInt();
this.mUid = uid;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mUid,
"from", 0L);
this.mOpCode = opCode;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mOpCode,
"from", 0L,
"to", AppOpsManager._NUM_OP - 1);
this.mPackageName = packageName;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mPackageName);
this.mFeatureId = featureId;
this.mMessage = message;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mMessage);
this.mSamplingStrategy = samplingStrategy;
com.android.internal.util.AnnotationValidations.validate(
AppOpsManager.SamplingStrategy.class, null, mSamplingStrategy);
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<RuntimeAppOpAccessMessage> CREATOR
= new Parcelable.Creator<RuntimeAppOpAccessMessage>() {
@Override
public RuntimeAppOpAccessMessage[] newArray(int size) {
return new RuntimeAppOpAccessMessage[size];
}
@Override
public RuntimeAppOpAccessMessage createFromParcel(@NonNull Parcel in) {
return new RuntimeAppOpAccessMessage(in);
}
};
/*@DataClass.Generated(
time = 1581517099127L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/app/RuntimeAppOpAccessMessage.java",
inputSignatures = "private final @android.annotation.IntRange(from=0L) int mUid\nprivate final @android.annotation.IntRange(from=0L, to=AppOpsManager._NUM_OP - 1) int mOpCode\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.app.AppOpsManager.SamplingStrategy int mSamplingStrategy\npublic @android.annotation.NonNull java.lang.String getOp()\nclass RuntimeAppOpAccessMessage extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false)")*/
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2019 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.app;
parcelable SyncNotedAppOp;

View File

@@ -19,8 +19,10 @@ package android.app;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcelable;
import com.android.internal.annotations.Immutable;
import com.android.internal.util.DataClass;
/**
* Description of an app-op that was noted for the current process.
@@ -32,10 +34,35 @@ import com.android.internal.annotations.Immutable;
* itself}.
*/
@Immutable
public final class SyncNotedAppOp {
private final int mOpCode;
/*@DataClass(
genEqualsHashCode = true,
genConstructor = false
)
@DataClass.Suppress("getOpCode")*/
public final class SyncNotedAppOp implements Parcelable {
/** op code of synchronous appop noted */
private final @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int mOpCode;
/** featureId of synchronous appop noted */
private final @Nullable String mFeatureId;
/**
* Creates a new SyncNotedAppOp.
*
* @param opCode
* op code of synchronous appop noted
* @param featureId
* featureId of synchronous appop noted
*/
public SyncNotedAppOp(@IntRange(from = 0L) int opCode, @Nullable String featureId) {
this.mOpCode = opCode;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mOpCode,
"from", 0,
"to", AppOpsManager._NUM_OP - 1);
this.mFeatureId = featureId;
}
/**
* @return The op that was noted.
*/
@@ -43,37 +70,118 @@ public final class SyncNotedAppOp {
return AppOpsManager.opToPublicName(mOpCode);
}
// Code below generated by codegen v1.0.14.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/SyncNotedAppOp.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
/**
* @return The {@link android.content.Context#createFeatureContext Feature} in the app
* featureId of synchronous appop noted
*/
@DataClass.Generated.Member
public @Nullable String getFeatureId() {
return mFeatureId;
}
/**
* Create a new sync op description
*
* @param opCode The op that was noted
*
* @hide
*/
public SyncNotedAppOp(@IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int opCode,
@Nullable String featureId) {
mOpCode = opCode;
mFeatureId = featureId;
@Override
@DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(SyncNotedAppOp other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
SyncNotedAppOp that = (SyncNotedAppOp) o;
//noinspection PointlessBooleanExpression
return true
&& mOpCode == that.mOpCode
&& java.util.Objects.equals(mFeatureId, that.mFeatureId);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof SyncNotedAppOp)) {
return false;
@DataClass.Generated.Member
public int hashCode() {
// You can override field hashCode logic by defining methods like:
// int fieldNameHashCode() { ... }
int _hash = 1;
_hash = 31 * _hash + mOpCode;
_hash = 31 * _hash + java.util.Objects.hashCode(mFeatureId);
return _hash;
}
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
if (mFeatureId != null) flg |= 0x2;
dest.writeByte(flg);
dest.writeInt(mOpCode);
if (mFeatureId != null) dest.writeString(mFeatureId);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
/* package-private */ SyncNotedAppOp(@NonNull android.os.Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
int opCode = in.readInt();
String featureId = (flg & 0x2) == 0 ? null : in.readString();
this.mOpCode = opCode;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mOpCode,
"from", 0,
"to", AppOpsManager._NUM_OP - 1);
this.mFeatureId = featureId;
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<SyncNotedAppOp> CREATOR
= new Parcelable.Creator<SyncNotedAppOp>() {
@Override
public SyncNotedAppOp[] newArray(int size) {
return new SyncNotedAppOp[size];
}
return mOpCode == ((SyncNotedAppOp) other).mOpCode;
}
@Override
public SyncNotedAppOp createFromParcel(@NonNull android.os.Parcel in) {
return new SyncNotedAppOp(in);
}
};
/*@DataClass.Generated(
time = 1579188889960L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/app/SyncNotedAppOp.java",
inputSignatures = "private final @android.annotation.IntRange(from=0L, to=AppOpsManager._NUM_OP - 1) int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\npublic @android.annotation.NonNull java.lang.String getOp()\npublic @android.annotation.SystemApi int getOpCode()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genConstructor=false)")*/
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
@Override
public int hashCode() {
return mOpCode;
}
}

View File

@@ -18,6 +18,8 @@ package com.android.internal.app;
import android.app.AppOpsManager;
import android.app.AsyncNotedAppOp;
import android.app.SyncNotedAppOp;
import android.app.RuntimeAppOpAccessMessage;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
import android.os.RemoteCallback;
@@ -25,6 +27,7 @@ import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsAsyncNotedCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.MessageSamplingConfig;
interface IAppOpsService {
// These methods are also called by native code, so must
@@ -54,6 +57,9 @@ interface IAppOpsService {
// Remaining methods are only used in Java.
int checkPackage(int uid, String packageName);
RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage();
MessageSamplingConfig reportRuntimeAppOpAccessMessageAndGetConfig(String packageName,
in SyncNotedAppOp appOp, String message);
@UnsupportedAppUsage
List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
@UnsupportedAppUsage

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2019 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 com.android.internal.app;
parcelable MessageSamplingConfig;

View File

@@ -0,0 +1,187 @@
/*
* Copyright (C) 2019 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 com.android.internal.app;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.annotations.Immutable;
import com.android.internal.util.DataClass;
/**
* Configuration for AppOps access messages sampling.
*/
@Immutable
/*@DataClass*/
public final class MessageSamplingConfig implements Parcelable {
/** Op code targeted during current sampling session */
private final @IntRange(from = -1L, to = AppOpsManager._NUM_OP - 1) int mSampledOpCode;
/** Range of ops which should be reported during current sampling session */
private final @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int mAcceptableLeftDistance;
/** Expiration time for this sampling config */
private final @IntRange(from = 0L) long mExpirationTimeSinceBootMillis;
// Code below generated by codegen v1.0.14.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/app/MessageSamplingConfig.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
/**
* Creates a new MessageSamplingConfig.
*
* @param sampledOpCode
* Op code targeted during current sampling session
* @param acceptableLeftDistance
* Range of ops which should be reported during current sampling session
* @param expirationTimeSinceBootMillis
* Expiration time for this sampling config
*/
@DataClass.Generated.Member
public MessageSamplingConfig(
@IntRange(from = -1L, to = AppOpsManager._NUM_OP - 1) int sampledOpCode,
@IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int acceptableLeftDistance,
@IntRange(from = 0L) long expirationTimeSinceBootMillis) {
this.mSampledOpCode = sampledOpCode;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mSampledOpCode,
"from", -1L,
"to", AppOpsManager._NUM_OP - 1);
this.mAcceptableLeftDistance = acceptableLeftDistance;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mAcceptableLeftDistance,
"from", 0L,
"to", AppOpsManager._NUM_OP - 1);
this.mExpirationTimeSinceBootMillis = expirationTimeSinceBootMillis;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mExpirationTimeSinceBootMillis,
"from", 0L);
// onConstructed(); // You can define this method to get a callback
}
/**
* Op code targeted during current sampling session
*/
@DataClass.Generated.Member
public @IntRange(from = -1L, to = AppOpsManager._NUM_OP - 1) int getSampledOpCode() {
return mSampledOpCode;
}
/**
* Range of ops which should be reported during current sampling session
*/
@DataClass.Generated.Member
public @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int getAcceptableLeftDistance() {
return mAcceptableLeftDistance;
}
/**
* Expiration time for this sampling config
*/
@DataClass.Generated.Member
public @IntRange(from = 0L) long getExpirationTimeSinceBootMillis() {
return mExpirationTimeSinceBootMillis;
}
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
dest.writeInt(mSampledOpCode);
dest.writeInt(mAcceptableLeftDistance);
dest.writeLong(mExpirationTimeSinceBootMillis);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
/* package-private */ MessageSamplingConfig(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
int sampledOpCode = in.readInt();
int acceptableLeftDistance = in.readInt();
long expirationTimeSinceBootMillis = in.readLong();
this.mSampledOpCode = sampledOpCode;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mSampledOpCode,
"from", -1L,
"to", AppOpsManager._NUM_OP - 1);
this.mAcceptableLeftDistance = acceptableLeftDistance;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mAcceptableLeftDistance,
"from", 0L,
"to", AppOpsManager._NUM_OP - 1);
this.mExpirationTimeSinceBootMillis = expirationTimeSinceBootMillis;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mExpirationTimeSinceBootMillis,
"from", 0L);
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<MessageSamplingConfig> CREATOR
= new Parcelable.Creator<MessageSamplingConfig>() {
@Override
public MessageSamplingConfig[] newArray(int size) {
return new MessageSamplingConfig[size];
}
@Override
public MessageSamplingConfig createFromParcel(@NonNull Parcel in) {
return new MessageSamplingConfig(in);
}
};
/*@DataClass.Generated(
time = 1580691255495L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/com/android/internal/app/MessageSamplingConfig.java",
inputSignatures = "private final @android.annotation.IntRange(from=-1L, to=AppOpsManager._NUM_OP - 1) int mSampledOpCode\nprivate final @android.annotation.IntRange(from=0L, to=AppOpsManager._NUM_OP - 1) int mAcceptableLeftDistance\nprivate final @android.annotation.IntRange(from=0L) long mExpirationTimeSinceBootMillis\nclass MessageSamplingConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass")*/
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}

View File

@@ -34,6 +34,7 @@ import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.OP_PLAY_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
@@ -52,11 +53,14 @@ import static android.app.AppOpsManager.extractUidStateFromKey;
import static android.app.AppOpsManager.makeKey;
import static android.app.AppOpsManager.modeToName;
import static android.app.AppOpsManager.opToName;
import static android.app.AppOpsManager.opToPublicName;
import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED;
import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
@@ -79,6 +83,8 @@ import android.app.AppOpsManager.OpFlags;
import android.app.AppOpsManagerInternal;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
import android.app.AsyncNotedAppOp;
import android.app.RuntimeAppOpAccessMessage;
import android.app.SyncNotedAppOp;
import android.compat.Compatibility;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -87,6 +93,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
@@ -137,6 +144,7 @@ import com.android.internal.app.IAppOpsAsyncNotedCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.MessageSamplingConfig;
import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -147,6 +155,7 @@ import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.pm.PackageList;
import libcore.util.EmptyArray;
@@ -163,6 +172,8 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -172,6 +183,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
public class AppOpsService extends IAppOpsService.Stub {
static final String TAG = "AppOps";
@@ -308,6 +321,34 @@ public class AppOpsService extends IAppOpsService.Stub {
private ActivityManagerInternal mActivityManagerInternal;
/** Package sampled for message collection in the current session */
@GuardedBy("this")
private String mSampledPackage = null;
/** Appop sampled for message collection in the current session */
@GuardedBy("this")
private int mSampledAppOpCode = OP_NONE;
/** Maximum distance for appop to be considered for message collection in the current session */
@GuardedBy("this")
private int mAcceptableLeftDistance = 0;
/** Number of messages collected for sampled package and appop in the current session */
@GuardedBy("this")
private float mMessagesCollectedCount;
/** List of rarely used packages priorities for message collection */
@GuardedBy("this")
private ArraySet<String> mRarelyUsedPackages = new ArraySet<>();
/** Sampling strategy used for current session */
@GuardedBy("this")
@AppOpsManager.SamplingStrategy
private int mSamplingStrategy;
/** Last runtime permission access message collected and ready for reporting */
@GuardedBy("this")
private RuntimeAppOpAccessMessage mCollectedRuntimePermissionMessage;
/**
* An unsynchronized pool of {@link OpEventProxyInfo} objects.
*/
@@ -1535,6 +1576,38 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}, packageSuspendFilter);
final IntentFilter packageAddedFilter = new IntentFilter();
packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageAddedFilter.addDataScheme("package");
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final Uri data = intent.getData();
final String packageName = data.getSchemeSpecificPart();
PackageInfo pi = LocalServices.getService(
PackageManagerInternal.class).getPackageInfo(packageName,
PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
if (isSamplingTarget(pi)) {
synchronized (this) {
mRarelyUsedPackages.add(packageName);
}
}
}
}, packageAddedFilter);
List<String> packageNames = getPackageNamesForSampling();
synchronized (this) {
resamplePackageAndAppOpLocked(packageNames);
}
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
initializeRarelyUsedPackagesList(new ArraySet<>(packageNames));
}
});
PackageManagerInternal packageManagerInternal = LocalServices.getService(
PackageManagerInternal.class);
packageManagerInternal.setExternalSourcesPolicy(
@@ -3030,6 +3103,9 @@ public class AppOpsService extends IAppOpsService.Stub {
featureId, message, System.currentTimeMillis());
final boolean[] wasNoteForwarded = {false};
reportRuntimeAppOpAccessMessageAsyncLocked(uid, packageName, opCode, featureId,
message);
if (callbacks != null) {
callbacks.broadcast((cb) -> {
try {
@@ -5494,6 +5570,227 @@ public class AppOpsService extends IAppOpsService.Stub {
mHistoricalRegistry.clearHistory();
}
/**
* Report runtime access to AppOp together with message (including stack trace)
*
* @param packageName The package which reported the op
* @param notedAppOp contains code of op and featureId provided by developer
* @param message Message describing AppOp access (can be stack trace)
*
* @return Config for future sampling to reduce amount of reporting
*/
@Override
public MessageSamplingConfig reportRuntimeAppOpAccessMessageAndGetConfig(
String packageName, SyncNotedAppOp notedAppOp, String message) {
int uid = Binder.getCallingUid();
Objects.requireNonNull(packageName);
synchronized (this) {
switchPackageIfRarelyUsedLocked(packageName);
if (!packageName.equals(mSampledPackage)) {
return new MessageSamplingConfig(OP_NONE, 0,
Instant.now().plus(1, ChronoUnit.HOURS).toEpochMilli());
}
Objects.requireNonNull(notedAppOp);
Objects.requireNonNull(message);
reportRuntimeAppOpAccessMessageInternalLocked(uid, packageName,
AppOpsManager.strOpToOp(notedAppOp.getOp()),
notedAppOp.getFeatureId(), message);
return new MessageSamplingConfig(mSampledAppOpCode, mAcceptableLeftDistance,
Instant.now().plus(1, ChronoUnit.HOURS).toEpochMilli());
}
}
/**
* Report runtime access to AppOp together with message (entry point for reporting
* asynchronous access)
* @param uid Uid of the package which reported the op
* @param packageName The package which reported the op
* @param opCode Code of AppOp
* @param featureId FeautreId of AppOp reported
* @param message Message describing AppOp access (can be stack trace)
*/
private void reportRuntimeAppOpAccessMessageAsyncLocked(int uid,
@NonNull String packageName, int opCode, @Nullable String featureId,
@NonNull String message) {
switchPackageIfRarelyUsedLocked(packageName);
if (!Objects.equals(mSampledPackage, packageName)) {
return;
}
reportRuntimeAppOpAccessMessageInternalLocked(uid, packageName, opCode, featureId, message);
}
/**
* Decides whether reported message is within the range of watched AppOps and picks it for
* reporting uniformly at random across all received messages.
*/
private void reportRuntimeAppOpAccessMessageInternalLocked(int uid,
@NonNull String packageName, int opCode, @Nullable String featureId,
@NonNull String message) {
int newLeftDistance = AppOpsManager.leftCircularDistance(opCode,
mSampledAppOpCode, _NUM_OP);
if (mAcceptableLeftDistance < newLeftDistance) {
return;
}
if (mAcceptableLeftDistance > newLeftDistance) {
mAcceptableLeftDistance = newLeftDistance;
mMessagesCollectedCount = 0.0f;
}
mMessagesCollectedCount += 1.0f;
if (ThreadLocalRandom.current().nextFloat() <= 1.0f / mMessagesCollectedCount) {
mCollectedRuntimePermissionMessage = new RuntimeAppOpAccessMessage(uid, opCode,
packageName, featureId, message, mSamplingStrategy);
}
return;
}
/** Pulls current AppOps access report and resamples package and app op to watch */
@Override
public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
RuntimeAppOpAccessMessage result;
List<String> packageNames = getPackageNamesForSampling();
synchronized (this) {
result = mCollectedRuntimePermissionMessage;
resamplePackageAndAppOpLocked(packageNames);
}
return result;
}
/**
* Checks if package is in the list of rarely used package and starts watching the new package
* to collect incoming message.
* @param packageName
*/
private void switchPackageIfRarelyUsedLocked(@NonNull String packageName) {
if (mRarelyUsedPackages.contains(packageName)) {
mRarelyUsedPackages.remove(packageName);
if (ThreadLocalRandom.current().nextFloat() < 0.5f) {
mSamplingStrategy = RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED;
resampleAppOpForPackageLocked(packageName);
}
}
}
/** Resamples package and appop to watch from the list provided. */
private void resamplePackageAndAppOpLocked(@NonNull List<String> packageNames) {
if (!packageNames.isEmpty()) {
mSamplingStrategy = RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM;
resampleAppOpForPackageLocked(packageNames.get(
ThreadLocalRandom.current().nextInt(packageNames.size())));
}
}
/** Resamples appop for the chosen package and initializes sampling state */
private void resampleAppOpForPackageLocked(@NonNull String packageName) {
mMessagesCollectedCount = 0.0f;
mSampledAppOpCode = ThreadLocalRandom.current().nextInt(_NUM_OP);
mAcceptableLeftDistance = _NUM_OP;
mSampledPackage = packageName;
mCollectedRuntimePermissionMessage = null;
}
/**
* Creates list of rarely used packages - packages which were not used over last week or
* which declared but did not use permissions over last week.
* */
private void initializeRarelyUsedPackagesList(@NonNull ArraySet<String> candidates) {
AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
List<String> runtimeAppOpsList = getRuntimeAppOpsList();
AppOpsManager.HistoricalOpsRequest histOpsRequest =
new AppOpsManager.HistoricalOpsRequest.Builder(
Instant.now().minus(7, ChronoUnit.DAYS).toEpochMilli(),
Long.MAX_VALUE).setOpNames(runtimeAppOpsList).setFlags(
OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED).build();
appOps.getHistoricalOps(histOpsRequest, AsyncTask.THREAD_POOL_EXECUTOR,
new Consumer<HistoricalOps>() {
@Override
public void accept(HistoricalOps histOps) {
int uidCount = histOps.getUidCount();
for (int uidIdx = 0; uidIdx < uidCount; uidIdx++) {
final AppOpsManager.HistoricalUidOps uidOps = histOps.getUidOpsAt(
uidIdx);
int pkgCount = uidOps.getPackageCount();
for (int pkgIdx = 0; pkgIdx < pkgCount; pkgIdx++) {
String packageName = uidOps.getPackageOpsAt(
pkgIdx).getPackageName();
if (!candidates.contains(packageName)) {
continue;
}
AppOpsManager.HistoricalPackageOps packageOps =
uidOps.getPackageOpsAt(pkgIdx);
if (packageOps.getOpCount() != 0) {
candidates.remove(packageName);
}
}
}
synchronized (this) {
mRarelyUsedPackages = candidates;
}
}
});
}
/** List of app ops related to runtime permissions */
private List<String> getRuntimeAppOpsList() {
ArrayList<String> result = new ArrayList();
for (int i = 0; i < _NUM_OP; i++) {
if (shouldCollectNotes(i)) {
result.add(opToPublicName(i));
}
}
return result;
}
/** Returns list of packages to be used for package sampling */
private @NonNull List<String> getPackageNamesForSampling() {
List<String> packageNames = new ArrayList<>();
PackageManagerInternal packageManagerInternal = LocalServices.getService(
PackageManagerInternal.class);
PackageList packages = packageManagerInternal.getPackageList();
for (String packageName : packages.getPackageNames()) {
PackageInfo pkg = packageManagerInternal.getPackageInfo(packageName,
PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
if (isSamplingTarget(pkg)) {
packageNames.add(pkg.packageName);
}
}
return packageNames;
}
/** Checks whether package should be included in sampling pool */
private boolean isSamplingTarget(@Nullable PackageInfo pkg) {
if (pkg == null) {
return false;
}
if (pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.Q) {
return false;
}
String[] requestedPermissions = pkg.requestedPermissions;
if (requestedPermissions == null) {
return false;
}
for (String permission : requestedPermissions) {
PermissionInfo permissionInfo;
try {
permissionInfo = mContext.getPackageManager().getPermissionInfo(permission, 0);
} catch (PackageManager.NameNotFoundException ignored) {
continue;
}
if (permissionInfo.getProtection() == PROTECTION_DANGEROUS) {
return true;
}
}
return false;
}
private void removeUidsForUserLocked(int userHandle) {
for (int i = mUidStates.size() - 1; i >= 0; --i) {
final int uid = mUidStates.keyAt(i);