summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt6
-rwxr-xr-xapi/system-current.txt14
-rw-r--r--api/test-current.txt14
-rw-r--r--core/java/android/app/AppOpsManager.java122
-rw-r--r--core/java/android/app/RuntimeAppOpAccessMessage.aidl19
-rw-r--r--core/java/android/app/RuntimeAppOpAccessMessage.java245
-rw-r--r--core/java/android/app/SyncNotedAppOp.aidl19
-rw-r--r--core/java/android/app/SyncNotedAppOp.java150
-rw-r--r--core/java/com/android/internal/app/IAppOpsService.aidl6
-rw-r--r--core/java/com/android/internal/app/MessageSamplingConfig.aidl19
-rw-r--r--core/java/com/android/internal/app/MessageSamplingConfig.java187
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java297
12 files changed, 1073 insertions, 25 deletions
diff --git a/api/current.txt b/api/current.txt
index 1000989a124b..c2bbb6d932e6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -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 {
diff --git a/api/system-current.txt b/api/system-current.txt
index 92d1bf40d228..c6d4eb38acc0 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -365,6 +365,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...);
@@ -681,6 +682,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);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 4c8bb0290cae..7284fe64f74e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -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();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1de68ba56c6a..f6bbc6824e71 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -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();
@@ -8089,6 +8181,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;
+ }
}
diff --git a/core/java/android/app/RuntimeAppOpAccessMessage.aidl b/core/java/android/app/RuntimeAppOpAccessMessage.aidl
new file mode 100644
index 000000000000..68e8819a3a8b
--- /dev/null
+++ b/core/java/android/app/RuntimeAppOpAccessMessage.aidl
@@ -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;
diff --git a/core/java/android/app/RuntimeAppOpAccessMessage.java b/core/java/android/app/RuntimeAppOpAccessMessage.java
new file mode 100644
index 000000000000..a81b8e7ab13c
--- /dev/null
+++ b/core/java/android/app/RuntimeAppOpAccessMessage.java
@@ -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
+
+}
diff --git a/core/java/android/app/SyncNotedAppOp.aidl b/core/java/android/app/SyncNotedAppOp.aidl
new file mode 100644
index 000000000000..ab062d241ac3
--- /dev/null
+++ b/core/java/android/app/SyncNotedAppOp.aidl
@@ -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;
diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
index 065d5de368ae..aa11b9510c94 100644
--- a/core/java/android/app/SyncNotedAppOp.java
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -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,48 +34,154 @@ 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.
*/
public @NonNull String getOp() {
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() { ... }
- return mOpCode == ((SyncNotedAppOp) other).mOpCode;
+ int _hash = 1;
+ _hash = 31 * _hash + mOpCode;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mFeatureId);
+ return _hash;
}
@Override
- public int hashCode() {
- return mOpCode;
+ @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];
+ }
+
+ @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
+
}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index dabaf5a8263c..1c1c25459b66 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -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
diff --git a/core/java/com/android/internal/app/MessageSamplingConfig.aidl b/core/java/com/android/internal/app/MessageSamplingConfig.aidl
new file mode 100644
index 000000000000..ab89ca265981
--- /dev/null
+++ b/core/java/com/android/internal/app/MessageSamplingConfig.aidl
@@ -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;
diff --git a/core/java/com/android/internal/app/MessageSamplingConfig.java b/core/java/com/android/internal/app/MessageSamplingConfig.java
new file mode 100644
index 000000000000..5300c1c21e60
--- /dev/null
+++ b/core/java/com/android/internal/app/MessageSamplingConfig.java
@@ -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
+
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index fe53174ace80..5e1582cf4775 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -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.
*/
@@ -1542,6 +1583,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(
@@ -3037,6 +3110,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 {
@@ -5501,6 +5577,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);