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
This commit is contained in:
@@ -4279,14 +4279,21 @@ package android.app {
|
||||
method public void checkPackage(int, @NonNull String);
|
||||
method public void finishOp(@NonNull String, int, @NonNull String);
|
||||
method public boolean isOpActive(@NonNull String, int, @NonNull String);
|
||||
method public int noteOp(@NonNull String, int, @NonNull String);
|
||||
method public int noteOpNoThrow(@NonNull String, int, @NonNull String);
|
||||
method public int noteProxyOp(@NonNull String, @NonNull String);
|
||||
method public int noteProxyOpNoThrow(@NonNull String, @NonNull String);
|
||||
method public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int);
|
||||
method @Deprecated public int noteOp(@NonNull String, int, @NonNull String);
|
||||
method public int noteOp(@NonNull String, int, @Nullable String, @Nullable String);
|
||||
method @Deprecated public int noteOpNoThrow(@NonNull String, int, @NonNull String);
|
||||
method public int noteOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String);
|
||||
method @Deprecated public int noteProxyOp(@NonNull String, @NonNull String);
|
||||
method public int noteProxyOp(@NonNull String, @Nullable String, int, @Nullable String);
|
||||
method @Deprecated public int noteProxyOpNoThrow(@NonNull String, @NonNull String);
|
||||
method @Deprecated public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int);
|
||||
method public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int, @Nullable String);
|
||||
method public static String permissionToOp(String);
|
||||
method public int startOp(@NonNull String, int, @NonNull String);
|
||||
method public int startOpNoThrow(@NonNull String, int, @NonNull String);
|
||||
method public void setNotedAppOpsCollector(@Nullable android.app.AppOpsManager.AppOpsCollector);
|
||||
method @Deprecated public int startOp(@NonNull String, int, @NonNull String);
|
||||
method public int startOp(@NonNull String, int, @Nullable String, @Nullable String);
|
||||
method @Deprecated public int startOpNoThrow(@NonNull String, int, @NonNull String);
|
||||
method public int startOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String);
|
||||
method public void startWatchingActive(@NonNull String[], @NonNull java.util.concurrent.Executor, @NonNull android.app.AppOpsManager.OnOpActiveChangedListener);
|
||||
method public void startWatchingMode(@NonNull String, @Nullable String, @NonNull android.app.AppOpsManager.OnOpChangedListener);
|
||||
method public void startWatchingMode(@NonNull String, @Nullable String, int, @NonNull android.app.AppOpsManager.OnOpChangedListener);
|
||||
@@ -4338,6 +4345,14 @@ package android.app {
|
||||
field public static final int WATCH_FOREGROUND_CHANGES = 1; // 0x1
|
||||
}
|
||||
|
||||
public abstract static class AppOpsManager.AppOpsCollector {
|
||||
ctor public AppOpsManager.AppOpsCollector();
|
||||
method @NonNull public java.util.concurrent.Executor getAsyncNotedExecutor();
|
||||
method public abstract void onAsyncNoted(@NonNull android.app.AsyncNotedAppOp);
|
||||
method public abstract void onNoted(@NonNull android.app.SyncNotedAppOp);
|
||||
method public abstract void onSelfNoted(@NonNull android.app.SyncNotedAppOp);
|
||||
}
|
||||
|
||||
public static interface AppOpsManager.OnOpActiveChangedListener {
|
||||
method public void onOpActiveChanged(@NonNull String, int, @NonNull String, boolean);
|
||||
}
|
||||
@@ -4458,6 +4473,17 @@ package android.app {
|
||||
field public String serviceDetails;
|
||||
}
|
||||
|
||||
public final class AsyncNotedAppOp implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method @NonNull public String getMessage();
|
||||
method @Nullable public String getNotingPackageName();
|
||||
method @IntRange(from=0) public int getNotingUid();
|
||||
method @NonNull public String getOp();
|
||||
method @IntRange(from=0) public long getTime();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.app.AsyncNotedAppOp> CREATOR;
|
||||
}
|
||||
|
||||
public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable {
|
||||
ctor public AuthenticationRequiredException(Throwable, android.app.PendingIntent);
|
||||
method public int describeContents();
|
||||
@@ -6267,6 +6293,10 @@ package android.app {
|
||||
public class StatusBarManager {
|
||||
}
|
||||
|
||||
public final class SyncNotedAppOp {
|
||||
method @NonNull public String getOp();
|
||||
}
|
||||
|
||||
@Deprecated public class TabActivity extends android.app.ActivityGroup {
|
||||
ctor @Deprecated public TabActivity();
|
||||
method @Deprecated public android.widget.TabHost getTabHost();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
19
core/java/android/app/AsyncNotedAppOp.aidl
Normal file
19
core/java/android/app/AsyncNotedAppOp.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.app;
|
||||
|
||||
parcelable AsyncNotedAppOp;
|
||||
245
core/java/android/app/AsyncNotedAppOp.java
Normal file
245
core/java/android/app/AsyncNotedAppOp.java
Normal file
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.app;
|
||||
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.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() {}
|
||||
|
||||
}
|
||||
68
core/java/android/app/SyncNotedAppOp.java
Normal file
68
core/java/android/app/SyncNotedAppOp.java
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 com.android.internal.annotations.Immutable;
|
||||
|
||||
/**
|
||||
* Description of an app-op that was noted for the current process.
|
||||
*
|
||||
* <p>This is either delivered after a
|
||||
* {@link AppOpsManager.AppOpsCollector#onNoted(SyncNotedAppOp) two way binder call} or
|
||||
* when the app
|
||||
* {@link AppOpsManager.AppOpsCollector#onSelfNoted(SyncNotedAppOp) notes an app-op for
|
||||
* itself}.
|
||||
*/
|
||||
@Immutable
|
||||
public final class SyncNotedAppOp {
|
||||
private final int mOpCode;
|
||||
|
||||
/**
|
||||
* @return The op that was noted.
|
||||
*/
|
||||
public @NonNull String getOp() {
|
||||
return AppOpsManager.opToPublicName(mOpCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
mOpCode = opCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof SyncNotedAppOp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mOpCode == ((SyncNotedAppOp) other).mOpCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mOpCode;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package android.content;
|
||||
|
||||
import static android.app.AppOpsManager.strOpToOp;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
@@ -74,6 +76,16 @@ public final class PermissionChecker {
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #checkPermission(Context, String, int, int, String, String)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
@PermissionResult
|
||||
public static int checkPermission(@NonNull Context context, @NonNull String permission,
|
||||
int pid, int uid, @Nullable String packageName) {
|
||||
return checkPermission(context, permission, pid, uid, packageName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given package in a UID and PID has a given permission
|
||||
* and whether the app op that corresponds to this permission is allowed.
|
||||
@@ -84,12 +96,14 @@ public final class PermissionChecker {
|
||||
* @param uid The uid for which to check.
|
||||
* @param packageName The package name for which to check. If null the
|
||||
* the first package for the calling UID will be used.
|
||||
* @param message A message describing the reason the permission was checked
|
||||
*
|
||||
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
|
||||
* or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
|
||||
*/
|
||||
@PermissionResult
|
||||
public static int checkPermission(@NonNull Context context, @NonNull String permission,
|
||||
int pid, int uid, @Nullable String packageName) {
|
||||
int pid, int uid, @Nullable String packageName, @Nullable String message) {
|
||||
if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
|
||||
return PERMISSION_DENIED;
|
||||
}
|
||||
@@ -108,7 +122,8 @@ public final class PermissionChecker {
|
||||
packageName = packageNames[0];
|
||||
}
|
||||
|
||||
if (appOpsManager.noteProxyOpNoThrow(op, packageName, uid) != AppOpsManager.MODE_ALLOWED) {
|
||||
if (appOpsManager.noteProxyOpNoThrow(strOpToOp(op), packageName, uid, message)
|
||||
!= AppOpsManager.MODE_ALLOWED) {
|
||||
return PERMISSION_DENIED_APP_OP;
|
||||
}
|
||||
|
||||
@@ -131,7 +146,17 @@ public final class PermissionChecker {
|
||||
public static int checkSelfPermission(@NonNull Context context,
|
||||
@NonNull String permission) {
|
||||
return checkPermission(context, permission, Process.myPid(),
|
||||
Process.myUid(), context.getPackageName());
|
||||
Process.myUid(), context.getPackageName(), null /* self access */);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #checkCallingPermission(Context, String, String, String)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
@PermissionResult
|
||||
public static int checkCallingPermission(@NonNull Context context,
|
||||
@NonNull String permission, @Nullable String packageName) {
|
||||
return checkCallingPermission(context, permission, packageName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,17 +167,29 @@ public final class PermissionChecker {
|
||||
* @param permission The permission to check.
|
||||
* @param packageName The package name making the IPC. If null the
|
||||
* the first package for the calling UID will be used.
|
||||
* @param message A message describing the reason the permission was checked
|
||||
*
|
||||
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
|
||||
* or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
|
||||
*/
|
||||
@PermissionResult
|
||||
public static int checkCallingPermission(@NonNull Context context,
|
||||
@NonNull String permission, @Nullable String packageName) {
|
||||
@NonNull String permission, @Nullable String packageName, @Nullable String message) {
|
||||
if (Binder.getCallingPid() == Process.myPid()) {
|
||||
return PERMISSION_DENIED;
|
||||
}
|
||||
return checkPermission(context, permission, Binder.getCallingPid(),
|
||||
Binder.getCallingUid(), packageName);
|
||||
Binder.getCallingUid(), packageName, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #checkCallingOrSelfPermission(Context, String, String)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
@PermissionResult
|
||||
public static int checkCallingOrSelfPermission(@NonNull Context context,
|
||||
@NonNull String permission) {
|
||||
return checkCallingOrSelfPermission(context, permission, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,15 +198,17 @@ public final class PermissionChecker {
|
||||
*
|
||||
* @param context Context for accessing resources.
|
||||
* @param permission The permission to check.
|
||||
* @param message A message describing the reason the permission was checked
|
||||
*
|
||||
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
|
||||
* or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
|
||||
*/
|
||||
@PermissionResult
|
||||
public static int checkCallingOrSelfPermission(@NonNull Context context,
|
||||
@NonNull String permission) {
|
||||
@NonNull String permission, @Nullable String message) {
|
||||
String packageName = (Binder.getCallingPid() == Process.myPid())
|
||||
? context.getPackageName() : null;
|
||||
return checkPermission(context, permission, Binder.getCallingPid(),
|
||||
Binder.getCallingUid(), packageName);
|
||||
Binder.getCallingUid(), packageName, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.UnsupportedAppUsage;
|
||||
import android.app.AppOpsManager;
|
||||
import android.util.ExceptionUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
@@ -1029,7 +1030,17 @@ public class Binder implements IBinder {
|
||||
Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":"
|
||||
+ (transactionName != null ? transactionName : code));
|
||||
}
|
||||
res = onTransact(code, data, reply, flags);
|
||||
|
||||
if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) {
|
||||
AppOpsManager.startNotedAppOpsCollection(callingUid);
|
||||
try {
|
||||
res = onTransact(code, data, reply, flags);
|
||||
} finally {
|
||||
AppOpsManager.finishNotedAppOpsCollection();
|
||||
}
|
||||
} else {
|
||||
res = onTransact(code, data, reply, flags);
|
||||
}
|
||||
} catch (RemoteException|RuntimeException e) {
|
||||
if (observer != null) {
|
||||
observer.callThrewException(callSession, e);
|
||||
|
||||
@@ -18,6 +18,7 @@ package android.os;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.AppOpsManager;
|
||||
import android.util.Log;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
@@ -506,9 +507,18 @@ public final class BinderProxy implements IBinder {
|
||||
}
|
||||
}
|
||||
|
||||
final AppOpsManager.PausedNotedAppOpsCollection prevCollection =
|
||||
AppOpsManager.pauseNotedAppOpsCollection();
|
||||
|
||||
if ((flags & FLAG_ONEWAY) == 0 && AppOpsManager.isCollectingNotedAppOps()) {
|
||||
flags |= FLAG_COLLECT_NOTED_APP_OPS;
|
||||
}
|
||||
|
||||
try {
|
||||
return transactNative(code, data, reply, flags);
|
||||
} finally {
|
||||
AppOpsManager.resumeNotedAppOpsCollection(prevCollection);
|
||||
|
||||
if (transactListener != null) {
|
||||
transactListener.onTransactEnded(session);
|
||||
}
|
||||
|
||||
@@ -169,6 +169,11 @@ public interface IBinder {
|
||||
*/
|
||||
int FLAG_ONEWAY = 0x00000001;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
int FLAG_COLLECT_NOTED_APP_OPS = 0x00000002;
|
||||
|
||||
/**
|
||||
* Limit that should be placed on IPC sizes to keep them safely under the
|
||||
* transaction buffer limit.
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.TestApi;
|
||||
import android.annotation.UnsupportedAppUsage;
|
||||
import android.app.AppOpsManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
@@ -267,7 +268,9 @@ public final class Parcel {
|
||||
private static final int EX_UNSUPPORTED_OPERATION = -7;
|
||||
private static final int EX_SERVICE_SPECIFIC = -8;
|
||||
private static final int EX_PARCELABLE = -9;
|
||||
private static final int EX_HAS_REPLY_HEADER = -128; // special; see below
|
||||
/** @hide */
|
||||
public static final int EX_HAS_NOTED_APPOPS_REPLY_HEADER = -127; // special; see below
|
||||
private static final int EX_HAS_STRICTMODE_REPLY_HEADER = -128; // special; see below
|
||||
// EX_TRANSACTION_FAILED is used exclusively in native code.
|
||||
// see libbinder's binder/Status.h
|
||||
private static final int EX_TRANSACTION_FAILED = -129;
|
||||
@@ -1866,6 +1869,8 @@ public final class Parcel {
|
||||
* @see #readException
|
||||
*/
|
||||
public final void writeException(@NonNull Exception e) {
|
||||
AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
|
||||
|
||||
int code = 0;
|
||||
if (e instanceof Parcelable
|
||||
&& (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
|
||||
@@ -1944,6 +1949,8 @@ public final class Parcel {
|
||||
* @see #readException
|
||||
*/
|
||||
public final void writeNoException() {
|
||||
AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
|
||||
|
||||
// Despite the name of this function ("write no exception"),
|
||||
// it should instead be thought of as "write the RPC response
|
||||
// header", but because this function name is written out by
|
||||
@@ -1951,14 +1958,14 @@ public final class Parcel {
|
||||
//
|
||||
// The response header, in the non-exception case (see also
|
||||
// writeException above, also called by the AIDL compiler), is
|
||||
// either a 0 (the default case), or EX_HAS_REPLY_HEADER if
|
||||
// either a 0 (the default case), or EX_HAS_STRICTMODE_REPLY_HEADER if
|
||||
// StrictMode has gathered up violations that have occurred
|
||||
// during a Binder call, in which case we write out the number
|
||||
// of violations and their details, serialized, before the
|
||||
// actual RPC respons data. The receiving end of this is
|
||||
// readException(), below.
|
||||
if (StrictMode.hasGatheredViolations()) {
|
||||
writeInt(EX_HAS_REPLY_HEADER);
|
||||
writeInt(EX_HAS_STRICTMODE_REPLY_HEADER);
|
||||
final int sizePosition = dataPosition();
|
||||
writeInt(0); // total size of fat header, to be filled in later
|
||||
StrictMode.writeGatheredViolationsToParcel(this);
|
||||
@@ -2005,7 +2012,13 @@ public final class Parcel {
|
||||
@TestApi
|
||||
public final int readExceptionCode() {
|
||||
int code = readInt();
|
||||
if (code == EX_HAS_REPLY_HEADER) {
|
||||
if (code == EX_HAS_NOTED_APPOPS_REPLY_HEADER) {
|
||||
AppOpsManager.readAndLogNotedAppops(this);
|
||||
// Read next header or real exception if there is no more header
|
||||
code = readInt();
|
||||
}
|
||||
|
||||
if (code == EX_HAS_STRICTMODE_REPLY_HEADER) {
|
||||
int headerSize = readInt();
|
||||
if (headerSize == 0) {
|
||||
Log.e(TAG, "Unexpected zero-sized Parcel reply header.");
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.app.AsyncNotedAppOp;
|
||||
|
||||
// Interface to observe async noted appops
|
||||
oneway interface IAppOpsAsyncNotedCallback {
|
||||
void opNoted(in AsyncNotedAppOp op);
|
||||
}
|
||||
@@ -17,12 +17,13 @@
|
||||
package com.android.internal.app;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.AsyncNotedAppOp;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteCallback;
|
||||
import com.android.internal.app.IAppOpsCallback;
|
||||
import com.android.internal.app.IAppOpsActiveCallback;
|
||||
import com.android.internal.app.IAppOpsAsyncNotedCallback;
|
||||
import com.android.internal.app.IAppOpsNotedCallback;
|
||||
|
||||
interface IAppOpsService {
|
||||
@@ -40,6 +41,9 @@ interface IAppOpsService {
|
||||
IBinder getToken(IBinder clientToken);
|
||||
int permissionToOpCode(String permission);
|
||||
int checkAudioOperation(int code, int usage, int uid, String packageName);
|
||||
void noteAsyncOp(String callingPackageName, int uid, String packageName, int opCode,
|
||||
String message);
|
||||
boolean shouldCollectNotes(int opCode);
|
||||
// End of methods also called by native code.
|
||||
// Any new method exposed to native must be added after the last one, do not reorder
|
||||
|
||||
@@ -82,6 +86,10 @@ interface IAppOpsService {
|
||||
void startWatchingNoted(in int[] ops, IAppOpsNotedCallback callback);
|
||||
void stopWatchingNoted(IAppOpsNotedCallback callback);
|
||||
|
||||
void startWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback);
|
||||
void stopWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback);
|
||||
List<AsyncNotedAppOp> extractAsyncOps(String packageName);
|
||||
|
||||
int checkOperationRaw(int code, int uid, String packageName);
|
||||
|
||||
void reloadNonHistoricalState();
|
||||
|
||||
@@ -61,16 +61,52 @@ public class AnnotationValidations {
|
||||
}
|
||||
|
||||
public static void validate(Class<IntRange> annotation, IntRange ignored, int value,
|
||||
String paramName1, int param1, String paramName2, int param2) {
|
||||
String paramName1, long param1, String paramName2, long param2) {
|
||||
validate(annotation, ignored, value, paramName1, param1);
|
||||
validate(annotation, ignored, value, paramName2, param2);
|
||||
}
|
||||
|
||||
public static void validate(Class<IntRange> annotation, IntRange ignored, int value,
|
||||
String paramName, int param) {
|
||||
String paramName, long param) {
|
||||
switch (paramName) {
|
||||
case "from": if (value < param) invalid(annotation, value, paramName, param); break;
|
||||
case "to": if (value > param) invalid(annotation, value, paramName, param); break;
|
||||
case "from":
|
||||
if (value < param) {
|
||||
invalid(annotation, value, paramName, param);
|
||||
}
|
||||
break;
|
||||
case "to":
|
||||
if (value > param) {
|
||||
invalid(annotation, value, paramName, param);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a long value with two parameters.
|
||||
*/
|
||||
public static void validate(Class<IntRange> annotation, IntRange ignored, long value,
|
||||
String paramName1, long param1, String paramName2, long param2) {
|
||||
validate(annotation, ignored, value, paramName1, param1);
|
||||
validate(annotation, ignored, value, paramName2, param2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a long value with one parameter.
|
||||
*/
|
||||
public static void validate(Class<IntRange> annotation, IntRange ignored, long value,
|
||||
String paramName, long param) {
|
||||
switch (paramName) {
|
||||
case "from":
|
||||
if (value < param) {
|
||||
invalid(annotation, value, paramName, param);
|
||||
}
|
||||
break;
|
||||
case "to":
|
||||
if (value > param) {
|
||||
invalid(annotation, value, paramName, param);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,11 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE_LOCATION;
|
||||
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
|
||||
import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
|
||||
import static android.app.AppOpsManager.UID_STATE_TOP;
|
||||
import static android.app.AppOpsManager._NUM_OP;
|
||||
import static android.app.AppOpsManager.modeToName;
|
||||
import static android.app.AppOpsManager.opToName;
|
||||
import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState;
|
||||
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
@@ -49,6 +51,7 @@ import android.app.AppOpsManager.OpEntry;
|
||||
import android.app.AppOpsManager.OpFlags;
|
||||
import android.app.AppOpsManagerInternal;
|
||||
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
|
||||
import android.app.AsyncNotedAppOp;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
@@ -58,6 +61,7 @@ import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.PermissionInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.database.ContentObserver;
|
||||
import android.media.AudioAttributes;
|
||||
@@ -69,6 +73,7 @@ import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.ServiceManager;
|
||||
@@ -86,6 +91,7 @@ import android.util.AtomicFile;
|
||||
import android.util.KeyValueListParser;
|
||||
import android.util.LongSparseArray;
|
||||
import android.util.LongSparseLongArray;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
@@ -96,6 +102,7 @@ import android.util.Xml;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.app.IAppOpsActiveCallback;
|
||||
import com.android.internal.app.IAppOpsAsyncNotedCallback;
|
||||
import com.android.internal.app.IAppOpsCallback;
|
||||
import com.android.internal.app.IAppOpsNotedCallback;
|
||||
import com.android.internal.app.IAppOpsService;
|
||||
@@ -181,6 +188,8 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
OP_CAMERA,
|
||||
};
|
||||
|
||||
private static final int MAX_UNFORWARED_OPS = 10;
|
||||
|
||||
Context mContext;
|
||||
final AtomicFile mFile;
|
||||
final Handler mHandler;
|
||||
@@ -188,6 +197,29 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
private final AppOpsManagerInternalImpl mAppOpsManagerInternal
|
||||
= new AppOpsManagerInternalImpl();
|
||||
|
||||
/**
|
||||
* Registered callbacks, called from {@link #noteAsyncOp}.
|
||||
*
|
||||
* <p>(package name, uid) -> callbacks
|
||||
*
|
||||
* @see #getAsyncNotedOpsKey(String, int)
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private final ArrayMap<Pair<String, Integer>, RemoteCallbackList<IAppOpsAsyncNotedCallback>>
|
||||
mAsyncOpWatchers = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Async note-ops collected from {@link #noteAsyncOp} that have not been delivered to a
|
||||
* callback yet.
|
||||
*
|
||||
* <p>(package name, uid) -> list<ops>
|
||||
*
|
||||
* @see #getAsyncNotedOpsKey(String, int)
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private final ArrayMap<Pair<String, Integer>, ArrayList<AsyncNotedAppOp>>
|
||||
mUnforwardedAsyncNotedOps = new ArrayMap<>();
|
||||
|
||||
boolean mWriteScheduled;
|
||||
boolean mFastWriteScheduled;
|
||||
final Runnable mWriteRunner = new Runnable() {
|
||||
@@ -2097,6 +2129,141 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noteAsyncOp(String callingPackageName, int uid, String packageName, int opCode,
|
||||
String message) {
|
||||
Preconditions.checkNotNull(message);
|
||||
Preconditions.checkNotNull(packageName);
|
||||
verifyAndGetIsPrivileged(uid, packageName);
|
||||
|
||||
verifyIncomingUid(uid);
|
||||
verifyIncomingOp(opCode);
|
||||
|
||||
int callingUid = Binder.getCallingUid();
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (callingPackageName != null) {
|
||||
verifyAndGetIsPrivileged(callingUid, callingPackageName);
|
||||
}
|
||||
|
||||
long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (this) {
|
||||
Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
|
||||
|
||||
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
|
||||
AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
|
||||
callingPackageName, message, now);
|
||||
final boolean[] wasNoteForwarded = {false};
|
||||
|
||||
if (callbacks != null) {
|
||||
callbacks.broadcast((cb) -> {
|
||||
try {
|
||||
cb.opNoted(asyncNotedOp);
|
||||
wasNoteForwarded[0] = true;
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG,
|
||||
"Could not forward noteOp of " + opCode + " to " + packageName
|
||||
+ "/" + uid, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!wasNoteForwarded[0]) {
|
||||
ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(key);
|
||||
if (unforwardedOps == null) {
|
||||
unforwardedOps = new ArrayList<>(1);
|
||||
mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
|
||||
}
|
||||
|
||||
unforwardedOps.add(asyncNotedOp);
|
||||
if (unforwardedOps.size() > MAX_UNFORWARED_OPS) {
|
||||
unforwardedOps.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a key to be used in {@link #mAsyncOpWatchers} and {@link #mUnforwardedAsyncNotedOps}
|
||||
*
|
||||
* @param packageName The package name of the app
|
||||
* @param uid The uid of the app
|
||||
*
|
||||
* @return They key uniquely identifying the app
|
||||
*/
|
||||
private @NonNull Pair<String, Integer> getAsyncNotedOpsKey(@NonNull String packageName,
|
||||
int uid) {
|
||||
return new Pair<>(packageName, uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback) {
|
||||
Preconditions.checkNotNull(packageName);
|
||||
Preconditions.checkNotNull(callback);
|
||||
|
||||
int uid = Binder.getCallingUid();
|
||||
Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
|
||||
|
||||
verifyAndGetIsPrivileged(uid, packageName);
|
||||
|
||||
synchronized (this) {
|
||||
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
|
||||
if (callbacks == null) {
|
||||
callbacks = new RemoteCallbackList<IAppOpsAsyncNotedCallback>() {
|
||||
@Override
|
||||
public void onCallbackDied(IAppOpsAsyncNotedCallback callback) {
|
||||
synchronized (AppOpsService.this) {
|
||||
if (getRegisteredCallbackCount() == 0) {
|
||||
mAsyncOpWatchers.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
mAsyncOpWatchers.put(key, callbacks);
|
||||
}
|
||||
|
||||
callbacks.register(callback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback) {
|
||||
Preconditions.checkNotNull(packageName);
|
||||
Preconditions.checkNotNull(callback);
|
||||
|
||||
int uid = Binder.getCallingUid();
|
||||
Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
|
||||
|
||||
verifyAndGetIsPrivileged(uid, packageName);
|
||||
|
||||
synchronized (this) {
|
||||
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
|
||||
if (callbacks != null) {
|
||||
callbacks.unregister(callback);
|
||||
if (callbacks.getRegisteredCallbackCount() == 0) {
|
||||
mAsyncOpWatchers.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AsyncNotedAppOp> extractAsyncOps(String packageName) {
|
||||
Preconditions.checkNotNull(packageName);
|
||||
|
||||
int uid = Binder.getCallingUid();
|
||||
|
||||
verifyAndGetIsPrivileged(uid, packageName);
|
||||
|
||||
synchronized (this) {
|
||||
return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int startOperation(IBinder token, int code, int uid, String packageName,
|
||||
boolean startIfModeDefault) {
|
||||
@@ -2340,6 +2507,25 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
return AppOpsManager.permissionToOpCode(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldCollectNotes(int opCode) {
|
||||
Preconditions.checkArgumentInRange(opCode, 0, _NUM_OP - 1, "opCode");
|
||||
|
||||
String perm = AppOpsManager.opToPermission(opCode);
|
||||
if (perm == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PermissionInfo permInfo;
|
||||
try {
|
||||
permInfo = mContext.getPackageManager().getPermissionInfo(perm, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return permInfo.getProtection() == PROTECTION_DANGEROUS;
|
||||
}
|
||||
|
||||
void finishOperationLocked(Op op, boolean finishNested) {
|
||||
final int opCode = op.op;
|
||||
final int uid = op.uidState.uid;
|
||||
|
||||
Reference in New Issue
Block a user