Switch media fw permissions checks to AttributionSource
Attribution source is the abstraction to capture the data
flows for private data across apps. Checking permissions
for an attribution source does this for all apps in the
chain that would receive the data as well as the relevant
app ops are checked/noted/started as needed.
Teach speech recognition service about attribution
chains. If an implementation does nothing the OS
would enforce permisisons and do blame as always.
This apporach leads to double blaming and doesn't
support attribition chains where app calls into
the default recognizer which calls into the on
device recognizer (this nests recursively). If the
implementer takes advantage of the attribution chain
mechanims the permissions for the entire chain are
checked at mic access time and all apps are blamed
only once.
Fixed a few bugs around finishing ops for attribution
chains. Also ensured that any app death in a started
attribution chain would lead to finishing the op for
this app
bug: 158792096
Test: ((added tests for speech reco))
atest CtsMediaTestCases
atest CtsPermissionTestCases
atest CtsPermission2TestCases
atest CtsPermission3TestCases
atest CtsPermission4TestCases
atest CtsPermission5TestCases
atest CtsAppOpsTestCases
atest CtsAppOps2TestCases
Change-Id: Ic92c7adc14bd2d135ac13b96f17a1b393dd562e4
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0b70a49..15696b0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -201,6 +201,11 @@
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void setHistoryParameters(int, long, int);
method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(int, int, String, int);
method public static int strOpToOp(@NonNull String);
+ field public static final int ATTRIBUTION_CHAIN_ID_NONE = -1; // 0xffffffff
+ field public static final int ATTRIBUTION_FLAGS_NONE = 0; // 0x0
+ field public static final int ATTRIBUTION_FLAG_ACCESSOR = 1; // 0x1
+ field public static final int ATTRIBUTION_FLAG_INTERMEDIARY = 2; // 0x2
+ field public static final int ATTRIBUTION_FLAG_RECEIVER = 4; // 0x4
field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0
field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1
field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2
@@ -230,6 +235,10 @@
method public void offsetBeginAndEndTime(long);
}
+ public static interface AppOpsManager.OnOpActiveChangedListener {
+ method public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, boolean, int, int);
+ }
+
public class BroadcastOptions {
ctor public BroadcastOptions(@NonNull android.os.Bundle);
method public int getMaxManifestReceiverApiLevel();
@@ -669,7 +678,7 @@
public final class AttributionSource implements android.os.Parcelable {
ctor public AttributionSource(int, @Nullable String, @Nullable String);
- ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable android.content.AttributionSource);
+ ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder);
ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource);
}
@@ -2056,7 +2065,7 @@
public final class PermissionManager {
method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData();
method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean);
- method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource);
+ method public void registerAttributionSource(@NonNull android.content.AttributionSource);
}
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 02520af..ea42d95 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -708,6 +708,62 @@
}
}
+ /**
+ * Attribution chain flag: specifies that this is the accessor. When
+ * an app A accesses the data that is then passed to app B that is then
+ * passed to C, we call app A accessor, app B intermediary, and app C
+ * receiver. If A accesses the data for itself, then it is the accessor
+ * and the receiver.
+ * @hide
+ */
+ @TestApi
+ public static final int ATTRIBUTION_FLAG_ACCESSOR = 0x1;
+
+ /**
+ * Attribution chain flag: specifies that this is the intermediary. When
+ * an app A accesses the data that is then passed to app B that is then
+ * passed to C, we call app A accessor, app B intermediary, and app C
+ * receiver. If A accesses the data for itself, then it is the accessor
+ * and the receiver.
+ * @hide
+ */
+ @TestApi
+ public static final int ATTRIBUTION_FLAG_INTERMEDIARY = 0x2;
+
+ /**
+ * Attribution chain flag: specifies that this is the receiver. When
+ * an app A accesses the data that is then passed to app B that is then
+ * passed to C, we call app A accessor, app B intermediary, and app C
+ * receiver. If A accesses the data for itself, then it is the accessor
+ * and the receiver.
+ * @hide
+ */
+ @TestApi
+ public static final int ATTRIBUTION_FLAG_RECEIVER = 0x4;
+
+ /**
+ * No attribution flags.
+ * @hide
+ */
+ @TestApi
+ public static final int ATTRIBUTION_FLAGS_NONE = 0x0;
+
+ /**
+ * No attribution chain id.
+ * @hide
+ */
+ @TestApi
+ public static final int ATTRIBUTION_CHAIN_ID_NONE = -1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ ATTRIBUTION_FLAG_ACCESSOR,
+ ATTRIBUTION_FLAG_INTERMEDIARY,
+ ATTRIBUTION_FLAG_RECEIVER
+ })
+ public @interface AttributionFlags {}
+
// These constants are redefined here to work around a metalava limitation/bug where
// @IntDef is not able to see @hide symbols when they are hidden via package hiding:
// frameworks/base/core/java/com/android/internal/package.html
@@ -7073,6 +7129,25 @@
*/
void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
boolean active);
+
+ /**
+ * Called when the active state of an app-op changes.
+ *
+ * @param op The operation that changed.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param attributionTag The operation's attribution tag.
+ * @param active Whether the operation became active or inactive.
+ * @param attributionFlags the attribution flags for this operation.
+ * @param attributionChainId the unique id of the attribution chain this op is a part of.
+ * @hide
+ */
+ @TestApi
+ default void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, boolean active, @AttributionFlags
+ int attributionFlags, int attributionChainId) {
+ onOpActiveChanged(op, uid, packageName, active);
+ }
}
/**
@@ -7694,14 +7769,17 @@
}
cb = new IAppOpsActiveCallback.Stub() {
@Override
- public void opActiveChanged(int op, int uid, String packageName, boolean active) {
+ public void opActiveChanged(int op, int uid, String packageName,
+ String attributionTag, boolean active, @AttributionFlags
+ int attributionFlags, int attributionChainId) {
executor.execute(() -> {
if (callback instanceof OnOpActiveChangedInternalListener) {
((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op,
uid, packageName, active);
}
if (sOpToString[op] != null) {
- callback.onOpActiveChanged(sOpToString[op], uid, packageName, active);
+ callback.onOpActiveChanged(sOpToString[op], uid, packageName,
+ attributionTag, active, attributionFlags, attributionChainId);
}
});
}
@@ -8179,8 +8257,9 @@
public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
@Nullable String proxiedAttributionTag, @Nullable String message) {
return noteProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)),
- message, /*skipProxyOperation*/ false);
+ new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
+ mContext.getAttributionSource().getToken())), message,
+ /*skipProxyOperation*/ false);
}
/**
@@ -8265,8 +8344,8 @@
int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) {
return noteProxyOpNoThrow(strOpToOp(op), new AttributionSource(
mContext.getAttributionSource(), new AttributionSource(proxiedUid,
- proxiedPackageName, proxiedAttributionTag)), message,
- /*skipProxyOperation*/ false);
+ proxiedPackageName, proxiedAttributionTag, mContext.getAttributionSource()
+ .getToken())), message,/*skipProxyOperation*/ false);
}
/**
@@ -8602,6 +8681,29 @@
*/
public int startOpNoThrow(int op, int uid, @NonNull String packageName,
boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message) {
+ return startOpNoThrow(mContext.getAttributionSource().getToken(), op, uid, packageName,
+ startIfModeDefault, attributionTag, message);
+ }
+
+ /**
+ * @see #startOpNoThrow(String, int, String, String, String)
+ *
+ * @hide
+ */
+ public int startOpNoThrow(@NonNull IBinder token, int op, int uid, @NonNull String packageName,
+ boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message) {
+ return startOpNoThrow(token, op, uid, packageName, startIfModeDefault, attributionTag,
+ message, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_CHAIN_ID_NONE);
+ }
+
+ /**
+ * @see #startOpNoThrow(String, int, String, String, String)
+ *
+ * @hide
+ */
+ public int startOpNoThrow(@NonNull IBinder token, int op, int uid, @NonNull String packageName,
+ boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
try {
collectNoteOpCallsForValidation(op);
int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
@@ -8614,9 +8716,9 @@
}
}
- SyncNotedAppOp syncOp = mService.startOperation(getClientId(), op, uid, packageName,
+ SyncNotedAppOp syncOp = mService.startOperation(token, op, uid, packageName,
attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC, message,
- shouldCollectMessage);
+ shouldCollectMessage, attributionFlags, attributionChainId);
if (syncOp.getOpMode() == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
@@ -8653,8 +8755,9 @@
public int startProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName,
@Nullable String proxiedAttributionTag, @Nullable String message) {
return startProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)),
- message, /*skipProxyOperation*/ false);
+ new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
+ mContext.getAttributionSource().getToken())), message,
+ /*skipProxyOperation*/ false);
}
/**
@@ -8700,8 +8803,9 @@
@Nullable String message) {
return startProxyOpNoThrow(AppOpsManager.strOpToOp(op), new AttributionSource(
mContext.getAttributionSource(), new AttributionSource(proxiedUid,
- proxiedPackageName, proxiedAttributionTag)), message,
- /*skipProxyOperation*/ false);
+ proxiedPackageName, proxiedAttributionTag,
+ mContext.getAttributionSource().getToken())), message,
+ /*skipProxyOperation*/ false);
}
/**
@@ -8715,6 +8819,23 @@
*/
public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
@Nullable String message, boolean skipProxyOperation) {
+ return startProxyOpNoThrow(op, attributionSource, message, skipProxyOperation,
+ ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_CHAIN_ID_NONE);
+ }
+
+ /**
+ * Like {@link #startProxyOp(String, AttributionSource, String)} but instead
+ * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED} and
+ * the checks is for the attribution chain specified by the {@link AttributionSource}.
+ *
+ * @see #startProxyOp(String, AttributionSource, String)
+ *
+ * @hide
+ */
+ public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
+ @Nullable String message, boolean skipProxyOperation, @AttributionFlags
+ int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
+ int attributionChainId) {
try {
collectNoteOpCallsForValidation(op);
int collectionMode = getNotedOpCollectionMode(
@@ -8729,9 +8850,10 @@
}
}
- SyncNotedAppOp syncOp = mService.startProxyOperation(getClientId(), op,
+ SyncNotedAppOp syncOp = mService.startProxyOperation(op,
attributionSource, false, collectionMode == COLLECT_ASYNC, message,
- shouldCollectMessage, skipProxyOperation);
+ shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+ proxiedAttributionFlags, attributionChainId);
if (syncOp.getOpMode() == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
@@ -8795,8 +8917,18 @@
*/
public void finishOp(int op, int uid, @NonNull String packageName,
@Nullable String attributionTag) {
+ finishOp(mContext.getAttributionSource().getToken(), op, uid, packageName, attributionTag);
+ }
+
+ /**
+ * @see #finishOp(String, int, String, String)
+ *
+ * @hide
+ */
+ public void finishOp(IBinder token, int op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag) {
try {
- mService.finishOperation(getClientId(), op, uid, packageName, attributionTag);
+ mService.finishOperation(token, op, uid, packageName, attributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8817,23 +8949,26 @@
public void finishProxyOp(@NonNull String op, int proxiedUid,
@NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
finishProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)));
+ new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
+ mContext.getAttributionSource().getToken())), /*skipProxyOperation*/ false);
}
/**
* Report that an application is no longer performing an operation that had previously
- * been started with {@link #startProxyOp(String, AttributionSource, String)}. There is no
- * validation of input or result; the parameters supplied here must be the exact same ones
- * previously passed in when starting the operation.
+ * been started with {@link #startProxyOp(String, AttributionSource, String, boolean)}. There
+ * is no validation of input or result; the parameters supplied here must be the exact same
+ * ones previously passed in when starting the operation.
*
* @param op The operation which was started
* @param attributionSource The permission identity for which to finish
+ * @param skipProxyOperation Whether to skip the proxy finish.
*
* @hide
*/
- public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource) {
+ public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource,
+ boolean skipProxyOperation) {
try {
- mService.finishProxyOperation(getClientId(), strOpToOp(op), attributionSource);
+ mService.finishProxyOperation(strOpToOp(op), attributionSource, skipProxyOperation);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index 2de0ddb..a757e32 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -16,6 +16,7 @@
package android.app;
+import android.app.AppOpsManager.AttributionFlags;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.AttributionSource;
@@ -24,13 +25,13 @@
import android.util.SparseIntArray;
import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.HeptFunction;
import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.NonaFunction;
-import com.android.internal.util.function.OctFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.QuintFunction;
import com.android.internal.util.function.TriFunction;
+import com.android.internal.util.function.UndecFunction;
/**
* App ops service local interface.
@@ -52,8 +53,8 @@
* @return The app op check result.
*/
int checkOperation(int code, int uid, String packageName, @Nullable String attributionTag,
- boolean raw,
- QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl);
+ boolean raw, QuintFunction<Integer, Integer, String, String, Boolean, Integer>
+ superImpl);
/**
* Allows overriding check audio operation behavior.
@@ -116,20 +117,22 @@
* @param shouldCollectAsyncNotedOp If an {@link AsyncNotedAppOp} should be collected
* @param message The message in the async noted op
* @param shouldCollectMessage whether to collect messages
+ * @param attributionFlags the attribution flags for this operation.
+ * @param attributionChainId the unique id of the attribution chain this op is a part of.
* @param superImpl The super implementation.
* @return The app op note result.
*/
SyncNotedAppOp startOperation(IBinder token, int code, int uid,
@Nullable String packageName, @Nullable String attributionTag,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
- @Nullable String message, boolean shouldCollectMessage, @NonNull NonaFunction<
- IBinder, Integer, Integer, String, String, Boolean, Boolean, String,
- Boolean, SyncNotedAppOp> superImpl);
+ @Nullable String message, boolean shouldCollectMessage,
+ @AttributionFlags int attributionFlags, int attributionChainId,
+ @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean,
+ Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl);
/**
* Allows overriding start proxy operation behavior.
*
- * @param token The client state.
* @param code The op code to start.
* @param attributionSource The permission identity of the caller.
* @param startIfModeDefault Whether to start the op of the mode is default.
@@ -137,26 +140,29 @@
* @param message The message in the async noted op
* @param shouldCollectMessage whether to collect messages
* @param skipProxyOperation Whether to skip the proxy portion of the operation
+ * @param proxyAttributionFlags The attribution flags for the proxy.
+ * @param proxiedAttributionFlags The attribution flags for the proxied.
+ * @oaram attributionChainId The id of the attribution chain this operation is a part of.
* @param superImpl The super implementation.
* @return The app op note result.
*/
- SyncNotedAppOp startProxyOperation(IBinder token, int code,
- @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
- boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
- boolean skipProxyOperation, @NonNull OctFunction<IBinder, Integer,
- AttributionSource, Boolean, Boolean, String, Boolean, Boolean,
+ SyncNotedAppOp startProxyOperation(int code, @NonNull AttributionSource attributionSource,
+ boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
+ boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
+ int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
+ int attributionChainId, @NonNull DecFunction<Integer, AttributionSource, Boolean,
+ Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
SyncNotedAppOp> superImpl);
/**
* Allows overriding finish proxy op.
*
- * @param clientId Client state token.
* @param code The op code to finish.
* @param attributionSource The permission identity of the caller.
*/
- void finishProxyOperation(IBinder clientId, int code,
- @NonNull AttributionSource attributionSource,
- @NonNull TriFunction<IBinder, Integer, AttributionSource, Void> superImpl);
+ void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
+ boolean skipProxyOperation,
+ @NonNull TriFunction<Integer, AttributionSource, Boolean, Void> superImpl);
}
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index daef8b1..16b6ea5 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3157,13 +3157,7 @@
// If we want to access protected data on behalf of another app we need to
// tell the OS that we opt in to participate in the attribution chain.
if (nextAttributionSource != null) {
- // If an app happened to stub the internal OS for testing the registration method
- // can return null. In this case we keep the current untrusted attribution source.
- final AttributionSource registeredAttributionSource = getSystemService(
- PermissionManager.class).registerAttributionSource(attributionSource);
- if (registeredAttributionSource != null) {
- return registeredAttributionSource;
- }
+ getSystemService(PermissionManager.class).registerAttributionSource(attributionSource);
}
return attributionSource;
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 91dad2a..871d48b 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -186,6 +186,7 @@
import android.os.incremental.IncrementalManager;
import android.os.storage.StorageManager;
import android.permission.LegacyPermissionManager;
+import android.permission.PermissionCheckerManager;
import android.permission.PermissionControllerManager;
import android.permission.PermissionManager;
import android.print.IPrintManager;
@@ -1334,6 +1335,14 @@
ctx.getMainThreadHandler());
}});
+ registerService(Context.PERMISSION_CHECKER_SERVICE, PermissionCheckerManager.class,
+ new CachedServiceFetcher<PermissionCheckerManager>() {
+ @Override
+ public PermissionCheckerManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ return new PermissionCheckerManager(ctx.getOuterContext());
+ }});
+
registerService(Context.DYNAMIC_SYSTEM_SERVICE, DynamicSystemManager.class,
new CachedServiceFetcher<DynamicSystemManager>() {
@Override
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 7ab731f..1dda637 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -21,6 +21,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.ActivityThread;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
@@ -94,14 +95,22 @@
@TestApi
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag) {
- this(uid, packageName, attributionTag, /*next*/ null);
+ this(uid, packageName, attributionTag, new Binder());
}
/** @hide */
@TestApi
public AttributionSource(int uid, @Nullable String packageName,
- @Nullable String attributionTag, @Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, /*renouncedPermissions*/ null, next);
+ @Nullable String attributionTag, @NonNull IBinder token) {
+ this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
+ /*next*/ null);
+ }
+
+ /** @hide */
+ public AttributionSource(int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @NonNull IBinder token,
+ @Nullable AttributionSource next) {
+ this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, next);
}
/** @hide */
@@ -109,28 +118,32 @@
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
@Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, /*token*/ null, (renouncedPermissions != null)
+ this(uid, packageName, attributionTag, (renouncedPermissions != null)
? renouncedPermissions.toArray(new String[0]) : null, next);
}
/** @hide */
- public AttributionSource(@NonNull AttributionSource current,
- @Nullable AttributionSource next) {
+ public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) {
this(current.getUid(), current.getPackageName(), current.getAttributionTag(),
- /*token*/ null, /*renouncedPermissions*/ null, next);
+ current.getToken(), current.mAttributionSourceState.renouncedPermissions, next);
}
AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
- @Nullable IBinder token, @Nullable String[] renouncedPermissions,
+ @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) {
+ this(uid, packageName, attributionTag, new Binder(), renouncedPermissions, next);
+ }
+
+ AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
+ @NonNull IBinder token, @Nullable String[] renouncedPermissions,
@Nullable AttributionSource next) {
mAttributionSourceState = new AttributionSourceState();
mAttributionSourceState.uid = uid;
+ mAttributionSourceState.token = token;
mAttributionSourceState.packageName = packageName;
mAttributionSourceState.attributionTag = attributionTag;
- mAttributionSourceState.token = token;
mAttributionSourceState.renouncedPermissions = renouncedPermissions;
mAttributionSourceState.next = (next != null) ? new AttributionSourceState[]
- {next.mAttributionSourceState} : null;
+ {next.mAttributionSourceState} : new AttributionSourceState[0];
}
AttributionSource(@NonNull Parcel in) {
@@ -145,18 +158,12 @@
/** @hide */
public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
- getToken(), mAttributionSourceState.renouncedPermissions, next);
- }
-
- /** @hide */
- public AttributionSource withToken(@Nullable IBinder token) {
- return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
- token, mAttributionSourceState.renouncedPermissions, getNext());
+ mAttributionSourceState.renouncedPermissions, next);
}
/** @hide */
public AttributionSource withPackageName(@Nullable String packageName) {
- return new AttributionSource(getUid(), packageName, getAttributionTag(), getToken(),
+ return new AttributionSource(getUid(), packageName, getAttributionTag(),
mAttributionSourceState.renouncedPermissions, getNext());
}
@@ -165,6 +172,45 @@
return mAttributionSourceState;
}
+ /** @hide */
+ public @NonNull ScopedParcelState asScopedParcelState() {
+ return new ScopedParcelState(this);
+ }
+
+ /** @hide */
+ public static AttributionSource myAttributionSource() {
+ return new AttributionSource(Process.myUid(), ActivityThread.currentOpPackageName(),
+ /*attributionTag*/ null, (String[]) /*renouncedPermissions*/ null, /*next*/ null);
+ }
+
+ /**
+ * This is a scoped object that exposes the content of an attribution source
+ * as a parcel. This is useful when passing one to native and avoid custom
+ * conversion logic from Java to native state that needs to be kept in sync
+ * as attribution source evolves. This way we use the same logic for passing
+ * to native as the ones for passing in an IPC - in both cases this is the
+ * same auto generated code.
+ *
+ * @hide
+ */
+ public static class ScopedParcelState implements AutoCloseable {
+ private final Parcel mParcel;
+
+ public @NonNull Parcel getParcel() {
+ return mParcel;
+ }
+
+ public ScopedParcelState(AttributionSource attributionSource) {
+ mParcel = Parcel.obtain();
+ attributionSource.writeToParcel(mParcel, 0);
+ mParcel.setDataPosition(0);
+ }
+
+ public void close() {
+ mParcel.recycle();
+ }
+ }
+
/**
* If you are handling an IPC and you don't trust the caller you need to validate
* whether the attribution source is one for the calling app to prevent the caller
@@ -209,7 +255,8 @@
"attributionTag = " + mAttributionSourceState.attributionTag + ", " +
"token = " + mAttributionSourceState.token + ", " +
"next = " + (mAttributionSourceState.next != null
- ? mAttributionSourceState.next[0]: null) +
+ && mAttributionSourceState.next.length > 0
+ ? mAttributionSourceState.next[0] : null) +
" }";
}
return super.toString();
@@ -221,7 +268,8 @@
* @hide
*/
public int getNextUid() {
- if (mAttributionSourceState.next != null) {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
return mAttributionSourceState.next[0].uid;
}
return Process.INVALID_UID;
@@ -233,26 +281,42 @@
* @hide
*/
public @Nullable String getNextPackageName() {
- if (mAttributionSourceState.next != null) {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
return mAttributionSourceState.next[0].packageName;
}
return null;
}
/**
- * @return The nexxt package's attribution tag that would receive
+ * @return The next package's attribution tag that would receive
* the permission protected data.
*
* @hide
*/
public @Nullable String getNextAttributionTag() {
- if (mAttributionSourceState.next != null) {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
return mAttributionSourceState.next[0].attributionTag;
}
return null;
}
/**
+ * @return The next package's token that would receive
+ * the permission protected data.
+ *
+ * @hide
+ */
+ public @Nullable IBinder getNextToken() {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
+ return mAttributionSourceState.next[0].token;
+ }
+ return null;
+ }
+
+ /**
* Checks whether this attribution source can be trusted. That is whether
* the app it refers to created it and provided to the attribution chain.
*
@@ -311,7 +375,7 @@
*
* @hide
*/
- public @Nullable IBinder getToken() {
+ public @NonNull IBinder getToken() {
return mAttributionSourceState.token;
}
@@ -319,7 +383,8 @@
* The next app to receive the permission protected data.
*/
public @Nullable AttributionSource getNext() {
- if (mNextCached == null && mAttributionSourceState.next != null) {
+ if (mNextCached == null && mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
mNextCached = new AttributionSource(mAttributionSourceState.next[0]);
}
return mNextCached;
@@ -442,7 +507,7 @@
@RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x10;
+ mBuilderFieldsSet |= 0x8;
mAttributionSourceState.renouncedPermissions = (value != null)
? value.toArray(new String[0]) : null;
return this;
@@ -453,9 +518,9 @@
*/
public @NonNull Builder setNext(@Nullable AttributionSource value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x20;
+ mBuilderFieldsSet |= 0x10;
mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
- {value.mAttributionSourceState} : null;
+ {value.mAttributionSourceState} : mAttributionSourceState.next;
return this;
}
@@ -471,14 +536,16 @@
mAttributionSourceState.attributionTag = null;
}
if ((mBuilderFieldsSet & 0x8) == 0) {
- mAttributionSourceState.token = null;
- }
- if ((mBuilderFieldsSet & 0x10) == 0) {
mAttributionSourceState.renouncedPermissions = null;
}
- if ((mBuilderFieldsSet & 0x20) == 0) {
+ if ((mBuilderFieldsSet & 0x10) == 0) {
mAttributionSourceState.next = null;
}
+ mAttributionSourceState.token = new Binder();
+ if (mAttributionSourceState.next == null) {
+ // The NDK aidl backend doesn't support null parcelable arrays.
+ mAttributionSourceState.next = new AttributionSourceState[0];
+ }
return new AttributionSource(mAttributionSourceState);
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 88686a3..dc29c5e 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -49,6 +49,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.StorageManager;
+import android.permission.PermissionCheckerManager;
import android.text.TextUtils;
import android.util.Log;
@@ -670,7 +671,7 @@
}
}
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
private void enforceFilePermission(@NonNull AttributionSource attributionSource,
Uri uri, String mode)
throws FileNotFoundException, SecurityException {
@@ -687,7 +688,7 @@
}
}
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
private int enforceReadPermission(@NonNull AttributionSource attributionSource, Uri uri)
throws SecurityException {
final int result = enforceReadPermissionInner(uri, attributionSource);
@@ -705,7 +706,7 @@
return PermissionChecker.PERMISSION_GRANTED;
}
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
private int enforceWritePermission(@NonNull AttributionSource attributionSource, Uri uri)
throws SecurityException {
final int result = enforceWritePermissionInner(uri, attributionSource);
@@ -738,7 +739,7 @@
* Verify that calling app holds both the given permission and any app-op
* associated with that permission.
*/
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
private int checkPermission(String permission,
@NonNull AttributionSource attributionSource) {
if (Binder.getCallingPid() == Process.myPid()) {
@@ -753,7 +754,7 @@
}
/** {@hide} */
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
protected int enforceReadPermissionInner(Uri uri,
@NonNull AttributionSource attributionSource) throws SecurityException {
final Context context = getContext();
@@ -836,7 +837,7 @@
}
/** {@hide} */
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
protected int enforceWritePermissionInner(Uri uri,
@NonNull AttributionSource attributionSource) throws SecurityException {
final Context context = getContext();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fc676cf..1872a98 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4839,6 +4839,14 @@
public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller";
/**
+ * Official published name of the (internal) permission checker service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String PERMISSION_CHECKER_SERVICE = "permission_checker";
+
+ /**
* Use with {@link #getSystemService(String) to retrieve an
* {@link android.apphibernation.AppHibernationManager}} for
* communicating with the hibernation service.
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 66b74f2..8d3452e 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -16,19 +16,14 @@
package android.content;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.os.Binder;
-import android.os.IBinder;
import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.permission.IPermissionChecker;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import android.permission.PermissionCheckerManager;
+import android.permission.PermissionCheckerManager.PermissionResult;
/**
* This class provides permission check APIs that verify both the
@@ -75,7 +70,7 @@
*
* @hide
*/
- public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED;
+ public static final int PERMISSION_GRANTED = PermissionCheckerManager.PERMISSION_GRANTED;
/**
* The permission is denied. Applicable only to runtime and app op permissions.
@@ -89,7 +84,8 @@
*
* @hide
*/
- public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED;
+ public static final int PERMISSION_SOFT_DENIED =
+ PermissionCheckerManager.PERMISSION_SOFT_DENIED;
/**
* The permission is denied.
@@ -103,18 +99,12 @@
*
* @hide
*/
- public static final int PERMISSION_HARD_DENIED = IPermissionChecker.PERMISSION_HARD_DENIED;
+ public static final int PERMISSION_HARD_DENIED =
+ PermissionCheckerManager.PERMISSION_HARD_DENIED;
/** Constant when the PID for which we check permissions is unknown. */
public static final int PID_UNKNOWN = -1;
- /** @hide */
- @IntDef({PERMISSION_GRANTED,
- PERMISSION_SOFT_DENIED,
- PERMISSION_HARD_DENIED})
- @Retention(RetentionPolicy.SOURCE)
- public @interface PermissionResult {}
-
private static volatile IPermissionChecker sService;
private PermissionChecker() {
@@ -157,7 +147,7 @@
*
* @see #checkPermissionForPreflight(Context, String, int, int, String)
*/
- @PermissionResult
+ @PermissionCheckerManager.PermissionResult
public static int checkPermissionForDataDelivery(@NonNull Context context,
@NonNull String permission, int pid, int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable String message, boolean startDataDelivery) {
@@ -321,19 +311,13 @@
message, startDataDelivery, /*fromDatasource*/ false);
}
+ @SuppressWarnings("ConstantConditions")
private static int checkPermissionForDataDeliveryCommon(@NonNull Context context,
@NonNull String permission, @NonNull AttributionSource attributionSource,
@Nullable String message, boolean startDataDelivery, boolean fromDatasource) {
- // If the check failed in the middle of the chain, finish any started op.
- try {
- final int result = getPermissionCheckerService().checkPermission(permission,
- attributionSource.asState(), message, true /*forDataDelivery*/,
- startDataDelivery, fromDatasource);
- return result;
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return context.getSystemService(PermissionCheckerManager.class).checkPermission(permission,
+ attributionSource.asState(), message, true /*forDataDelivery*/, startDataDelivery,
+ fromDatasource, AppOpsManager.OP_NONE);
}
/**
@@ -365,17 +349,13 @@
* @see #checkPermissionForPreflight(Context, String, AttributionSource)
*/
@PermissionResult
+ @SuppressWarnings("ConstantConditions")
public static int checkPermissionAndStartDataDelivery(@NonNull Context context,
@NonNull String permission, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- try {
- return getPermissionCheckerService().checkPermission(permission,
- attributionSource.asState(), message, true /*forDataDelivery*/,
- /*startDataDelivery*/ true, /*fromDatasource*/ false);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return context.getSystemService(PermissionCheckerManager.class).checkPermission(
+ permission, attributionSource.asState(), message, true /*forDataDelivery*/,
+ /*startDataDelivery*/ true, /*fromDatasource*/ false, AppOpsManager.OP_NONE);
}
/**
@@ -404,17 +384,13 @@
* @see #finishDataDelivery(Context, String, AttributionSource)
*/
@PermissionResult
+ @SuppressWarnings("ConstantConditions")
public static int startOpForDataDelivery(@NonNull Context context,
@NonNull String opName, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- try {
- return getPermissionCheckerService().checkOp(
- AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
- true /*forDataDelivery*/, true /*startDataDelivery*/);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return context.getSystemService(PermissionCheckerManager.class).checkOp(
+ AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+ true /*forDataDelivery*/, true /*startDataDelivery*/);
}
/**
@@ -428,13 +404,32 @@
* @see #startOpForDataDelivery(Context, String, AttributionSource, String)
* @see #checkPermissionAndStartDataDelivery(Context, String, AttributionSource, String)
*/
+ @SuppressWarnings("ConstantConditions")
public static void finishDataDelivery(@NonNull Context context, @NonNull String op,
@NonNull AttributionSource attributionSource) {
- try {
- getPermissionCheckerService().finishDataDelivery(op, attributionSource.asState());
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
+ context.getSystemService(PermissionCheckerManager.class).finishDataDelivery(
+ AppOpsManager.strOpToOp(op), attributionSource.asState(),
+ /*fromDatasource*/ false);
+ }
+
+ /**
+ * Finishes an ongoing op for data access chain described by the given {@link
+ * AttributionSource}. Call this method if you are the datasource which would
+ * not finish an op for your attribution source as it was not started.
+ *
+ * @param context Context for accessing resources.
+ * @param op The op to finish.
+ * @param attributionSource The identity for which finish op.
+ *
+ * @see #startOpForDataDelivery(Context, String, AttributionSource, String)
+ * @see #checkPermissionAndStartDataDelivery(Context, String, AttributionSource, String)
+ */
+ @SuppressWarnings("ConstantConditions")
+ public static void finishDataDeliveryFromDatasource(@NonNull Context context,
+ @NonNull String op, @NonNull AttributionSource attributionSource) {
+ context.getSystemService(PermissionCheckerManager.class).finishDataDelivery(
+ AppOpsManager.strOpToOp(op), attributionSource.asState(),
+ /*fromDatasource*/ true);
}
/**
@@ -466,17 +461,13 @@
* @see #checkOpForDataDelivery(Context, String, AttributionSource, String)
*/
@PermissionResult
+ @SuppressWarnings("ConstantConditions")
public static int checkOpForPreflight(@NonNull Context context,
@NonNull String opName, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- try {
- return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
- attributionSource.asState(), message, false /*forDataDelivery*/,
- false /*startDataDelivery*/);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return context.getSystemService(PermissionCheckerManager.class).checkOp(
+ AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+ false /*forDataDelivery*/, false /*startDataDelivery*/);
}
/**
@@ -505,17 +496,13 @@
* @see #checkOpForPreflight(Context, String, AttributionSource, String)
*/
@PermissionResult
+ @SuppressWarnings("ConstantConditions")
public static int checkOpForDataDelivery(@NonNull Context context,
@NonNull String opName, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- try {
- return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
- attributionSource.asState(), message, true /*forDataDelivery*/,
- false /*startDataDelivery*/);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return context.getSystemService(PermissionCheckerManager.class).checkOp(
+ AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+ true /*forDataDelivery*/, false /*startDataDelivery*/);
}
/**
@@ -584,16 +571,13 @@
* String, boolean)
*/
@PermissionResult
+ @SuppressWarnings("ConstantConditions")
public static int checkPermissionForPreflight(@NonNull Context context,
@NonNull String permission, @NonNull AttributionSource attributionSource) {
- try {
- return getPermissionCheckerService().checkPermission(permission,
- attributionSource.asState(), null /*message*/, false /*forDataDelivery*/,
- /*startDataDelivery*/ false, /*fromDatasource*/ false);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return context.getSystemService(PermissionCheckerManager.class)
+ .checkPermission(permission, attributionSource.asState(), null /*message*/,
+ false /*forDataDelivery*/, /*startDataDelivery*/ false, /*fromDatasource*/ false,
+ AppOpsManager.OP_NONE);
}
/**
@@ -827,13 +811,4 @@
return checkPermissionForPreflight(context, permission, Binder.getCallingPid(),
Binder.getCallingUid(), packageName);
}
-
- private static @NonNull IPermissionChecker getPermissionCheckerService() {
- // Race is fine, we may end up looking up the same instance twice, no big deal.
- if (sService == null) {
- final IBinder service = ServiceManager.getService("permission_checker");
- sService = IPermissionChecker.Stub.asInterface(service);
- }
- return sService;
- }
}
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index ef075e1..9ab6955 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -87,7 +87,7 @@
boolean isAutoRevokeExempted(String packageName, int userId);
- AttributionSource registerAttributionSource(in AttributionSource source);
+ void registerAttributionSource(in AttributionSource source);
boolean isRegisteredAttributionSource(in AttributionSource source);
}
diff --git a/core/java/android/permission/PermissionCheckerManager.java b/core/java/android/permission/PermissionCheckerManager.java
new file mode 100644
index 0000000..7523816
--- /dev/null
+++ b/core/java/android/permission/PermissionCheckerManager.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2018 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.permission;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.AttributionSourceState;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Manager for checking runtime and app op permissions. This is a temporary
+ * class and we may fold its function in the PermissionManager once the
+ * permission re-architecture starts falling into place. The main benefit
+ * of this class is to allow context level caching.
+ *
+ * @hide
+ */
+public class PermissionCheckerManager {
+
+ /**
+ * The permission is granted.
+ */
+ public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED;
+
+ /**
+ * The permission is denied. Applicable only to runtime and app op permissions.
+ *
+ * <p>Returned when:
+ * <ul>
+ * <li>the runtime permission is granted, but the corresponding app op is denied
+ * for runtime permissions.</li>
+ * <li>the app ops is ignored for app op permissions.</li>
+ * </ul>
+ */
+ public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED;
+
+ /**
+ * The permission is denied.
+ *
+ * <p>Returned when:
+ * <ul>
+ * <li>the permission is denied for non app op permissions.</li>
+ * <li>the app op is denied or app op is {@link AppOpsManager#MODE_DEFAULT}
+ * and permission is denied.</li>
+ * </ul>
+ */
+ public static final int PERMISSION_HARD_DENIED = IPermissionChecker.PERMISSION_HARD_DENIED;
+
+ /** @hide */
+ @IntDef({PERMISSION_GRANTED,
+ PERMISSION_SOFT_DENIED,
+ PERMISSION_HARD_DENIED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PermissionResult {}
+
+ @NonNull
+ private final Context mContext;
+
+ @NonNull
+ private final IPermissionChecker mService;
+
+ @NonNull
+ private final PackageManager mPackageManager;
+
+ public PermissionCheckerManager(@NonNull Context context)
+ throws ServiceManager.ServiceNotFoundException {
+ mContext = context;
+ mService = IPermissionChecker.Stub.asInterface(ServiceManager.getServiceOrThrow(
+ Context.PERMISSION_CHECKER_SERVICE));
+ mPackageManager = context.getPackageManager();
+ }
+
+ /**
+ * Checks a permission by validating the entire attribution source chain. If the
+ * permission is associated with an app op the op is also noted/started for the
+ * entire attribution chain.
+ *
+ * @param permission The permission
+ * @param attributionSource The attribution chain to check.
+ * @param message Message associated with the permission if permission has an app op
+ * @param forDataDelivery Whether the check is for delivering data if permission has an app op
+ * @param startDataDelivery Whether to start data delivery (start op) if permission has
+ * an app op
+ * @param fromDatasource Whether the check is by a datasource (skip checks for the
+ * first attribution source in the chain as this is the datasource)
+ * @param attributedOp Alternative app op to attribute
+ * @return The permission check result.
+ */
+ @PermissionResult
+ public int checkPermission(@NonNull String permission,
+ @NonNull AttributionSourceState attributionSource, @Nullable String message,
+ boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource,
+ int attributedOp) {
+ Objects.requireNonNull(permission);
+ Objects.requireNonNull(attributionSource);
+ // Fast path for non-runtime, non-op permissions where the attribution chain has
+ // length one. This is the majority of the cases and we want these to be fast by
+ // hitting the local in process permission cache.
+ if (AppOpsManager.permissionToOpCode(permission) == AppOpsManager.OP_NONE) {
+ if (fromDatasource) {
+ if (attributionSource.next != null && attributionSource.next.length > 0) {
+ return mContext.checkPermission(permission, attributionSource.next[0].pid,
+ attributionSource.next[0].uid) == PackageManager.PERMISSION_GRANTED
+ ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+ }
+ } else {
+ return (mContext.checkPermission(permission, attributionSource.pid,
+ attributionSource.uid) == PackageManager.PERMISSION_GRANTED)
+ ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+ }
+ }
+ try {
+ return mService.checkPermission(permission, attributionSource, message, forDataDelivery,
+ startDataDelivery, fromDatasource, attributedOp);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return PERMISSION_HARD_DENIED;
+ }
+
+ /**
+ * Finishes an app op by validating the entire attribution source chain.
+ *
+ * @param op The op to finish.
+ * @param attributionSource The attribution chain to finish.
+ * @param fromDatasource Whether the finish is by a datasource (skip finish for the
+ * first attribution source in the chain as this is the datasource)
+ */
+ public void finishDataDelivery(int op, @NonNull AttributionSourceState attributionSource,
+ boolean fromDatasource) {
+ Objects.requireNonNull(attributionSource);
+ try {
+ mService.finishDataDelivery(op, attributionSource, fromDatasource);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks an app op by validating the entire attribution source chain. The op is
+ * also noted/started for the entire attribution chain.
+ *
+ * @param op The op to check.
+ * @param attributionSource The attribution chain to check.
+ * @param message Message associated with the permission if permission has an app op
+ * @param forDataDelivery Whether the check is for delivering data if permission has an app op
+ * @param startDataDelivery Whether to start data delivery (start op) if permission has
+ * an app op
+ * @return The op check result.
+ */
+ @PermissionResult
+ public int checkOp(int op, @NonNull AttributionSourceState attributionSource,
+ @Nullable String message, boolean forDataDelivery, boolean startDataDelivery) {
+ Objects.requireNonNull(attributionSource);
+ try {
+ return mService.checkOp(op, attributionSource, message, forDataDelivery,
+ startDataDelivery);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return PERMISSION_HARD_DENIED;
+ }
+}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index d490e7a..f3cc35b3 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1156,13 +1156,12 @@
* @hide
*/
@TestApi
- public @NonNull AttributionSource registerAttributionSource(@NonNull AttributionSource source) {
+ public void registerAttributionSource(@NonNull AttributionSource source) {
try {
- return mPermissionManager.registerAttributionSource(source);
+ mPermissionManager.registerAttributionSource(source);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
- return null;
}
/**
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index bb48757..b9ff5e7 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -65,22 +65,20 @@
private static final String TAG = "RecognitionService";
/** Debugging flag */
- private static final boolean DBG = true;
-
- private static final String RECORD_AUDIO_APP_OP =
- AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
- private static final int RECORD_AUDIO_APP_OP_CODE =
- AppOpsManager.permissionToOpCode(Manifest.permission.RECORD_AUDIO);
+ private static final boolean DBG = false;
/** Binder of the recognition service */
private RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);
/**
* The current callback of an application that invoked the
+ *
* {@link RecognitionService#onStartListening(Intent, Callback)} method
*/
private Callback mCurrentCallback = null;
+ private boolean mStartedDataDelivery;
+
private static final int MSG_START_LISTENING = 1;
private static final int MSG_STOP_LISTENING = 2;
@@ -120,6 +118,11 @@
mCurrentCallback = new Callback(listener, attributionSource);
RecognitionService.this.onStartListening(intent, mCurrentCallback);
+ if (!checkPermissionAndStartDataDelivery()) {
+ listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
+ Log.i(TAG, "caller doesn't have permission:"
+ + Manifest.permission.RECORD_AUDIO);
+ }
} else {
listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
Log.i(TAG, "concurrent startListening received - ignoring this call");
@@ -152,13 +155,15 @@
Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
} else { // the correct state
RecognitionService.this.onCancel(mCurrentCallback);
- mCurrentCallback = null;
+ dispatchClearCallback();
if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
}
}
private void dispatchClearCallback() {
+ finishDataDelivery();
mCurrentCallback = null;
+ mStartedDataDelivery = false;
}
private class StartListeningArgs {
@@ -177,7 +182,30 @@
/**
* Notifies the service that it should start listening for speech.
- *
+ *
+ * <p> If you are recognizing speech from the microphone, in this callback you
+ * should create an attribution context for the caller such that when you access
+ * the mic the caller would be properly blamed (and their permission checked in
+ * the process) for accessing the microphone and that you served as a proxy for
+ * this sensitive data (and your permissions would be checked in the process).
+ * You should also open the mic in this callback via the attribution context
+ * and close the mic before returning the recognized result. If you don't do
+ * that then the caller would be blamed and you as being a proxy as well as you
+ * would get one more blame on yourself when you open the microphone.
+ *
+ * <pre>
+ * Context attributionContext = context.createContext(new ContextParams.Builder()
+ * .setNextAttributionSource(callback.getCallingAttributionSource())
+ * .build());
+ *
+ * AudioRecord recorder = AudioRecord.Builder()
+ * .setContext(attributionContext);
+ * . . .
+ * .build();
+ *
+ * recorder.startRecording()
+ * </pre>
+ *
* @param recognizerIntent contains parameters for the recognition to be performed. The intent
* may also contain optional extras, see {@link RecognizerIntent}. If these values are
* not set explicitly, default values should be used by the recognizer.
@@ -335,57 +363,13 @@
return mCallingAttributionSource;
}
- boolean maybeStartAttribution() {
- if (DBG) {
- Log.i(TAG, "Starting attribution");
- }
-
- if (DBG && isProxyingRecordAudioToCaller()) {
- Log.i(TAG, "Proxying already in progress, not starting the attribution");
- }
-
- if (!isProxyingRecordAudioToCaller()) {
+ @NonNull Context getAttributionContextForCaller() {
+ if (mAttributionContext == null) {
mAttributionContext = createContext(new ContextParams.Builder()
.setNextAttributionSource(mCallingAttributionSource)
.build());
-
- final int result = PermissionChecker.checkPermissionAndStartDataDelivery(
- RecognitionService.this,
- Manifest.permission.RECORD_AUDIO,
- mAttributionContext.getAttributionSource(),
- /*message*/ null);
-
- return result == PermissionChecker.PERMISSION_GRANTED;
}
- return false;
- }
-
- void maybeFinishAttribution() {
- if (DBG) {
- Log.i(TAG, "Finishing attribution");
- }
-
- if (DBG && !isProxyingRecordAudioToCaller()) {
- Log.i(TAG, "Not proxying currently, not finishing the attribution");
- }
-
- if (isProxyingRecordAudioToCaller()) {
- PermissionChecker.finishDataDelivery(
- RecognitionService.this,
- RECORD_AUDIO_APP_OP,
- mAttributionContext.getAttributionSource());
-
- mAttributionContext = null;
- }
- }
-
- private boolean isProxyingRecordAudioToCaller() {
- final AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
- return appOpsManager.isProxying(
- RECORD_AUDIO_APP_OP_CODE,
- getAttributionTag(),
- mCallingAttributionSource.getUid(),
- mCallingAttributionSource.getPackageName());
+ return mAttributionContext;
}
}
@@ -435,4 +419,35 @@
mServiceRef.clear();
}
}
+
+ private boolean checkPermissionAndStartDataDelivery() {
+ if (isPerformingDataDelivery()) {
+ return true;
+ }
+ if (PermissionChecker.checkPermissionAndStartDataDelivery(
+ RecognitionService.this, Manifest.permission.RECORD_AUDIO,
+ mCurrentCallback.getAttributionContextForCaller().getAttributionSource(),
+ /*message*/ null) == PermissionChecker.PERMISSION_GRANTED) {
+ mStartedDataDelivery = true;
+ }
+ return mStartedDataDelivery;
+ }
+
+ void finishDataDelivery() {
+ if (mStartedDataDelivery) {
+ mStartedDataDelivery = false;
+ final String op = AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
+ PermissionChecker.finishDataDelivery(RecognitionService.this, op,
+ mCurrentCallback.getAttributionContextForCaller().getAttributionSource());
+ }
+ }
+
+ @SuppressWarnings("ConstantCondition")
+ private boolean isPerformingDataDelivery() {
+ final int op = AppOpsManager.permissionToOpCode(Manifest.permission.RECORD_AUDIO);
+ final AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
+ return appOpsManager.isProxying(op, getAttributionTag(),
+ mCurrentCallback.getCallingAttributionSource().getUid(),
+ mCurrentCallback.getCallingAttributionSource().getPackageName());
+ }
}
diff --git a/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl b/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
index 510af77..ae6ad32 100644
--- a/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
@@ -18,5 +18,6 @@
// Iterface to observe op active changes
oneway interface IAppOpsActiveCallback {
- void opActiveChanged(int op, int uid, String packageName, boolean active);
+ void opActiveChanged(int op, int uid, String packageName, String attributionTag,
+ boolean active, int attributionFlags, int attributionChainId);
}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index c112d09..9ad4572 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -41,7 +41,8 @@
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage);
SyncNotedAppOp startOperation(IBinder clientId, int code, int uid, String packageName,
@nullable String attributionTag, boolean startIfModeDefault,
- boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage);
+ boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+ int attributionFlags, int attributionChainId);
@UnsupportedAppUsage
void finishOperation(IBinder clientId, int code, int uid, String packageName,
@nullable String attributionTag);
@@ -57,10 +58,12 @@
SyncNotedAppOp noteProxyOperation(int code, in AttributionSource attributionSource,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation);
- SyncNotedAppOp startProxyOperation(IBinder clientId, int code, in AttributionSource attributionSource,
+ SyncNotedAppOp startProxyOperation(int code, in AttributionSource attributionSource,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
- boolean shouldCollectMessage, boolean skipProxyOperation);
- void finishProxyOperation(IBinder clientId, int code, in AttributionSource attributionSource);
+ boolean shouldCollectMessage, boolean skipProxyOperation, int proxyAttributionFlags,
+ int proxiedAttributionFlags, int attributionChainId);
+ void finishProxyOperation(int code, in AttributionSource attributionSource,
+ boolean skipProxyOperation);
// Remaining methods are only used in Java.
int checkPackage(int uid, String packageName);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 1f805c9..323b401 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -221,7 +221,6 @@
"fd_utils.cpp",
"android_hardware_input_InputWindowHandle.cpp",
"android_hardware_input_InputApplicationHandle.cpp",
- "permission_utils.cpp",
],
static_libs: [
@@ -240,7 +239,6 @@
"audioflinger-aidl-cpp",
"av-types-aidl-cpp",
"android.hardware.camera.device@3.2",
- "media_permission-aidl-cpp",
"libandroidicu",
"libbpf_android",
"libnetdbpf",
@@ -257,6 +255,7 @@
"libgraphicsenv",
"libgui",
"libmediandk",
+ "libpermission",
"libsensor",
"libinput",
"libcamera_client",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f76cccb..de6a2b5 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -98,7 +98,6 @@
extern int register_android_media_MicrophoneInfo(JNIEnv *env);
extern int register_android_media_ToneGenerator(JNIEnv *env);
extern int register_android_media_midi(JNIEnv *env);
-extern int register_android_media_permission_Identity(JNIEnv* env);
namespace android {
@@ -1589,7 +1588,6 @@
REG_JNI(register_android_media_RemoteDisplay),
REG_JNI(register_android_media_ToneGenerator),
REG_JNI(register_android_media_midi),
- REG_JNI(register_android_media_permission_Identity),
REG_JNI(register_android_opengl_classes),
REG_JNI(register_android_server_NetworkManagementSocketTagger),
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 83dc1e0..bce4ed7 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -22,13 +22,15 @@
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include "core_jni_helpers.h"
-#include "permission_utils.h"
#include <utils/Log.h>
#include <media/AudioRecord.h>
#include <media/MicrophoneInfo.h>
#include <vector>
+#include <android/content/AttributionSourceState.h>
+#include <android_os_Parcel.h>
+
#include <nativehelper/ScopedUtfChars.h>
#include "android_media_AudioFormat.h"
@@ -38,10 +40,8 @@
#include "android_media_MicrophoneInfo.h"
#include "android_media_AudioAttributes.h"
-// ----------------------------------------------------------------------------
-using android::media::permission::convertIdentity;
-using android::media::permission::Identity;
+// ----------------------------------------------------------------------------
using namespace android;
@@ -189,7 +189,7 @@
jobject jaa, jintArray jSampleRate, jint channelMask,
jint channelIndexMask, jint audioFormat,
jint buffSizeInBytes, jintArray jSession,
- jobject jIdentity, jlong nativeRecordInJavaObj,
+ jobject jAttributionSource, jlong nativeRecordInJavaObj,
jint sharedAudioHistoryMs) {
//ALOGV(">> Entering android_media_AudioRecord_setup");
//ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d "
@@ -260,14 +260,18 @@
size_t bytesPerSample = audio_bytes_per_sample(format);
if (buffSizeInBytes == 0) {
- ALOGE("Error creating AudioRecord: frameCount is 0.");
+ ALOGE("Error creating AudioRecord: frameCount is 0.");
return (jint) AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT;
}
size_t frameSize = channelCount * bytesPerSample;
size_t frameCount = buffSizeInBytes / frameSize;
// create an uninitialized AudioRecord object
- lpRecorder = new AudioRecord(convertIdentity(env, jIdentity));
+ Parcel* parcel = parcelForJavaObject(env, jAttributionSource);
+ android::content::AttributionSourceState attributionSource;
+ attributionSource.readFromParcel(parcel);
+
+ lpRecorder = new AudioRecord(attributionSource);
// read the AudioAttributes values
auto paa = JNIAudioAttributeHelper::makeUnique();
@@ -912,7 +916,7 @@
{"native_start", "(II)I", (void *)android_media_AudioRecord_start},
{"native_stop", "()V", (void *)android_media_AudioRecord_stop},
{"native_setup",
- "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/media/permission/Identity;JI)I",
+ "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/os/Parcel;JI)I",
(void *)android_media_AudioRecord_setup},
{"native_finalize", "()V", (void *)android_media_AudioRecord_finalize},
{"native_release", "()V", (void *)android_media_AudioRecord_release},
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index de5df20..73d2d8d 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -48,7 +48,6 @@
using namespace android;
using ::android::media::VolumeShaper;
-using ::android::media::permission::Identity;
// ----------------------------------------------------------------------------
static const char* const kClassPathName = "android/media/AudioTrack";
@@ -330,9 +329,10 @@
// create the native AudioTrack object
ScopedUtfChars opPackageNameStr(env, opPackageName);
// TODO b/182469354: make consistent with AudioRecord
- Identity identity = Identity();
- identity.packageName = std::string(opPackageNameStr.c_str());
- lpTrack = new AudioTrack(identity);
+ AttributionSourceState attributionSource;
+ attributionSource.packageName = std::string(opPackageNameStr.c_str());
+ attributionSource.token = sp<BBinder>::make();
+ lpTrack = new AudioTrack(attributionSource);
// read the AudioAttributes values
auto paa = JNIAudioAttributeHelper::makeUnique();
@@ -395,7 +395,7 @@
offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK
: AudioTrack::TRANSFER_SYNC,
(offload || encapsulationMode) ? &offloadInfo : NULL,
- Identity(), // default uid, pid values
+ AttributionSourceState(), // default uid, pid values
paa.get());
break;
@@ -421,7 +421,7 @@
sessionId, // audio session ID
AudioTrack::TRANSFER_SHARED,
NULL, // default offloadInfo
- Identity(), // default uid, pid values
+ AttributionSourceState(), // default uid, pid values
paa.get());
break;
diff --git a/core/jni/permission_utils.cpp b/core/jni/permission_utils.cpp
deleted file mode 100644
index 2b7ef99..0000000
--- a/core/jni/permission_utils.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-#include "permission_utils.h"
-#include "core_jni_helpers.h"
-
-static struct {
- jfieldID fieldUid; // Identity.uid
- jfieldID fieldPid; // Identity.pid
- jfieldID fieldPackageName; // Identity.packageName
- jfieldID fieldAttributionTag; // Identity.attributionTag
-} javaIdentityFields;
-
-static const JNINativeMethod method_table[] = {
- // no static methods, currently
-};
-
-int register_android_media_permission_Identity(JNIEnv* env) {
- jclass identityClass = android::FindClassOrDie(env, "android/media/permission/Identity");
- javaIdentityFields.fieldUid = android::GetFieldIDOrDie(env, identityClass, "uid", "I");
- javaIdentityFields.fieldPid = android::GetFieldIDOrDie(env, identityClass, "pid", "I");
- javaIdentityFields.fieldPackageName =
- android::GetFieldIDOrDie(env, identityClass, "packageName", "Ljava/lang/String;");
- javaIdentityFields.fieldAttributionTag =
- android::GetFieldIDOrDie(env, identityClass, "attributionTag", "Ljava/lang/String;");
-
- return android::RegisterMethodsOrDie(env, "android/media/permission/Identity", method_table,
- NELEM(method_table));
-}
-
-namespace android::media::permission {
-
-Identity convertIdentity(JNIEnv* env, const jobject& jIdentity) {
- Identity identity;
-
- identity.uid = env->GetIntField(jIdentity, javaIdentityFields.fieldUid);
- identity.pid = env->GetIntField(jIdentity, javaIdentityFields.fieldPid);
-
- jstring packageNameStr = static_cast<jstring>(
- env->GetObjectField(jIdentity, javaIdentityFields.fieldPackageName));
- if (packageNameStr == nullptr) {
- identity.packageName = std::nullopt;
- } else {
- identity.packageName = std::string(ScopedUtfChars(env, packageNameStr).c_str());
- }
-
- jstring attributionTagStr = static_cast<jstring>(
- env->GetObjectField(jIdentity, javaIdentityFields.fieldAttributionTag));
- if (attributionTagStr == nullptr) {
- identity.attributionTag = std::nullopt;
- } else {
- identity.attributionTag = std::string(ScopedUtfChars(env, attributionTagStr).c_str());
- }
-
- return identity;
-}
-
-} // namespace android::media::permission
diff --git a/core/jni/permission_utils.h b/core/jni/permission_utils.h
deleted file mode 100644
index d625bb6..0000000
--- a/core/jni/permission_utils.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-#pragma once
-
-#include <android/media/permission/Identity.h>
-#include <jni.h>
-
-namespace android::media::permission {
-
-Identity convertIdentity(JNIEnv* env, const jobject& jIdentity);
-}
-
-int register_android_media_permission_Identity(JNIEnv* env);
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 0d44a85..7c6ae28 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -16,8 +16,6 @@
package android.media;
-import static android.media.permission.PermissionUtil.myIdentity;
-
import android.annotation.CallbackExecutor;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -29,12 +27,13 @@
import android.annotation.TestApi;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AttributionSource;
+import android.content.AttributionSource.ScopedParcelState;
import android.content.Context;
import android.media.MediaRecorder.Source;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioPolicy;
import android.media.metrics.LogSessionId;
-import android.media.permission.Identity;
import android.media.projection.MediaProjection;
import android.os.Binder;
import android.os.Build;
@@ -42,6 +41,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -381,7 +381,8 @@
* {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at construction
* time. See also {@link AudioManager#generateAudioSessionId()} to obtain a session ID before
* construction.
- * @param context An optional context to pull an attribution tag from.
+ * @param context An optional context on whose behalf the recoding is performed.
+ *
* @throws IllegalArgumentException
*/
private AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
@@ -449,10 +450,11 @@
audioBuffSizeCheck(bufferSizeInBytes);
- Identity identity = myIdentity(context);
- if (identity.packageName == null) {
+ AttributionSource attributionSource = (context != null)
+ ? context.getAttributionSource() : AttributionSource.myAttributionSource();
+ if (attributionSource.getPackageName() == null) {
// Command line utility
- identity.packageName = "uid:" + Binder.getCallingUid();
+ attributionSource = attributionSource.withPackageName("uid:" + Binder.getCallingUid());
}
int[] sampleRate = new int[] {mSampleRate};
@@ -461,14 +463,15 @@
//TODO: update native initialization when information about hardware init failure
// due to capture device already open is available.
- int initResult = native_setup(new WeakReference<AudioRecord>(this),
- mAudioAttributes, sampleRate, mChannelMask, mChannelIndexMask,
- mAudioFormat, mNativeBufferSizeInBytes,
- session, identity, 0 /*nativeRecordInJavaObj*/,
- maxSharedAudioHistoryMs);
- if (initResult != SUCCESS) {
- loge("Error code "+initResult+" when initializing native AudioRecord object.");
- return; // with mState == STATE_UNINITIALIZED
+ try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
+ int initResult = native_setup(new WeakReference<AudioRecord>(this), mAudioAttributes,
+ sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
+ mNativeBufferSizeInBytes, session, attributionSourceState.getParcel(),
+ 0 /*nativeRecordInJavaObj*/, maxSharedAudioHistoryMs);
+ if (initResult != SUCCESS) {
+ loge("Error code " + initResult + " when initializing native AudioRecord object.");
+ return; // with mState == STATE_UNINITIALIZED
+ }
}
mSampleRate = sampleRate[0];
@@ -512,23 +515,27 @@
*/
/* package */ void deferred_connect(long nativeRecordInJavaObj) {
if (mState != STATE_INITIALIZED) {
- int[] session = { 0 };
- int[] rates = { 0 };
+ int[] session = {0};
+ int[] rates = {0};
//TODO: update native initialization when information about hardware init failure
// due to capture device already open is available.
// Note that for this native_setup, we are providing an already created/initialized
// *Native* AudioRecord, so the attributes parameters to native_setup() are ignored.
- int initResult = native_setup(new WeakReference<AudioRecord>(this),
- null /*mAudioAttributes*/,
- rates /*mSampleRates*/,
- 0 /*mChannelMask*/,
- 0 /*mChannelIndexMask*/,
- 0 /*mAudioFormat*/,
- 0 /*mNativeBufferSizeInBytes*/,
- session,
- myIdentity(null),
- nativeRecordInJavaObj,
- 0);
+ final int initResult;
+ try (ScopedParcelState attributionSourceState = AttributionSource.myAttributionSource()
+ .asScopedParcelState()) {
+ initResult = native_setup(new WeakReference<>(this),
+ null /*mAudioAttributes*/,
+ rates /*mSampleRates*/,
+ 0 /*mChannelMask*/,
+ 0 /*mChannelIndexMask*/,
+ 0 /*mAudioFormat*/,
+ 0 /*mNativeBufferSizeInBytes*/,
+ session,
+ attributionSourceState.getParcel(),
+ nativeRecordInJavaObj,
+ 0);
+ }
if (initResult != SUCCESS) {
loge("Error code "+initResult+" when initializing native AudioRecord object.");
return; // with mState == STATE_UNINITIALIZED
@@ -620,8 +627,8 @@
/**
* Sets the context the record belongs to. This context will be used to pull information,
- * such as attribution tags, which will be associated with the AudioRecord. However, the
- * context itself will not be retained by the AudioRecord.
+ * such as {@link android.content.AttributionSource}, which will be associated with
+ * the AudioRecord. However, the context itself will not be retained by the AudioRecord.
* @param context a non-null {@link Context} instance
* @return the same Builder instance.
*/
@@ -2216,7 +2223,7 @@
//--------------------
/**
- * @deprecated Use native_setup that takes an Identity object
+ * @deprecated Use native_setup that takes an {@link AttributionSource} object
* @return
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R,
@@ -2227,18 +2234,20 @@
int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
int buffSizeInBytes, int[] sessionId, String opPackageName,
long nativeRecordInJavaObj) {
- Identity identity = myIdentity(null);
- identity.packageName = opPackageName;
-
- return native_setup(audiorecordThis, attributes, sampleRate, channelMask, channelIndexMask,
- audioFormat, buffSizeInBytes, sessionId, identity, nativeRecordInJavaObj, 0);
+ AttributionSource attributionSource = AttributionSource.myAttributionSource()
+ .withPackageName(opPackageName);
+ try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
+ return native_setup(audiorecordThis, attributes, sampleRate, channelMask,
+ channelIndexMask, audioFormat, buffSizeInBytes, sessionId,
+ attributionSourceState.getParcel(), nativeRecordInJavaObj, 0);
+ }
}
private native int native_setup(Object audiorecordThis,
Object /*AudioAttributes*/ attributes,
int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
- int buffSizeInBytes, int[] sessionId, Identity identity, long nativeRecordInJavaObj,
- int maxSharedAudioHistoryMs);
+ int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource,
+ long nativeRecordInJavaObj, int maxSharedAudioHistoryMs);
// TODO remove: implementation calls directly into implementation of native_release()
private native void native_finalize();
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index db3e8b0..ccd830a 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -18,7 +18,6 @@
import static android.Manifest.permission.BIND_IMS_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.media.permission.PermissionUtil.myIdentity;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -27,6 +26,8 @@
import android.annotation.SystemApi;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AttributionSource;
+import android.content.AttributionSource.ScopedParcelState;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
@@ -34,7 +35,6 @@
import android.graphics.SurfaceTexture;
import android.media.SubtitleController.Anchor;
import android.media.SubtitleTrack.RenderingWidget;
-import android.media.permission.Identity;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -55,7 +55,6 @@
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
@@ -684,14 +683,18 @@
mTimeProvider = new TimeProvider(this);
mOpenSubtitleSources = new Vector<InputStream>();
- Identity identity = myIdentity(null);
+ AttributionSource attributionSource = AttributionSource.myAttributionSource();
// set the package name to empty if it was null
- identity.packageName = TextUtils.emptyIfNull(identity.packageName);
+ if (attributionSource.getPackageName() == null) {
+ attributionSource = attributionSource.withPackageName("");
+ }
/* Native setup requires a weak reference to our object.
* It's easier to create it here than in C++.
*/
- native_setup(new WeakReference<MediaPlayer>(this), identity);
+ try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
+ native_setup(new WeakReference<MediaPlayer>(this), attributionSourceState.getParcel());
+ }
baseRegisterPlayer(sessionId);
}
@@ -2474,7 +2477,8 @@
private native final int native_setMetadataFilter(Parcel request);
private static native final void native_init();
- private native void native_setup(Object mediaplayerThis, @NonNull Identity identity);
+ private native void native_setup(Object mediaplayerThis,
+ @NonNull Parcel attributionSource);
private native final void native_finalize();
/**
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index c63e64a..da18a77 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -16,8 +16,6 @@
package android.media;
-import static android.media.permission.PermissionUtil.myIdentity;
-
import android.annotation.CallbackExecutor;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -27,14 +25,16 @@
import android.annotation.SystemApi;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AttributionSource;
+import android.content.AttributionSource.ScopedParcelState;
import android.content.Context;
import android.hardware.Camera;
import android.media.metrics.LogSessionId;
-import android.media.permission.Identity;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
import android.os.PersistableBundle;
import android.util.ArrayMap;
import android.util.Log;
@@ -163,8 +163,11 @@
/* Native setup requires a weak reference to our object.
* It's easier to create it here than in C++.
*/
- native_setup(new WeakReference<MediaRecorder>(this),
- ActivityThread.currentPackageName(), myIdentity(context));
+ try (ScopedParcelState attributionSourceState = context.getAttributionSource()
+ .asScopedParcelState()) {
+ native_setup(new WeakReference<>(this), ActivityThread.currentPackageName(),
+ attributionSourceState.getParcel());
+ }
}
/**
@@ -1898,14 +1901,15 @@
publicAlternatives = "{@link MediaRecorder}")
private void native_setup(Object mediarecorderThis,
String clientName, String opPackageName) throws IllegalStateException {
- Identity identity = myIdentity(null);
- identity.packageName = opPackageName;
-
- native_setup(mediarecorderThis, clientName, identity);
+ AttributionSource attributionSource = AttributionSource.myAttributionSource()
+ .withPackageName(opPackageName);
+ try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
+ native_setup(mediarecorderThis, clientName, attributionSourceState.getParcel());
+ }
}
private native void native_setup(Object mediarecorderThis,
- String clientName, Identity identity)
+ String clientName, @NonNull Parcel attributionSource)
throws IllegalStateException;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index fd3c405..70bb960 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -16,8 +16,6 @@
package android.media.audiofx;
-import static android.media.permission.PermissionUtil.myIdentity;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -26,10 +24,11 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AttributionSource;
+import android.content.AttributionSource.ScopedParcelState;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioSystem;
-import android.media.permission.Identity;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -518,10 +517,13 @@
// native initialization
// TODO b/182469354: Make consistent with AudioRecord
- int initResult = native_setup(new WeakReference<AudioEffect>(this),
- type.toString(), uuid.toString(), priority, audioSession,
- deviceType, deviceAddress,
- id, desc, myIdentity(null), probe);
+ int initResult;
+ try (ScopedParcelState attributionSourceState = AttributionSource.myAttributionSource()
+ .asScopedParcelState()) {
+ initResult = native_setup(new WeakReference<>(this), type.toString(), uuid.toString(),
+ priority, audioSession, deviceType, deviceAddress, id, desc,
+ attributionSourceState.getParcel(), probe);
+ }
if (initResult != SUCCESS && initResult != ALREADY_EXISTS) {
Log.e(TAG, "Error code " + initResult
+ " when initializing AudioEffect.");
@@ -1388,7 +1390,7 @@
private native final int native_setup(Object audioeffect_this, String type,
String uuid, int priority, int audioSession,
int deviceType, String deviceAddress, int[] id, Object[] desc,
- Identity identity, boolean probe);
+ @NonNull Parcel attributionSource, boolean probe);
private native final void native_finalize();
diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index 58c9e65..3349277 100644
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -16,12 +16,13 @@
package android.media.audiofx;
-import static android.media.permission.PermissionUtil.myIdentity;
-
+import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
-import android.media.permission.Identity;
+import android.content.AttributionSource;
+import android.content.AttributionSource.ScopedParcelState;
import android.os.Handler;
import android.os.Looper;
+import android.os.Parcel;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -222,8 +223,12 @@
// native initialization
// TODO b/182469354: make consistent with AudioRecord
- int result = native_setup(new WeakReference<Visualizer>(this), audioSession, id,
- myIdentity(null));
+ int result;
+ try (ScopedParcelState attributionSourceState = AttributionSource.myAttributionSource()
+ .asScopedParcelState()) {
+ result = native_setup(new WeakReference<>(this), audioSession, id,
+ attributionSourceState.getParcel());
+ }
if (result != SUCCESS && result != ALREADY_EXISTS) {
Log.e(TAG, "Error code "+result+" when initializing Visualizer.");
switch (result) {
@@ -690,7 +695,7 @@
private native final int native_setup(Object audioeffect_this,
int audioSession,
int[] id,
- Identity identity);
+ @NonNull Parcel attributionSource);
@GuardedBy("mStateLock")
private native final void native_finalize();
diff --git a/media/java/android/media/permission/PermissionUtil.java b/media/java/android/media/permission/PermissionUtil.java
index 92fe882..b08d111d 100644
--- a/media/java/android/media/permission/PermissionUtil.java
+++ b/media/java/android/media/permission/PermissionUtil.java
@@ -51,24 +51,6 @@
* @hide
*/
public class PermissionUtil {
- /**
- * Create an identity for the current process and the passed context.
- *
- * @param context The process the identity is for. If {@code null}, the process's default
- * identity is chosen.
- * @return The identity for the current process and context
- */
- public static @NonNull Identity myIdentity(@Nullable Context context) {
- Identity identity = new Identity();
-
- identity.pid = Process.myPid();
- identity.uid = Process.myUid();
- identity.packageName = context != null ? context.getOpPackageName()
- : ActivityThread.currentOpPackageName();
- identity.attributionTag = context != null ? context.getAttributionTag() : null;
-
- return identity;
- }
/**
* Authenticate an originator, where the binder call is coming from a middleman.
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index d49790e..bc73f6a 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -51,6 +51,7 @@
shared_libs: [
"audioclient-types-aidl-cpp",
"av-types-aidl-cpp",
+ "framework-permission-aidl-cpp",
"libandroid_runtime",
"libaudioclient",
"libnativehelper",
@@ -85,7 +86,6 @@
"android.hardware.drm@1.4",
"android.hidl.memory@1.0",
"android.hidl.token@1.0-utils",
- "media_permission-aidl-cpp",
],
header_libs: [
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index a360759..2636ab2 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -17,7 +17,6 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaPlayer-JNI"
-#include "permission_utils.h"
#include "utils/Log.h"
#include <media/mediaplayer.h>
@@ -80,8 +79,6 @@
using namespace android;
using media::VolumeShaper;
-using media::permission::Identity;
-using media::permission::convertIdentity;
// ----------------------------------------------------------------------------
@@ -949,11 +946,14 @@
static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
- jobject jIdentity)
+ jobject jAttributionSource)
{
ALOGV("native_setup");
- sp<MediaPlayer> mp = new MediaPlayer(convertIdentity(env, jIdentity));
+ Parcel* parcel = parcelForJavaObject(env, jAttributionSource);
+ android::content::AttributionSourceState attributionSource;
+ attributionSource.readFromParcel(parcel);
+ sp<MediaPlayer> mp = new MediaPlayer(attributionSource);
if (mp == NULL) {
jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
return;
@@ -1409,7 +1409,7 @@
{"native_setMetadataFilter", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_setMetadataFilter},
{"native_getMetadata", "(ZZLandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_getMetadata},
{"native_init", "()V", (void *)android_media_MediaPlayer_native_init},
- {"native_setup", "(Ljava/lang/Object;Landroid/media/permission/Identity;)V",(void *)android_media_MediaPlayer_native_setup},
+ {"native_setup", "(Ljava/lang/Object;Landroid/os/Parcel;)V",(void *)android_media_MediaPlayer_native_setup},
{"native_finalize", "()V", (void *)android_media_MediaPlayer_native_finalize},
{"getAudioSessionId", "()I", (void *)android_media_MediaPlayer_get_audio_session_id},
{"native_setAudioSessionId", "(I)V", (void *)android_media_MediaPlayer_set_audio_session_id},
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 6641123..7ef0f77 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -24,7 +24,6 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaRecorderJNI"
-#include "permission_utils.h"
#include <utils/Log.h>
#include <gui/Surface.h>
@@ -46,13 +45,13 @@
#include <system/audio.h>
#include <android_runtime/android_view_Surface.h>
+#include <android/content/AttributionSourceState.h>
+#include <android_os_Parcel.h>
// ----------------------------------------------------------------------------
using namespace android;
-using android::media::permission::convertIdentity;
-
// ----------------------------------------------------------------------------
// helper function to extract a native Camera object from a Camera Java object
@@ -620,11 +619,14 @@
static void
android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
- jstring packageName, jobject jIdentity)
+ jstring packageName, jobject jAttributionSource)
{
ALOGV("setup");
- sp<MediaRecorder> mr = new MediaRecorder(convertIdentity(env, jIdentity));
+ Parcel* parcel = parcelForJavaObject(env, jAttributionSource);
+ android::content::AttributionSourceState attributionSource;
+ attributionSource.readFromParcel(parcel);
+ sp<MediaRecorder> mr = new MediaRecorder(attributionSource);
if (mr == NULL) {
jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
@@ -871,7 +873,7 @@
{"native_reset", "()V", (void *)android_media_MediaRecorder_native_reset},
{"release", "()V", (void *)android_media_MediaRecorder_release},
{"native_init", "()V", (void *)android_media_MediaRecorder_native_init},
- {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Landroid/media/permission/Identity;)V",
+ {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Landroid/os/Parcel;)V",
(void *)android_media_MediaRecorder_native_setup},
{"native_finalize", "()V", (void *)android_media_MediaRecorder_native_finalize},
{"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface },
diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp
index bfed983..2ddfacf 100644
--- a/media/jni/audioeffect/Android.bp
+++ b/media/jni/audioeffect/Android.bp
@@ -19,6 +19,7 @@
],
shared_libs: [
+ "framework-permission-aidl-cpp",
"liblog",
"libcutils",
"libutils",
@@ -27,11 +28,11 @@
"libaudioclient",
"libaudioutils",
"libaudiofoundation",
- "media_permission-aidl-cpp",
+ "libbinder"
],
export_shared_lib_headers: [
- "media_permission-aidl-cpp",
+ "framework-permission-aidl-cpp",
],
version_script: "exports.lds",
diff --git a/media/jni/audioeffect/Visualizer.cpp b/media/jni/audioeffect/Visualizer.cpp
index 8a52456..84a8d51 100644
--- a/media/jni/audioeffect/Visualizer.cpp
+++ b/media/jni/audioeffect/Visualizer.cpp
@@ -28,14 +28,16 @@
#include <cutils/bitops.h>
#include <utils/Thread.h>
+#include <android/content/AttributionSourceState.h>
+
#include "Visualizer.h"
namespace android {
// ---------------------------------------------------------------------------
-Visualizer::Visualizer (const Identity& identity)
- : AudioEffect(identity)
+Visualizer::Visualizer (const android::content::AttributionSourceState& attributionSource)
+ : AudioEffect(attributionSource)
{
}
diff --git a/media/jni/audioeffect/Visualizer.h b/media/jni/audioeffect/Visualizer.h
index 3ee91f0..aa07ce8 100644
--- a/media/jni/audioeffect/Visualizer.h
+++ b/media/jni/audioeffect/Visualizer.h
@@ -20,9 +20,7 @@
#include <media/AudioEffect.h>
#include <system/audio_effects/effect_visualizer.h>
#include <utils/Thread.h>
-#include "android/media/permission/Identity.h"
-
-using namespace android::media::permission;
+#include "android/content/AttributionSourceState.h"
/**
* The Visualizer class enables application to retrieve part of the currently playing audio for
@@ -68,9 +66,9 @@
/* Constructor.
* See AudioEffect constructor for details on parameters.
*/
- explicit Visualizer(const Identity& identity);
+ explicit Visualizer(const android::content::AttributionSourceState& attributionSource);
- ~Visualizer();
+ ~Visualizer();
/**
* Initialize an uninitialized Visualizer.
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index 953b7e0..3a8decd 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -25,7 +25,9 @@
#include <nativehelper/JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
#include "media/AudioEffect.h"
-#include "permission_utils.h"
+
+#include <android/content/AttributionSourceState.h>
+#include <android_os_Parcel.h>
#include <nativehelper/ScopedUtfChars.h>
@@ -35,8 +37,6 @@
using namespace android;
-using media::permission::convertIdentity;
-
#define AUDIOEFFECT_SUCCESS 0
#define AUDIOEFFECT_ERROR (-1)
#define AUDIOEFFECT_ERROR_ALREADY_EXISTS (-2)
@@ -273,7 +273,7 @@
android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jstring type, jstring uuid, jint priority, jint sessionId,
jint deviceType, jstring deviceAddress,
- jintArray jId, jobjectArray javadesc, jobject jIdentity, jboolean probe)
+ jintArray jId, jobjectArray javadesc, jobject jAttributionSource, jboolean probe)
{
ALOGV("android_media_AudioEffect_native_setup");
AudioEffectJniStorage* lpJniStorage = NULL;
@@ -285,6 +285,8 @@
effect_descriptor_t desc;
jobject jdesc;
AudioDeviceTypeAddr device;
+ AttributionSourceState attributionSource;
+ Parcel* parcel = NULL;
setAudioEffect(env, thiz, 0);
@@ -338,7 +340,9 @@
}
// create the native AudioEffect object
- lpAudioEffect = new AudioEffect(convertIdentity(env, jIdentity));
+ parcel = parcelForJavaObject(env, jAttributionSource);
+ attributionSource.readFromParcel(parcel);
+ lpAudioEffect = new AudioEffect(attributionSource);
if (lpAudioEffect == 0) {
ALOGE("Error creating AudioEffect");
goto setup_failure;
@@ -774,7 +778,7 @@
// Dalvik VM type signatures
static const JNINativeMethod gMethods[] = {
{"native_init", "()V", (void *)android_media_AudioEffect_native_init},
- {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;IIILjava/lang/String;[I[Ljava/lang/Object;Landroid/media/permission/Identity;Z)I",
+ {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;IIILjava/lang/String;[I[Ljava/lang/Object;Landroid/os/Parcel;Z)I",
(void *)android_media_AudioEffect_native_setup},
{"native_finalize", "()V", (void *)android_media_AudioEffect_native_finalize},
{"native_release", "()V", (void *)android_media_AudioEffect_native_release},
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index 439715c..b30f00f 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -25,12 +25,16 @@
#include <android_runtime/AndroidRuntime.h>
#include <utils/threads.h>
#include "Visualizer.h"
-#include "permission_utils.h"
#include <nativehelper/ScopedUtfChars.h>
+#include <android/content/AttributionSourceState.h>
+#include <android_os_Parcel.h>
+
using namespace android;
+using content::AttributionSourceState;
+
#define VISUALIZER_SUCCESS 0
#define VISUALIZER_ERROR (-1)
#define VISUALIZER_ERROR_ALREADY_EXISTS (-2)
@@ -348,13 +352,15 @@
static jint
android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
- jint sessionId, jintArray jId, jobject jIdentity)
+ jint sessionId, jintArray jId, jobject jAttributionSource)
{
ALOGV("android_media_visualizer_native_setup");
VisualizerJniStorage* lpJniStorage = NULL;
int lStatus = VISUALIZER_ERROR_NO_MEMORY;
sp<Visualizer> lpVisualizer;
jint* nId = NULL;
+ AttributionSourceState attributionSource;
+ Parcel* parcel = nullptr;
setVisualizer(env, thiz, 0);
@@ -381,7 +387,9 @@
}
// create the native Visualizer object
- lpVisualizer = new Visualizer(convertIdentity(env, jIdentity));
+ parcel = parcelForJavaObject(env, jAttributionSource);
+ attributionSource.readFromParcel(parcel);
+ lpVisualizer = sp<Visualizer>::make(attributionSource);
if (lpVisualizer == 0) {
ALOGE("Error creating Visualizer");
goto setup_failure;
@@ -678,7 +686,7 @@
// Dalvik VM type signatures
static const JNINativeMethod gMethods[] = {
{"native_init", "()V", (void *)android_media_visualizer_native_init},
- {"native_setup", "(Ljava/lang/Object;I[ILandroid/media/permission/Identity;)I",
+ {"native_setup", "(Ljava/lang/Object;I[ILandroid/os/Parcel;)I",
(void *)android_media_visualizer_native_setup},
{"native_finalize", "()V", (void *)android_media_visualizer_native_finalize},
{"native_release", "()V", (void *)android_media_visualizer_native_release},
diff --git a/media/jni/soundpool/Android.bp b/media/jni/soundpool/Android.bp
index 4227cd8..ee473f5 100644
--- a/media/jni/soundpool/Android.bp
+++ b/media/jni/soundpool/Android.bp
@@ -126,6 +126,7 @@
],
shared_libs: [
+ "framework-permission-aidl-cpp",
"libaudioutils",
"liblog",
"libcutils",
@@ -135,7 +136,6 @@
"libaudioclient",
"libmediandk",
"libbinder",
- "media_permission-aidl-cpp",
],
cflags: [
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
index 95fe000..bbbef38 100644
--- a/media/jni/soundpool/Stream.cpp
+++ b/media/jni/soundpool/Stream.cpp
@@ -17,7 +17,7 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "SoundPool::Stream"
#include <utils/Log.h>
-#include<android/media/permission/Identity.h>
+#include <android/content/AttributionSourceState.h>
#include "Stream.h"
@@ -25,8 +25,6 @@
namespace android::soundpool {
-using media::permission::Identity;
-
Stream::~Stream()
{
ALOGV("%s(%p)", __func__, this);
@@ -330,15 +328,16 @@
// do not create a new audio track if current track is compatible with sound parameters
- Identity identity = Identity();
- identity.packageName = mStreamManager->getOpPackageName();
+ android::content::AttributionSourceState attributionSource;
+ attributionSource.packageName = mStreamManager->getOpPackageName();
+ attributionSource.token = sp<BBinder>::make();
// TODO b/182469354 make consistent with AudioRecord, add util for native source
newTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
staticCallback, userData,
0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
AudioTrack::TRANSFER_DEFAULT,
- nullptr /*offloadInfo*/, identity,
+ nullptr /*offloadInfo*/, attributionSource,
mStreamManager->getAttributes(),
false /*doNotReconnect*/, 1.0f /*maxRequiredSpeed*/);
// Set caller name so it can be logged in destructor.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 562f8f44..84a24d8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -806,7 +806,8 @@
}
mAm.mAppOpsService.startOperation(AppOpsManager.getToken(mAm.mAppOpsService),
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null,
- true, false, null, false);
+ true, false, null, false, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+ AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
}
final ServiceMap smap = getServiceMapLocked(r.userId);
@@ -1875,7 +1876,8 @@
mAm.mAppOpsService.startOperation(
AppOpsManager.getToken(mAm.mAppOpsService),
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName,
- null, true, false, "", false);
+ null, true, false, "", false, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+ AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
registerAppOpCallbackLocked(r);
mAm.updateForegroundServiceUsageStats(r.name, r.userId, true);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b518490..172c3f7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -156,6 +156,7 @@
import android.app.AnrController;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.AppOpsManager.AttributionFlags;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
import android.app.ApplicationErrorReport;
import android.app.ApplicationExitInfo;
@@ -346,6 +347,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.HeptFunction;
import com.android.internal.util.function.HexFunction;
import com.android.internal.util.function.NonaFunction;
@@ -353,6 +355,7 @@
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.QuintFunction;
import com.android.internal.util.function.TriFunction;
+import com.android.internal.util.function.UndecFunction;
import com.android.server.AlarmManagerInternal;
import com.android.server.DeviceIdleInternal;
import com.android.server.DisplayThread;
@@ -1833,7 +1836,9 @@
final int[] cameraOp = {AppOpsManager.OP_CAMERA};
mAppOpsService.startWatchingActive(cameraOp, new IAppOpsActiveCallback.Stub() {
@Override
- public void opActiveChanged(int op, int uid, String packageName, boolean active) {
+ public void opActiveChanged(int op, int uid, String packageName, String attributionTag,
+ boolean active, @AttributionFlags int attributionFlags,
+ int attributionChainId) {
cameraActiveChanged(uid, active);
}
});
@@ -16851,7 +16856,7 @@
try {
return superImpl.apply(code, new AttributionSource(shellUid,
"com.android.shell", attributionSource.getAttributionTag(),
- attributionSource.getNext()),
+ attributionSource.getToken(), attributionSource.getNext()),
shouldCollectAsyncNotedOp, message, shouldCollectMessage,
skiProxyOperation);
} finally {
@@ -16867,8 +16872,9 @@
@Nullable String packageName, @Nullable String attributionTag,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
@Nullable String message, boolean shouldCollectMessage,
- @NonNull NonaFunction<IBinder, Integer, Integer, String, String, Boolean,
- Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
+ @AttributionFlags int attributionFlags, int attributionChainId,
+ @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean,
+ Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl) {
if (uid == mTargetUid && isTargetOp(code)) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
Process.SHELL_UID);
@@ -16876,57 +16882,62 @@
try {
return superImpl.apply(token, code, shellUid, "com.android.shell",
attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
+ shouldCollectMessage, attributionFlags, attributionChainId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
return superImpl.apply(token, code, uid, packageName, attributionTag,
- startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ attributionFlags, attributionChainId);
}
@Override
- public SyncNotedAppOp startProxyOperation(IBinder token, int code,
+ public SyncNotedAppOp startProxyOperation(int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
- boolean skipProsyOperation, @NonNull OctFunction<IBinder, Integer,
- AttributionSource, Boolean, Boolean, String, Boolean, Boolean,
- SyncNotedAppOp> superImpl) {
+ boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+ @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
+ @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean,
+ Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
- return superImpl.apply(token, code, new AttributionSource(shellUid,
- "com.android.shell", attributionSource.getAttributionTag(),
- attributionSource.getNext()), startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- skipProsyOperation);
+ return superImpl.apply(code, new AttributionSource(shellUid,
+ "com.android.shell", attributionSource.getAttributionTag(),
+ attributionSource.getToken(), attributionSource.getNext()),
+ startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+ proxiedAttributionFlags, attributionChainId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
- return superImpl.apply(token, code, attributionSource, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProsyOperation);
+ return superImpl.apply(code, attributionSource, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
+ proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
@Override
- public void finishProxyOperation(IBinder clientId, int code,
- @NonNull AttributionSource attributionSource,
- @NonNull TriFunction<IBinder, Integer, AttributionSource, Void> superImpl) {
+ public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
+ boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource,
+ Boolean, Void> superImpl) {
if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
- superImpl.apply(clientId, code, new AttributionSource(shellUid,
+ superImpl.apply(code, new AttributionSource(shellUid,
"com.android.shell", attributionSource.getAttributionTag(),
- attributionSource.getNext()));
+ attributionSource.getToken(), attributionSource.getNext()),
+ skipProxyOperation);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
- superImpl.apply(clientId, code, attributionSource);
+ superImpl.apply(code, attributionSource, skipProxyOperation);
}
private boolean isTargetOp(int code) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 541dcdc..fdb3d8c 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -84,6 +84,7 @@
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.AppOpsManager.AttributionFlags;
import android.app.AppOpsManager.AttributedOpEntry;
import android.app.AppOpsManager.HistoricalOps;
import android.app.AppOpsManager.Mode;
@@ -200,6 +201,7 @@
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
public class AppOpsService extends IAppOpsService.Stub {
@@ -257,11 +259,6 @@
private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
- //TODO: remove this when development is done.
- private static final int DEBUG_FGS_ALLOW_WHILE_IN_USE = 0;
- private static final int DEBUG_FGS_ENFORCE_TYPE = 1;
-
-
final Context mContext;
final AtomicFile mFile;
private final @Nullable File mNoteOpCallerStacktracesFile;
@@ -414,9 +411,10 @@
}
InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
- @NonNull Runnable onDeath, int proxyUid, @Nullable String proxyPackageName,
- @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
- @OpFlags int flags) throws RemoteException {
+ @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid,
+ @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+ @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags
+ int attributionFlags, int attributionChainId) throws RemoteException {
InProgressStartOpEvent recycled = acquire();
@@ -427,13 +425,14 @@
}
if (recycled != null) {
- recycled.reinit(startTime, elapsedTime, clientId, onDeath, uidState, flags,
- proxyInfo, mOpEventProxyInfoPool);
+ recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath,
+ uidState, flags, proxyInfo, attributionFlags, attributionChainId,
+ mOpEventProxyInfoPool);
return recycled;
}
- return new InProgressStartOpEvent(startTime, elapsedTime, clientId, onDeath, uidState,
- proxyInfo, flags);
+ return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag,
+ onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId);
}
}
@@ -685,6 +684,9 @@
/** Id of the client that started the event */
private @NonNull IBinder mClientId;
+ /** The attribution tag for this operation */
+ private @Nullable String mAttributionTag;
+
/** To call when client dies */
private @NonNull Runnable mOnDeath;
@@ -700,30 +702,44 @@
/** How many times the op was started but not finished yet */
int numUnfinishedStarts;
+ /** The attribution flags related to this event */
+ private @AttributionFlags int mAttributionFlags;
+
+ /** The id of the attribution chain this even is a part of */
+ private int mAttributionChainId;
+
/**
* Create a new {@link InProgressStartOpEvent}.
*
* @param startTime The time {@link #startOperation} was called
* @param startElapsedTime The elapsed time when {@link #startOperation} was called
* @param clientId The client id of the caller of {@link #startOperation}
+ * @param attributionTag The attribution tag for the operation.
* @param onDeath The code to execute on client death
* @param uidState The uidstate of the app {@link #startOperation} was called for
+ * @param attributionFlags the attribution flags for this operation.
+ * @param attributionChainId the unique id of the attribution chain this op is a part of.
* @param proxy The proxy information, if {@link #startProxyOperation} was called
* @param flags The trusted/nontrusted/self flags.
*
* @throws RemoteException If the client is dying
*/
private InProgressStartOpEvent(long startTime, long startElapsedTime,
- @NonNull IBinder clientId, @NonNull Runnable onDeath,
- @AppOpsManager.UidState int uidState, @Nullable OpEventProxyInfo proxy,
- @OpFlags int flags) throws RemoteException {
+ @NonNull IBinder clientId, @Nullable String attributionTag,
+ @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState,
+ @Nullable OpEventProxyInfo proxy, @OpFlags int flags,
+ @AttributionFlags int attributionFlags, int attributionChainId)
+ throws RemoteException {
mStartTime = startTime;
mStartElapsedTime = startElapsedTime;
mClientId = clientId;
+ mAttributionTag = attributionTag;
mOnDeath = onDeath;
mUidState = uidState;
mProxy = proxy;
mFlags = flags;
+ mAttributionFlags = attributionFlags;
+ mAttributionChainId = attributionChainId;
clientId.linkToDeath(this, 0);
}
@@ -744,21 +760,27 @@
* @param startTime The time {@link #startOperation} was called
* @param startElapsedTime The elapsed time when {@link #startOperation} was called
* @param clientId The client id of the caller of {@link #startOperation}
+ * @param attributionTag The attribution tag for this operation.
* @param onDeath The code to execute on client death
* @param uidState The uidstate of the app {@link #startOperation} was called for
* @param flags The flags relating to the proxy
* @param proxy The proxy information, if {@link #startProxyOperation} was called
+ * @param attributionFlags the attribution flags for this operation.
+ * @param attributionChainId the unique id of the attribution chain this op is a part of.
* @param proxyPool The pool to release previous {@link OpEventProxyInfo} to
*
* @throws RemoteException If the client is dying
*/
public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId,
- @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState, @OpFlags int flags,
- @Nullable OpEventProxyInfo proxy, @NonNull Pools.Pool<OpEventProxyInfo> proxyPool
+ @Nullable String attributionTag, @NonNull Runnable onDeath,
+ @AppOpsManager.UidState int uidState, @OpFlags int flags,
+ @Nullable OpEventProxyInfo proxy, @AttributionFlags int attributionFlags,
+ int attributionChainId, @NonNull Pools.Pool<OpEventProxyInfo> proxyPool
) throws RemoteException {
mStartTime = startTime;
mStartElapsedTime = startElapsedTime;
mClientId = clientId;
+ mAttributionTag = attributionTag;
mOnDeath = onDeath;
mUidState = uidState;
mFlags = flags;
@@ -767,6 +789,8 @@
proxyPool.release(mProxy);
}
mProxy = proxy;
+ mAttributionFlags = attributionFlags;
+ mAttributionChainId = attributionChainId;
clientId.linkToDeath(this, 0);
}
@@ -791,7 +815,7 @@
return mUidState;
}
- /** @return proxy info for the access */
+ /** @return proxy tag for the access */
public @Nullable OpEventProxyInfo getProxy() {
return mProxy;
}
@@ -800,6 +824,16 @@
public @OpFlags int getFlags() {
return mFlags;
}
+
+ /** @return attributoin flags used for the access */
+ public @AttributionFlags int getAttributionFlags() {
+ return mAttributionFlags;
+ }
+
+ /** @return attributoin chiang id for the access */
+ public int getAttributionChainId() {
+ return mAttributionChainId;
+ }
}
private final class AttributedOp {
@@ -943,32 +977,38 @@
* @param proxyAttributionTag The attribution tag of the proxy app
* @param uidState UID state of the app startOp is called for
* @param flags The proxy flags
+ * @param attributionFlags The attribution flags associated with this operation.
+ * @param attributionChainId The if of the attribution chain this operations is a part of.
*/
public void started(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @OpFlags int flags) throws RemoteException {
- started(clientId, proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
- true);
+ @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags
+ int attributionFlags, int attributionChainId) throws RemoteException {
+ started(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
+ uidState, flags,/*triggerCallbackIfNeeded*/ true, attributionFlags,
+ attributionChainId);
}
private void started(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
@AppOpsManager.UidState int uidState, @OpFlags int flags,
- boolean triggerCallbackIfNeeded) throws RemoteException {
- startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
- uidState, flags, triggerCallbackIfNeeded, true);
+ boolean triggerCallbackIfNeeded, @AttributionFlags int attributionFlags,
+ int attributionChainId) throws RemoteException {
+ startedOrPaused(clientId, proxyUid, proxyPackageName,
+ proxyAttributionTag, uidState, flags, triggerCallbackIfNeeded,
+ /*triggerCallbackIfNeeded*/ true, attributionFlags, attributionChainId);
}
private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
@AppOpsManager.UidState int uidState, @OpFlags int flags,
- boolean triggerCallbackIfNeeded, boolean isStarted) throws RemoteException {
+ boolean triggerCallbackIfNeeded, boolean isStarted, @AttributionFlags
+ int attributionFlags, int attributionChainId) throws RemoteException {
if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) {
- scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, true);
+ scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName,
+ tag, true, attributionFlags, attributionChainId);
}
-
if (isStarted && mInProgressEvents == null) {
mInProgressEvents = new ArrayMap<>(1);
} else if (mPausedInProgressEvents == null) {
@@ -981,9 +1021,10 @@
InProgressStartOpEvent event = events.get(clientId);
if (event == null) {
event = mInProgressStartOpEventPool.acquire(startTime,
- SystemClock.elapsedRealtime(), clientId,
+ SystemClock.elapsedRealtime(), clientId, tag,
PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
- proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags);
+ proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
+ attributionFlags, attributionChainId);
events.put(clientId, event);
} else {
if (uidState != event.mUidState) {
@@ -994,10 +1035,10 @@
event.numUnfinishedStarts++;
if (isStarted) {
+ // TODO: Consider storing the attribution chain flags and id
mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, tag, uidState, flags, startTime);
}
-
}
/**
@@ -1063,7 +1104,8 @@
// TODO ntmyren: Also callback for single attribution tag activity changes
if (triggerCallbackIfNeeded && !parent.isRunning()) {
scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, false);
+ parent.packageName, tag, false, event.getAttributionFlags(),
+ event.getAttributionChainId());
}
}
}
@@ -1103,9 +1145,10 @@
*/
public void createPaused(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @OpFlags int flags) throws RemoteException {
+ @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags
+ int attributionFlags, int attributionChainId) throws RemoteException {
startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
- uidState, flags, true, false);
+ uidState, flags, true, false, attributionFlags, attributionChainId);
}
/**
@@ -1124,9 +1167,11 @@
InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
mPausedInProgressEvents.put(event.mClientId, event);
finishOrPause(event.mClientId, true, true);
+
+ scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
+ parent.packageName, tag, false,
+ event.getAttributionFlags(), event.getAttributionChainId());
}
- scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, false);
mInProgressEvents = null;
}
@@ -1153,10 +1198,10 @@
event.mStartTime = startTime;
mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, tag, event.mUidState, event.mFlags, startTime);
- }
- if (shouldSendActive) {
- scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, true);
+ if (shouldSendActive) {
+ scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName,
+ tag, true, event.getAttributionFlags(), event.getAttributionChainId());
+ }
}
mPausedInProgressEvents = null;
}
@@ -1210,10 +1255,12 @@
// previously removed unfinished start counts back
if (proxy != null) {
started(event.getClientId(), proxy.getUid(), proxy.getPackageName(),
- proxy.getAttributionTag(), newState, event.getFlags(), false);
+ proxy.getAttributionTag(), newState, event.getFlags(), false,
+ event.getAttributionFlags(), event.getAttributionChainId());
} else {
started(event.getClientId(), Process.INVALID_UID, null, null, newState,
- OP_FLAG_SELF, false);
+ OP_FLAG_SELF, false, event.getAttributionFlags(),
+ event.getAttributionChainId());
}
InProgressStartOpEvent newEvent = mInProgressEvents.get(binders.get(i));
@@ -3321,9 +3368,10 @@
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
AppOpsManager.MODE_IGNORED);
if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
- + " package " + packageName);
+ + " package " + packageName + "flags: " +
+ AppOpsManager.flagsToString(flags));
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
- packageName);
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
}
final Op op = getOpLocked(ops, code, uid, true);
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
@@ -3349,7 +3397,7 @@
if (uidMode != AppOpsManager.MODE_ALLOWED) {
if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
- + packageName);
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
attributedOp.rejected(uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
uidMode);
@@ -3362,7 +3410,7 @@
if (mode != AppOpsManager.MODE_ALLOWED) {
if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
- + packageName);
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
attributedOp.rejected(uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
mode);
@@ -3373,7 +3421,8 @@
Slog.d(TAG,
"noteOperation: allowing code " + code + " uid " + uid + " package "
+ packageName + (attributionTag == null ? ""
- : "." + attributionTag));
+ : "." + attributionTag) + " flags: "
+ + AppOpsManager.flagsToString(flags));
}
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
AppOpsManager.MODE_ALLOWED);
@@ -3674,16 +3723,18 @@
public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
@Nullable String packageName, @Nullable String attributionTag,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
- String message, boolean shouldCollectMessage) {
+ String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+ int attributionChainId) {
return mCheckOpsDelegateDispatcher.startOperation(token, code, uid, packageName,
attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
+ shouldCollectMessage, attributionFlags, attributionChainId);
}
private SyncNotedAppOp startOperationImpl(@NonNull IBinder clientId, int code, int uid,
@Nullable String packageName, @Nullable String attributionTag,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message,
- boolean shouldCollectMessage) {
+ boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+ int attributionChainId) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
@@ -3707,29 +3758,36 @@
}
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage, false);
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
+ attributionChainId, /*dryRun*/ false);
}
@Override
- public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
+ public SyncNotedAppOp startProxyOperation(int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
- boolean skipProxyOperation) {
- return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource,
+ boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+ @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
+ return mCheckOpsDelegateDispatcher.startProxyOperation(code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- skipProxyOperation);
+ skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
+ attributionChainId);
}
- private SyncNotedAppOp startProxyOperationImpl(IBinder clientId, int code,
+ private SyncNotedAppOp startProxyOperationImpl(int code,
@NonNull AttributionSource attributionSource,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
- boolean shouldCollectMessage, boolean skipProxyOperation) {
+ boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
+ int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
+ int attributionChainId) {
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
+ final IBinder proxyToken = attributionSource.getToken();
final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+ final IBinder proxiedToken = attributionSource.getNextToken();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
@@ -3762,10 +3820,11 @@
if (!skipProxyOperation) {
// Test if the proxied operation will succeed before starting the proxy operation
- final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code, proxiedUid,
- resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
+ final SyncNotedAppOp testProxiedOp = startOperationUnchecked(proxiedToken, code,
+ proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage, true);
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ proxiedAttributionFlags, attributionChainId, /*dryRun*/ true);
if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
return testProxiedOp;
}
@@ -3773,19 +3832,21 @@
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
- final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
+ final SyncNotedAppOp proxyAppOp = startOperationUnchecked(proxyToken, code, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
- shouldCollectMessage, false);
+ shouldCollectMessage, proxyAttributionFlags, attributionChainId,
+ /*dryRun*/ false);
if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
return proxyAppOp;
}
}
- return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
+ return startOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName,
proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, false);
+ shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
+ /*dryRun*/ false);
}
private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
@@ -3796,7 +3857,8 @@
@NonNull String packageName, @Nullable String attributionTag, int proxyUid,
String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage, boolean dryRun) {
+ boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+ int attributionChainId, boolean dryRun) {
RestrictionBypass bypass;
try {
bypass = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3818,7 +3880,8 @@
flags, AppOpsManager.MODE_IGNORED);
}
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
- + " package " + packageName);
+ + " package " + packageName + " flags: "
+ + AppOpsManager.flagsToString(flags));
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -3835,7 +3898,7 @@
if (DEBUG) {
Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
- + packageName);
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
}
if (!dryRun) {
attributedOp.rejected(uidState.state, flags);
@@ -3852,7 +3915,7 @@
&& (!startIfModeDefault || mode != MODE_DEFAULT)) {
if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
- + packageName);
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
if (!dryRun) {
attributedOp.rejected(uidState.state, flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
@@ -3862,15 +3925,18 @@
}
}
if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
- + " package " + packageName + " restricted: " + isRestricted);
+ + " package " + packageName + " restricted: " + isRestricted
+ + " flags: " + AppOpsManager.flagsToString(flags));
if (!dryRun) {
try {
if (isRestricted) {
attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.state, flags);
+ proxyAttributionTag, uidState.state, flags, attributionFlags,
+ attributionChainId);
} else {
attributedOp.started(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.state, flags);
+ proxyAttributionTag, uidState.state, flags, attributionFlags,
+ attributionChainId);
}
} catch (RemoteException e) {
throw new RuntimeException(e);
@@ -3905,21 +3971,26 @@
}
@Override
- public void finishProxyOperation(IBinder clientId, int code,
- @NonNull AttributionSource attributionSource) {
- mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource);
+ public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
+ boolean skipProxyOperation) {
+ mCheckOpsDelegateDispatcher.finishProxyOperation(code, attributionSource,
+ skipProxyOperation);
}
- private Void finishProxyOperationImpl(IBinder clientId, int code,
- @NonNull AttributionSource attributionSource) {
+ private Void finishProxyOperationImpl(int code, @NonNull AttributionSource attributionSource,
+ boolean skipProxyOperation) {
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
+ final IBinder proxyToken = attributionSource.getToken();
final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+ final IBinder proxiedToken = attributionSource.getNextToken();
- verifyIncomingUid(proxyUid);
+ skipProxyOperation = resolveSkipProxyOperation(skipProxyOperation, attributionSource);
+
+ verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid));
verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid));
@@ -3930,8 +4001,10 @@
return null;
}
- finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
- proxyAttributionTag);
+ if (!skipProxyOperation) {
+ finishOperationUnchecked(proxyToken, code, proxyUid, resolvedProxyPackageName,
+ proxyAttributionTag);
+ }
String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
proxiedPackageName);
@@ -3939,7 +4012,7 @@
return null;
}
- finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
+ finishOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName,
proxiedAttributionTag);
return null;
@@ -3981,8 +4054,9 @@
}
}
- private void scheduleOpActiveChangedIfNeededLocked(int code, int uid, String packageName,
- boolean active) {
+ private void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
+ String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
+ int attributionFlags, int attributionChainId) {
ArraySet<ActiveCallback> dispatchedCallbacks = null;
final int callbackListCount = mActiveWatchers.size();
for (int i = 0; i < callbackListCount; i++) {
@@ -4003,11 +4077,13 @@
}
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpActiveChanged,
- this, dispatchedCallbacks, code, uid, packageName, active));
+ this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
+ attributionFlags, attributionChainId));
}
private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
- int code, int uid, String packageName, boolean active) {
+ int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
+ boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
// There are features watching for mode changes such as window manager
// and location manager which are in our process. The callbacks in these
// features may require permissions our remote caller does not have.
@@ -4017,7 +4093,8 @@
for (int i = 0; i < callbackCount; i++) {
final ActiveCallback callback = callbacks.valueAt(i);
try {
- callback.mCallback.opActiveChanged(code, uid, packageName, active);
+ callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
+ active, attributionFlags, attributionChainId);
} catch (RemoteException e) {
/* do nothing */
}
@@ -5023,6 +5100,8 @@
}
static class Shell extends ShellCommand {
+ static final AtomicInteger sAttributionChainIds = new AtomicInteger(0);
+
final IAppOpsService mInterface;
final AppOpsService mInternal;
@@ -5491,7 +5570,9 @@
if (shell.packageName != null) {
shell.mInterface.startOperation(shell.mToken, shell.op, shell.packageUid,
shell.packageName, shell.attributionTag, true, true,
- "appops start shell command", true);
+ "appops start shell command", true,
+ AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR,
+ shell.sAttributionChainIds.incrementAndGet());
} else {
return -1;
}
@@ -6421,7 +6502,9 @@
@NonNull String proxiedPackageName) {
Objects.requireNonNull(proxyPackageName);
Objects.requireNonNull(proxiedPackageName);
- Binder.withCleanCallingIdentity(() -> {
+ final long callingUid = Binder.getCallingUid();
+ final long identity = Binder.clearCallingIdentity();
+ try {
final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
proxiedPackageName, new int[] {op});
if (packageOps == null || packageOps.isEmpty()) {
@@ -6436,13 +6519,13 @@
return false;
}
final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
- AppOpsManager.OP_FLAG_TRUSTED_PROXY
- | AppOpsManager.OP_FLAG_UNTRUSTED_PROXY);
- return proxyInfo != null && Binder.getCallingUid() == proxyInfo.getUid()
+ OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
+ return proxyInfo != null && callingUid == proxyInfo.getUid()
&& proxyPackageName.equals(proxyInfo.getPackageName())
&& Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
- });
- return false;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
@@ -7275,89 +7358,101 @@
public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
@Nullable String packageName, @NonNull String attributionTag,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
- @Nullable String message, boolean shouldCollectMessage) {
+ @Nullable String message, boolean shouldCollectMessage,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
return mPolicy.startOperation(token, code, uid, packageName,
attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, this::startDelegateOperationImpl);
+ shouldCollectMessage, attributionFlags, attributionChainId,
+ this::startDelegateOperationImpl);
} else {
return mPolicy.startOperation(token, code, uid, packageName, attributionTag,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, AppOpsService.this::startOperationImpl);
+ shouldCollectMessage, attributionFlags, attributionChainId,
+ AppOpsService.this::startOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
return startDelegateOperationImpl(token, code, uid, packageName, attributionTag,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
+ shouldCollectMessage, attributionFlags, attributionChainId);
}
return startOperationImpl(token, code, uid, packageName, attributionTag,
- startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ attributionFlags, attributionChainId);
}
private SyncNotedAppOp startDelegateOperationImpl(IBinder token, int code, int uid,
@Nullable String packageName, @Nullable String attributionTag,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
- boolean shouldCollectMessage) {
+ boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+ int attributionChainId) {
return mCheckOpsDelegate.startOperation(token, code, uid, packageName, attributionTag,
startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- AppOpsService.this::startOperationImpl);
+ attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
}
- public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
+ public SyncNotedAppOp startProxyOperation(int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
- boolean skipProxyOperation) {
+ boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+ @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
- return mPolicy.startProxyOperation(clientId, code, attributionSource,
+ return mPolicy.startProxyOperation(code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, skipProxyOperation,
+ shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+ proxiedAttributionFlags, attributionChainId,
this::startDelegateProxyOperationImpl);
} else {
- return mPolicy.startProxyOperation(clientId, code, attributionSource,
+ return mPolicy.startProxyOperation(code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, skipProxyOperation,
+ shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+ proxiedAttributionFlags, attributionChainId,
AppOpsService.this::startProxyOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
- return startDelegateProxyOperationImpl(clientId, code,
- attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, skipProxyOperation);
+ return startDelegateProxyOperationImpl(code, attributionSource,
+ startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+ proxiedAttributionFlags, attributionChainId);
}
- return startProxyOperationImpl(clientId, code, attributionSource, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation);
+ return startProxyOperationImpl(code, attributionSource, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
+ proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
-
- private SyncNotedAppOp startDelegateProxyOperationImpl(IBinder token, int code,
+ private SyncNotedAppOp startDelegateProxyOperationImpl(int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
- boolean skipProxyOperation) {
- return mCheckOpsDelegate.startProxyOperation(token, code, attributionSource,
+ boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+ @AttributionFlags int proxiedAttributionFlsgs, int attributionChainId) {
+ return mCheckOpsDelegate.startProxyOperation(code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- skipProxyOperation, AppOpsService.this::startProxyOperationImpl);
+ skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlsgs,
+ attributionChainId, AppOpsService.this::startProxyOperationImpl);
}
- public void finishProxyOperation(IBinder clientId, int code,
- @NonNull AttributionSource attributionSource) {
+ public void finishProxyOperation(int code,
+ @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
- mPolicy.finishProxyOperation(clientId, code, attributionSource,
- this::finishDelegateProxyOperationImpl);
+ mPolicy.finishProxyOperation(code, attributionSource,
+ skipProxyOperation, this::finishDelegateProxyOperationImpl);
} else {
- mPolicy.finishProxyOperation(clientId, code, attributionSource,
- AppOpsService.this::finishProxyOperationImpl);
+ mPolicy.finishProxyOperation(code, attributionSource,
+ skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
- finishDelegateProxyOperationImpl(clientId, code, attributionSource);
+ finishDelegateProxyOperationImpl(code, attributionSource, skipProxyOperation);
+ } else {
+ finishProxyOperationImpl(code, attributionSource, skipProxyOperation);
}
- finishProxyOperationImpl(clientId, code, attributionSource);
}
- private Void finishDelegateProxyOperationImpl(IBinder clientId, int code,
- @NonNull AttributionSource attributionSource) {
- mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
+ private Void finishDelegateProxyOperationImpl(int code,
+ @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
+ mCheckOpsDelegate.finishProxyOperation(code, attributionSource, skipProxyOperation,
AppOpsService.this::finishProxyOperationImpl);
return null;
}
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index c1209d4..62db886 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -154,7 +154,7 @@
// must have the required permission and the users must be in the same profile group
// in order to launch any of its own activities.
if (callerUserId != userId) {
- final int permissionFlag = PermissionChecker.checkPermissionForPreflight(
+ final int permissionFlag = PermissionChecker.checkPermissionForPreflight(
mContext,
INTERACT_ACROSS_PROFILES,
callingPid,
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5523511..24c095b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
@@ -65,6 +66,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.app.AppOpsManager.AttributionFlags;
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.compat.annotation.ChangeId;
@@ -109,6 +111,7 @@
import android.permission.IOnPermissionsChangeListener;
import android.permission.IPermissionChecker;
import android.permission.IPermissionManager;
+import android.permission.PermissionCheckerManager;
import android.permission.PermissionControllerManager;
import android.permission.PermissionManager;
import android.permission.PermissionManagerInternal;
@@ -172,6 +175,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
/**
@@ -3321,8 +3325,8 @@
}
@Override
- public @NonNull AttributionSource registerAttributionSource(@NonNull AttributionSource source) {
- return mAttributionSourceRegistry.registerAttributionSource(source);
+ public void registerAttributionSource(@NonNull AttributionSource source) {
+ mAttributionSourceRegistry.registerAttributionSource(source);
}
@Override
@@ -5288,8 +5292,8 @@
* @param permissionName the name of the permission to be checked
* @param userId the user ID
* @param superImpl the original implementation that can be delegated to
- * @return {@link android.content.pm.PackageManager.PERMISSION_GRANTED} if the package has
- * the permission, or {@link android.content.pm.PackageManager.PERMISSION_DENITED} otherwise
+ * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
+ * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise
*
* @see android.content.pm.PackageManager#checkPermission(String, String)
*/
@@ -5303,8 +5307,8 @@
* @param uid the UID to be checked
* @param permissionName the name of the permission to be checked
* @param superImpl the original implementation that can be delegated to
- * @return {@link android.content.pm.PackageManager.PERMISSION_GRANTED} if the package has
- * the permission, or {@link android.content.pm.PackageManager.PERMISSION_DENITED} otherwise
+ * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
+ * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise
*/
int checkUidPermission(int uid, @NonNull String permissionName,
BiFunction<Integer, String, Integer> superImpl);
@@ -5388,8 +5392,7 @@
private final WeakHashMap<IBinder, AttributionSource> mAttributions = new WeakHashMap<>();
- public @NonNull AttributionSource registerAttributionSource(
- @NonNull AttributionSource source) {
+ public void registerAttributionSource(@NonNull AttributionSource source) {
// Here we keep track of attribution sources that were created by an app
// from an attribution chain that called into the app and the apps's
// own attribution source. An app can register an attribution chain up
@@ -5436,10 +5439,7 @@
}
synchronized (mLock) {
- final IBinder token = new Binder();
- final AttributionSource result = source.withToken(token);
- mAttributions.put(token, result);
- return result;
+ mAttributions.put(source.getToken(), source);
}
}
@@ -5465,6 +5465,8 @@
private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions
= new ConcurrentHashMap<>();
+ private static final AtomicInteger sAttributionChainIds = new AtomicInteger(0);
+
private final @NonNull Context mContext;
private final @NonNull AppOpsManager mAppOpsManager;
@@ -5474,53 +5476,108 @@
}
@Override
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
public int checkPermission(@NonNull String permission,
@NonNull AttributionSourceState attributionSourceState, @Nullable String message,
- boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
+ boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource,
+ int attributedOp) {
Objects.requireNonNull(permission);
Objects.requireNonNull(attributionSourceState);
final AttributionSource attributionSource = new AttributionSource(
attributionSourceState);
final int result = checkPermission(mContext, permission, attributionSource, message,
- forDataDelivery, startDataDelivery, fromDatasource);
+ forDataDelivery, startDataDelivery, fromDatasource, attributedOp);
// Finish any started op if some step in the attribution chain failed.
if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
- finishDataDelivery(AppOpsManager.permissionToOp(permission),
- attributionSource.asState());
+ if (attributedOp == AppOpsManager.OP_NONE) {
+ finishDataDelivery(AppOpsManager.permissionToOpCode(permission),
+ attributionSource.asState(), fromDatasource);
+ } else {
+ finishDataDelivery(attributedOp, attributionSource.asState(), fromDatasource);
+ }
}
return result;
}
@Override
- public void finishDataDelivery(@NonNull String op,
- @NonNull AttributionSourceState attributionSourceState) {
- if (op == null || attributionSourceState.packageName == null) {
+ public void finishDataDelivery(int op,
+ @NonNull AttributionSourceState attributionSourceState, boolean fromDatasource) {
+ Objects.requireNonNull(attributionSourceState);
+
+ if (op == AppOpsManager.OP_NONE) {
return;
}
- mAppOpsManager.finishProxyOp(op, new AttributionSource(attributionSourceState));
- if (attributionSourceState.next != null) {
- finishDataDelivery(op, attributionSourceState.next[0]);
+
+ AttributionSource current = new AttributionSource(attributionSourceState);
+ AttributionSource next = null;
+
+ while (true) {
+ final boolean skipCurrentFinish = (fromDatasource || next != null);
+
+ next = current.getNext();
+
+ // If the call is from a datasource we need to vet only the chain before it. This
+ // way we can avoid the datasource creating an attribution context for every call.
+ if (!(fromDatasource && current.asState() == attributionSourceState)
+ && next != null && !current.isTrusted(mContext)) {
+ return;
+ }
+
+ // The access is for oneself if this is the single receiver of data
+ // after the data source or if this is the single attribution source
+ // in the chain if not from a datasource.
+ final boolean singleReceiverFromDatasource = (fromDatasource
+ && current.asState() == attributionSourceState && next != null
+ && next.getNext() == null);
+ final boolean selfAccess = singleReceiverFromDatasource || next == null;
+
+ final AttributionSource accessorSource = (!singleReceiverFromDatasource)
+ ? current : next;
+
+ if (selfAccess) {
+ final String resolvedPackageName = resolvePackageName(mContext, accessorSource);
+ if (resolvedPackageName == null) {
+ return;
+ }
+ mAppOpsManager.finishOp(accessorSource.getToken(), op,
+ accessorSource.getUid(), resolvedPackageName,
+ accessorSource.getAttributionTag());
+ } else {
+ final AttributionSource resolvedAttributionSource =
+ resolveAttributionSource(mContext, accessorSource);
+ if (resolvedAttributionSource.getPackageName() == null) {
+ return;
+ }
+ mAppOpsManager.finishProxyOp(AppOpsManager.opToPublicName(op),
+ resolvedAttributionSource, skipCurrentFinish);
+ }
+
+ if (next == null || next.getNext() == null) {
+ return;
+ }
+
+ current = next;
}
}
@Override
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
public int checkOp(int op, AttributionSourceState attributionSource,
String message, boolean forDataDelivery, boolean startDataDelivery) {
int result = checkOp(mContext, op, new AttributionSource(attributionSource), message,
forDataDelivery, startDataDelivery);
if (result != PermissionChecker.PERMISSION_GRANTED && startDataDelivery) {
// Finish any started op if some step in the attribution chain failed.
- finishDataDelivery(AppOpsManager.opToName(op), attributionSource);
+ finishDataDelivery(op, attributionSource, /*fromDatasource*/ false);
}
return result;
}
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
private static int checkPermission(@NonNull Context context, @NonNull String permission,
@NonNull AttributionSource attributionSource, @Nullable String message,
- boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
+ boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource,
+ int attributedOp) {
PermissionInfo permissionInfo = sPlatformPermissions.get(permission);
if (permissionInfo == null) {
@@ -5542,7 +5599,7 @@
}
if (permissionInfo.isRuntime()) {
return checkRuntimePermission(context, permission, attributionSource, message,
- forDataDelivery, startDataDelivery, fromDatasource);
+ forDataDelivery, startDataDelivery, fromDatasource, attributedOp);
}
if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(),
@@ -5551,15 +5608,14 @@
}
if (attributionSource.getNext() != null) {
- return checkPermission(context, permission,
- attributionSource.getNext(), message, forDataDelivery,
- startDataDelivery, /*fromDatasource*/ false);
+ return checkPermission(context, permission, attributionSource.getNext(), message,
+ forDataDelivery, startDataDelivery, /*fromDatasource*/ false, attributedOp);
}
return PermissionChecker.PERMISSION_GRANTED;
}
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
private static int checkAppOpPermission(@NonNull Context context,
@NonNull String permission, @NonNull AttributionSource attributionSource,
@Nullable String message, boolean forDataDelivery, boolean fromDatasource) {
@@ -5579,7 +5635,7 @@
// If the call is from a datasource we need to vet only the chain before it. This
// way we can avoid the datasource creating an attribution context for every call.
- if (!(fromDatasource && current == attributionSource)
+ if (!(fromDatasource && current.equals(attributionSource))
&& next != null && !current.isTrusted(context)) {
return PermissionChecker.PERMISSION_HARD_DENIED;
}
@@ -5588,12 +5644,15 @@
// after the data source or if this is the single attribution source
// in the chain if not from a datasource.
final boolean singleReceiverFromDatasource = (fromDatasource
- && current == attributionSource && next != null && next.getNext() == null);
+ && current.equals(attributionSource) && next != null
+ && next.getNext() == null);
final boolean selfAccess = singleReceiverFromDatasource || next == null;
final int opMode = performOpTransaction(context, op, current, message,
forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks,
- selfAccess, singleReceiverFromDatasource);
+ selfAccess, singleReceiverFromDatasource, AppOpsManager.OP_NONE,
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+ AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
switch (opMode) {
case AppOpsManager.MODE_IGNORED:
@@ -5624,9 +5683,12 @@
private static int checkRuntimePermission(@NonNull Context context,
@NonNull String permission, @NonNull AttributionSource attributionSource,
@Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
- boolean fromDatasource) {
+ boolean fromDatasource, int attributedOp) {
// Now let's check the identity chain...
final int op = AppOpsManager.permissionToOpCode(permission);
+ final int attributionChainId = (startDataDelivery)
+ ? sAttributionChainIds.incrementAndGet()
+ : AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
AttributionSource current = attributionSource;
AttributionSource next = null;
@@ -5637,7 +5699,7 @@
// If the call is from a datasource we need to vet only the chain before it. This
// way we can avoid the datasource creating an attribution context for every call.
- if (!(fromDatasource && current == attributionSource)
+ if (!(fromDatasource && current.equals(attributionSource))
&& next != null && !current.isTrusted(context)) {
return PermissionChecker.PERMISSION_HARD_DENIED;
}
@@ -5671,12 +5733,21 @@
// after the data source or if this is the single attribution source
// in the chain if not from a datasource.
final boolean singleReceiverFromDatasource = (fromDatasource
- && current == attributionSource && next != null && next.getNext() == null);
+ && current.equals(attributionSource)
+ && next != null && next.getNext() == null);
final boolean selfAccess = singleReceiverFromDatasource || next == null;
+ final int proxyAttributionFlags = (!skipCurrentChecks)
+ ? resolveProxyAttributionFlags(attributionSource, current, fromDatasource,
+ startDataDelivery, selfAccess)
+ : AppOpsManager.ATTRIBUTION_FLAGS_NONE;
+ final int proxiedAttributionFlags = resolveProxiedAttributionFlags(
+ attributionSource, next, fromDatasource, startDataDelivery, selfAccess);
+
final int opMode = performOpTransaction(context, op, current, message,
forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
- singleReceiverFromDatasource);
+ singleReceiverFromDatasource, attributedOp, proxyAttributionFlags,
+ proxiedAttributionFlags, attributionChainId);
switch (opMode) {
case AppOpsManager.MODE_ERRORED: {
@@ -5707,6 +5778,50 @@
return permissionGranted;
}
+ private static @AttributionFlags int resolveProxyAttributionFlags(
+ @NonNull AttributionSource attributionChain,
+ @NonNull AttributionSource current, boolean fromDatasource,
+ boolean startDataDelivery, boolean selfAccess) {
+ return resolveAttributionFlags(attributionChain, current, fromDatasource,
+ startDataDelivery, selfAccess, /*flagsForProxy*/ true);
+ }
+
+ private static @AttributionFlags int resolveProxiedAttributionFlags(
+ @NonNull AttributionSource attributionChain,
+ @NonNull AttributionSource current, boolean fromDatasource,
+ boolean startDataDelivery, boolean selfAccess) {
+ return resolveAttributionFlags(attributionChain, current, fromDatasource,
+ startDataDelivery, selfAccess, /*flagsForProxy*/ false);
+ }
+
+ private static @AttributionFlags int resolveAttributionFlags(
+ @NonNull AttributionSource attributionChain,
+ @NonNull AttributionSource current, boolean fromDatasource,
+ boolean startDataDelivery, boolean selfAccess, boolean flagsForProxy) {
+ if (current == null || !startDataDelivery) {
+ return AppOpsManager.ATTRIBUTION_FLAGS_NONE;
+ }
+ if (flagsForProxy) {
+ if (selfAccess) {
+ return AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
+ } else if (!fromDatasource && current.equals(attributionChain)) {
+ return AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
+ }
+ } else {
+ if (selfAccess) {
+ return AppOpsManager.ATTRIBUTION_FLAG_RECEIVER;
+ } else if (fromDatasource && current.equals(attributionChain.getNext())) {
+ return AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
+ } else if (current.getNext() == null) {
+ return AppOpsManager.ATTRIBUTION_FLAG_RECEIVER;
+ }
+ }
+ if (fromDatasource && current.equals(attributionChain)) {
+ return AppOpsManager.ATTRIBUTION_FLAGS_NONE;
+ }
+ return AppOpsManager.ATTRIBUTION_FLAG_INTERMEDIARY;
+ }
+
private static int checkOp(@NonNull Context context, @NonNull int op,
@NonNull AttributionSource attributionSource, @Nullable String message,
boolean forDataDelivery, boolean startDataDelivery) {
@@ -5714,6 +5829,10 @@
return PermissionChecker.PERMISSION_HARD_DENIED;
}
+ final int attributionChainId = (startDataDelivery)
+ ? sAttributionChainIds.incrementAndGet()
+ : AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
+
AttributionSource current = attributionSource;
AttributionSource next = null;
@@ -5730,9 +5849,18 @@
// The access is for oneself if this is the single attribution source in the chain.
final boolean selfAccess = (next == null);
+ final int proxyAttributionFlags = (!skipCurrentChecks)
+ ? resolveProxyAttributionFlags(attributionSource, current,
+ /*fromDatasource*/ false, startDataDelivery, selfAccess)
+ : AppOpsManager.ATTRIBUTION_FLAGS_NONE;
+ final int proxiedAttributionFlags = resolveProxiedAttributionFlags(
+ attributionSource, next, /*fromDatasource*/ false, startDataDelivery,
+ selfAccess);
+
final int opMode = performOpTransaction(context, op, current, message,
forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
- /*fromDatasource*/ false);
+ /*fromDatasource*/ false, AppOpsManager.OP_NONE, proxyAttributionFlags,
+ proxiedAttributionFlags, attributionChainId);
switch (opMode) {
case AppOpsManager.MODE_ERRORED: {
@@ -5751,10 +5879,13 @@
}
}
+ @SuppressWarnings("ConstantConditions")
private static int performOpTransaction(@NonNull Context context, int op,
@NonNull AttributionSource attributionSource, @Nullable String message,
boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation,
- boolean selfAccess, boolean singleReceiverFromDatasource) {
+ boolean selfAccess, boolean singleReceiverFromDatasource, int attributedOp,
+ @AttributionFlags int proxyAttributionFlags,
+ @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
// We cannot perform app ops transactions without a package name. In all relevant
// places we pass the package name but just in case there is a bug somewhere we
// do a best effort to resolve the package from the UID (pick first without a loss
@@ -5786,36 +5917,75 @@
if (resolvedAttributionSource.getPackageName() == null) {
return AppOpsManager.MODE_ERRORED;
}
+ // If the datasource is not in a trusted platform component then in would not
+ // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
+ // an app is exposing runtime permission protected data but cannot blame others
+ // in a trusted way which would not properly show in permission usage UIs.
+ // As a fallback we note a proxy op that blames the app and the datasource.
+ int startedOp = op;
+ int checkedOpResult = MODE_ALLOWED;
+ int startedOpResult;
+
+ // If the datasource wants to attribute to another app op we need to
+ // make sure the op for the permission and the attributed ops allow
+ // the operation. We return the less permissive of the two and check
+ // the permission op while start the attributed op.
+ if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
+ checkedOpResult = appOpsManager.checkOpNoThrow(op,
+ resolvedAttributionSource.getUid(), resolvedAttributionSource
+ .getPackageName());
+ if (checkedOpResult == MODE_ERRORED) {
+ return checkedOpResult;
+ }
+ startedOp = attributedOp;
+ }
if (selfAccess) {
- // If the datasource is not in a trusted platform component then in would not
- // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
- // an app is exposing runtime permission protected data but cannot blame others
- // in a trusted way which would not properly show in permission usage UIs.
- // As a fallback we note a proxy op that blames the app and the datasource.
try {
- return appOpsManager.startOpNoThrow(op, resolvedAttributionSource.getUid(),
+ startedOpResult = appOpsManager.startOpNoThrow(
+ resolvedAttributionSource.getToken(), startedOp,
+ resolvedAttributionSource.getUid(),
resolvedAttributionSource.getPackageName(),
/*startIfModeDefault*/ false,
resolvedAttributionSource.getAttributionTag(),
- message);
+ message, proxyAttributionFlags, attributionChainId);
} catch (SecurityException e) {
Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
+ " platform defined runtime permission "
+ AppOpsManager.opToPermission(op) + " while not having "
+ Manifest.permission.UPDATE_APP_OPS_STATS);
- return appOpsManager.startProxyOpNoThrow(op, attributionSource, message,
- skipProxyOperation);
+ startedOpResult = appOpsManager.startProxyOpNoThrow(attributedOp,
+ attributionSource, message, skipProxyOperation,
+ proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
} else {
- return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message,
- skipProxyOperation);
+ startedOpResult = appOpsManager.startProxyOpNoThrow(startedOp,
+ resolvedAttributionSource, message, skipProxyOperation,
+ proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
+ return Math.max(checkedOpResult, startedOpResult);
} else {
final AttributionSource resolvedAttributionSource = resolveAttributionSource(
context, accessorSource);
if (resolvedAttributionSource.getPackageName() == null) {
return AppOpsManager.MODE_ERRORED;
}
+ int notedOp = op;
+ int checkedOpResult = MODE_ALLOWED;
+ int notedOpResult;
+
+ // If the datasource wants to attribute to another app op we need to
+ // make sure the op for the permission and the attributed ops allow
+ // the operation. We return the less permissive of the two and check
+ // the permission op while start the attributed op.
+ if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
+ checkedOpResult = appOpsManager.checkOpNoThrow(op,
+ resolvedAttributionSource.getUid(), resolvedAttributionSource
+ .getPackageName());
+ if (checkedOpResult == MODE_ERRORED) {
+ return checkedOpResult;
+ }
+ notedOp = attributedOp;
+ }
if (selfAccess) {
// If the datasource is not in a trusted platform component then in would not
// have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
@@ -5823,7 +5993,8 @@
// in a trusted way which would not properly show in permission usage UIs.
// As a fallback we note a proxy op that blames the app and the datasource.
try {
- return appOpsManager.noteOpNoThrow(op, resolvedAttributionSource.getUid(),
+ notedOpResult = appOpsManager.noteOpNoThrow(notedOp,
+ resolvedAttributionSource.getUid(),
resolvedAttributionSource.getPackageName(),
resolvedAttributionSource.getAttributionTag(),
message);
@@ -5832,13 +6003,14 @@
+ " platform defined runtime permission "
+ AppOpsManager.opToPermission(op) + " while not having "
+ Manifest.permission.UPDATE_APP_OPS_STATS);
- return appOpsManager.noteProxyOpNoThrow(op, attributionSource, message,
- skipProxyOperation);
+ notedOpResult = appOpsManager.noteProxyOpNoThrow(notedOp, attributionSource,
+ message, skipProxyOperation);
}
} else {
- return appOpsManager.noteProxyOpNoThrow(op, resolvedAttributionSource, message,
- skipProxyOperation);
+ notedOpResult = appOpsManager.noteProxyOpNoThrow(notedOp,
+ resolvedAttributionSource, message, skipProxyOperation);
}
+ return Math.max(checkedOpResult, notedOpResult);
}
}
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 2cfbf26..607bc56 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.app.AppOpsManager.AttributionFlags;
import android.app.AppOpsManagerInternal;
import android.app.SyncNotedAppOp;
import android.app.role.RoleManager;
@@ -39,6 +40,7 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.HeptFunction;
import com.android.internal.util.function.HexFunction;
import com.android.internal.util.function.NonaFunction;
@@ -46,6 +48,7 @@
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.QuintFunction;
import com.android.internal.util.function.TriFunction;
+import com.android.internal.util.function.UndecFunction;
import com.android.server.LocalServices;
import java.util.List;
@@ -179,32 +182,37 @@
public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
@Nullable String packageName, @Nullable String attributionTag,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
- boolean shouldCollectMessage, @NonNull NonaFunction<IBinder, Integer, Integer, String,
- String, Boolean, Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
+ boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+ int attributionChainId, @NonNull UndecFunction<IBinder, Integer, Integer, String,
+ String, Boolean, Boolean, String, Boolean, Integer, Integer,
+ SyncNotedAppOp> superImpl) {
return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag),
uid, packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp,
- message, shouldCollectMessage);
+ message, shouldCollectMessage, attributionFlags, attributionChainId);
}
@Override
- public SyncNotedAppOp startProxyOperation(IBinder token, int code,
+ public SyncNotedAppOp startProxyOperation(int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
- boolean skipProxyOperation, @NonNull OctFunction<IBinder, Integer, AttributionSource,
- Boolean, Boolean, String, Boolean, Boolean, SyncNotedAppOp> superImpl) {
- return superImpl.apply(token, resolveDatasourceOp(code, attributionSource.getUid(),
+ boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+ @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
+ @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean,
+ Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
+ return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
attributionSource.getPackageName(), attributionSource.getAttributionTag()),
attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, skipProxyOperation);
+ shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+ proxiedAttributionFlags, attributionChainId);
}
@Override
- public void finishProxyOperation(IBinder clientId, int code,
- @NonNull AttributionSource attributionSource,
- @NonNull TriFunction<IBinder, Integer, AttributionSource, Void> superImpl) {
- superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
+ public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
+ boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource,
+ Boolean, Void> superImpl) {
+ superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
attributionSource.getPackageName(), attributionSource.getAttributionTag()),
- attributionSource);
+ attributionSource, skipProxyOperation);
}
private int resolveDatasourceOp(int code, int uid, @NonNull String packageName,
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
index 98bc067..a2aaccc 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
@@ -20,6 +20,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
@@ -66,7 +68,8 @@
// Verify that we got called for the op being active
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
- eq(Process.myUid()), eq(getContext().getPackageName()), eq(true));
+ eq(Process.myUid()), eq(getContext().getPackageName()),
+ isNull(), eq(true), anyInt(), anyInt());
// This should be the only callback we got
verifyNoMoreInteractions(listener);
@@ -84,7 +87,8 @@
// Verify that we got called for the op being active
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
- eq(Process.myUid()), eq(getContext().getPackageName()), eq(false));
+ eq(Process.myUid()), eq(getContext().getPackageName()), isNull(),
+ eq(false), anyInt(), anyInt());
// Verify that the op is not active
assertThat(appOpsManager.isOperationActive(AppOpsManager.OP_CAMERA,
@@ -121,7 +125,8 @@
// We should get the callback again (and since we reset the listener, we therefore expect 1)
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
- eq(Process.myUid()), eq(getContext().getPackageName()), eq(true));
+ eq(Process.myUid()), eq(getContext().getPackageName()), isNull(),
+ eq(true), anyInt(), anyInt());
// Finish up
appOpsManager.finishOp(AppOpsManager.OP_CAMERA);
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index 2162c0b..e811c1f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -5,22 +5,29 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+
import static org.testng.Assert.assertThrows;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.IApplicationThread;
import android.app.admin.DevicePolicyManagerInternal;
+import android.content.AttributionSourceState;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.PermissionChecker;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.CrossProfileAppsInternal;
@@ -34,6 +41,7 @@
import android.os.IBinder;
import android.os.UserHandle;
import android.os.UserManager;
+import android.permission.PermissionCheckerManager;
import android.permission.PermissionManager;
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
@@ -411,6 +419,18 @@
}
mActivityInfo.exported = false;
+
+ // There's a bug in static mocking if the APK is large - so here is the next best thing...
+ doReturn(Context.PERMISSION_CHECKER_SERVICE).when(mContext)
+ .getSystemServiceName(PermissionCheckerManager.class);
+ PermissionCheckerManager permissionCheckerManager = mock(PermissionCheckerManager.class);
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(permissionCheckerManager)
+ .checkPermission(eq(Manifest.permission.INTERACT_ACROSS_PROFILES), any(
+ AttributionSourceState.class), anyString(), anyBoolean(), anyBoolean(),
+ anyBoolean(), anyInt());
+ doReturn(permissionCheckerManager).when(mContext).getSystemService(
+ Context.PERMISSION_CHECKER_SERVICE);
+
assertThrows(
SecurityException.class,
() ->