diff options
| -rw-r--r-- | api/current.txt | 6 | ||||
| -rwxr-xr-x | api/system-current.txt | 14 | ||||
| -rw-r--r-- | api/test-current.txt | 14 | ||||
| -rw-r--r-- | core/java/android/app/AppOpsManager.java | 122 | ||||
| -rw-r--r-- | core/java/android/app/RuntimeAppOpAccessMessage.aidl | 19 | ||||
| -rw-r--r-- | core/java/android/app/RuntimeAppOpAccessMessage.java | 245 | ||||
| -rw-r--r-- | core/java/android/app/SyncNotedAppOp.aidl | 19 | ||||
| -rw-r--r-- | core/java/android/app/SyncNotedAppOp.java | 150 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/IAppOpsService.aidl | 6 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/MessageSamplingConfig.aidl | 19 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/MessageSamplingConfig.java | 187 | ||||
| -rw-r--r-- | services/core/java/com/android/server/appop/AppOpsService.java | 297 |
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); |