Merge "Allow apps to collect which appops were noted"

This commit is contained in:
TreeHugger Robot
2019-08-30 18:53:22 +00:00
committed by Android (Google) Code Review
14 changed files with 1585 additions and 279 deletions

View File

@@ -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

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app;
parcelable AsyncNotedAppOp;

View File

@@ -0,0 +1,245 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.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() {}
}

View 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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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.");

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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&lt;ops&gt;
*
* @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;