summaryrefslogtreecommitdiff
path: root/core/java/android/app/AsyncNotedAppOp.java
diff options
context:
space:
mode:
authorPhilip P. Moltmann <moltmann@google.com>2019-06-10 08:49:11 -0700
committerPhilip P. Moltmann <moltmann@google.com>2019-08-30 08:18:50 -0700
commit2b08aafc1438e03e253b2dea01750e4df89ef148 (patch)
tree497e3e6e175634eb62f850b4113dc6f41395a4a1 /core/java/android/app/AsyncNotedAppOp.java
parent281a34ac94d5d66e849639dee8cd3105d0d3e1e8 (diff)
Allow apps to collect which appops were noted
If private user data is send to an app the data provider should note an app-op. This change adds a new API AppOpsManager#setNotedAppOpsCollector that allows an app to get notified every time such an private data access happens. This will allow apps to monitor their own private data usage. Esp. with big, old apps, distributed teams or 3rd party libraries it might not always be clear what subsystems access private data. There are three different situations how private data can be accessed and an app op is noted: 1. Private data access inside a two-way binder call. E.g. LocationManager#getLastKnownLocation. When we start a two way binder transaction, we remember the calling uid via AppOpsManager#collectNotedAppOps. Then when the data providing code calls AppOpsManager#noteOp->AppOpsManager#markAppOpNoted the noted app-op is remembered in AppOpsManager#sAppOpsNotedInThisBinderTransaction. Then when returning from the binder call, we add the list of noted app-ops to the reply-parcel via AppOpsManager#prefixParcelWithAppOpsIfNeeded. On the calling side we check if there were any app-ops noted in AppOpsManager#readAndLogNotedAppops and then call the collector while still in the binder code. This allows the collector e.g. collect a stack trace which can be used to figure out what app code caused the private data access. 2. Very complex apps might do permissions checks internal to themself. I.e. an app notes an op for itself. We detect this case in AppOpsManager#markAppOpNoted and immediately call the collector similar to case (1). 3. Sometimes private data is accessed outside of a two-way binder call. E.g. if an app registers a LocationListener an app-op is noted each time a new location is send to the app. In this case it is not clear to the framework which app-action triggered this app-op-note. Hence the data provider has to describe in a AsyncNotedAppOp object when an why the access happened. These objects are then send to the system server via IAppOpsService#noteAsyncOp and then the collector in the app. There are rare cases where a private data access happens before the app is running (e.g. when a geo-fence is triggered). In this case we cache a small amount of AsyncNotedAppOps (in AppOpsService#mUnforwardedAsyncNotedOps) and deliver them when the app is ready for these events (in AppOpsManager#setNotedAppOpsCollector). Test: atest CtsAppOpsTestCases (includes new tests covering this functionality) Bug: 136505050 Change-Id: I96ded4a8d8d9bcb37a4555d9b1281cb57945ffa9
Diffstat (limited to 'core/java/android/app/AsyncNotedAppOp.java')
-rw-r--r--core/java/android/app/AsyncNotedAppOp.java245
1 files changed, 245 insertions, 0 deletions
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
new file mode 100644
index 000000000000..64f886aa2f1d
--- /dev/null
+++ b/core/java/android/app/AsyncNotedAppOp.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.os.Parcelable;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.util.DataClass;
+
+/**
+ * When an {@link AppOpsManager#noteOp(String, int, String, String) app-op is noted} and the
+ * app the app-op is noted for has a {@link AppOpsManager.AppOpsCollector} registered the note-event
+ * needs to be delivered to the collector. Usually this is done via an {@link SyncNotedAppOp}, but
+ * in some cases this is not possible. In this case an {@link AsyncNotedAppOp} is send to the system
+ * server and then forwarded to the {@link AppOpsManager.AppOpsCollector} in the app.
+ */
+@Immutable
+@DataClass(genEqualsHashCode = true,
+ genAidl = true,
+ genHiddenConstructor = true)
+// - We don't expose the opCode, but rather the public name of the op, hence use a non-standard
+// getter
+@DataClass.Suppress({"getOpCode"})
+public final class AsyncNotedAppOp implements Parcelable {
+ /** Op that was noted */
+ private final @IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int mOpCode;
+
+ /** Uid that noted the op */
+ private final @IntRange(from = 0) int mNotingUid;
+
+ /**
+ * Package that noted the op. {@code null} if the package name that noted the op could be not
+ * be determined (e.g. when the op is noted from native code).
+ */
+ private final @Nullable String mNotingPackageName;
+
+ /** Message associated with the noteOp. This message is set by the app noting the op */
+ private final @NonNull String mMessage;
+
+ /** Milliseconds since epoch when the op was noted */
+ private final @IntRange(from = 0) long mTime;
+
+ /**
+ * @return Op that was noted.
+ */
+ public @NonNull String getOp() {
+ return AppOpsManager.opToPublicName(mOpCode);
+ }
+
+
+
+ // Code below generated by codegen v1.0.0.
+ //
+ // DO NOT MODIFY!
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/AsyncNotedAppOp.java
+ //
+ // CHECKSTYLE:OFF Generated code
+
+ /**
+ * Creates a new AsyncNotedAppOp.
+ *
+ * @param opCode
+ * Op that was noted
+ * @param notingUid
+ * Uid that noted the op
+ * @param notingPackageName
+ * Package that noted the op
+ * @param message
+ * Message associated with the noteOp. This message is set by the app noting the op
+ * @param time
+ * Milliseconds since epoch when the op was noted
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public AsyncNotedAppOp(
+ @IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int opCode,
+ @IntRange(from = 0) int notingUid,
+ @Nullable String notingPackageName,
+ @NonNull String message,
+ @IntRange(from = 0) long time) {
+ this.mOpCode = opCode;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mOpCode,
+ "from", 0,
+ "to", AppOpsManager._NUM_OP - 1);
+ this.mNotingUid = notingUid;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mNotingUid,
+ "from", 0);
+ this.mNotingPackageName = notingPackageName;
+ this.mMessage = message;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMessage);
+ this.mTime = time;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mTime,
+ "from", 0);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Uid that noted the op
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0) int getNotingUid() {
+ return mNotingUid;
+ }
+
+ /**
+ * Package that noted the op
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getNotingPackageName() {
+ return mNotingPackageName;
+ }
+
+ /**
+ * Message associated with the noteOp. This message is set by the app noting the op
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getMessage() {
+ return mMessage;
+ }
+
+ /**
+ * Milliseconds since epoch when the op was noted
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0) long getTime() {
+ return mTime;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(AsyncNotedAppOp other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ AsyncNotedAppOp that = (AsyncNotedAppOp) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mOpCode == that.mOpCode
+ && mNotingUid == that.mNotingUid
+ && java.util.Objects.equals(mNotingPackageName, that.mNotingPackageName)
+ && java.util.Objects.equals(mMessage, that.mMessage)
+ && mTime == that.mTime;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mOpCode;
+ _hash = 31 * _hash + mNotingUid;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mNotingPackageName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mMessage);
+ _hash = 31 * _hash + Long.hashCode(mTime);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(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 (mNotingPackageName != null) flg |= 0x4;
+ dest.writeByte(flg);
+ dest.writeInt(mOpCode);
+ dest.writeInt(mNotingUid);
+ if (mNotingPackageName != null) dest.writeString(mNotingPackageName);
+ dest.writeString(mMessage);
+ dest.writeLong(mTime);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<AsyncNotedAppOp> CREATOR
+ = new Parcelable.Creator<AsyncNotedAppOp>() {
+ @Override
+ public AsyncNotedAppOp[] newArray(int size) {
+ return new AsyncNotedAppOp[size];
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ public AsyncNotedAppOp createFromParcel(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();
+ int notingUid = in.readInt();
+ String notingPackageName = (flg & 0x4) == 0 ? null : in.readString();
+ String message = in.readString();
+ long time = in.readLong();
+ return new AsyncNotedAppOp(
+ opCode,
+ notingUid,
+ notingPackageName,
+ message,
+ time);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1566503083973L,
+ codegenVersion = "1.0.0",
+ sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
+ inputSignatures = "private final @android.annotation.IntRange(from=0L, to=90L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mNotingPackageName\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+
+}