Runtime permission attribution improvements
When an app is proxying access to runtime permission protected
data it needs to check whether the calling app has a permission
to the data it is about to proxy which leaves a trace in app ops
that the requesting app perofmed a data access. However, then the
app doing the work needs to get the protected data itself from the
OS which access gets attributed only to itself. As a result there
are two data accesses in app ops where only the first one is a
proxy one that app A got access to Foo through app B - that is the
one we want to show in the permission tracking UIs - and one
for the data access - that is the one we would want to blame on
the calling app, and in fact, these two accesses should be one -
that app A accessed Foo though B. This limitation requires fragile
one off workarounds where both accesses use the same attribution
tag and sys UI has hardcoded rules to dedupe. Since this is not
documented we cannot expect that the ecosystem would reliably
do this workaround in apps that that the workaround in the OS
would be respected by every OEM.
This change adds a mechaism to resolve this issue. It allows for
an app to create an attribution context for another app and then
any private data access thorugh this context would result in a
single app op blame that A accessed Foo though B, i.e. we no longer
have double accounting. Also this can be nested through apps, e.g.
app A asks app B which asks app C for contacts. In this case app
B creates an attribution context for app A and calls into app C
which creates an attribution context for app B. When app C gets
contacts the entire attribution chain would get a porper, single
blame: that C accessed the data, that B got the data from C, and
that A got the data form B. Furthermore, this mechanism ensures
that apps cannot forget to check permissions for the caller
before proxying private data. In our example B and C don't need
to check the permisisons for A and B, respectively, since the
permisisons for the entire attribution chain are checked before
data delivery. Attribution chains are not forgeable preventing
a bad actor to create an arbitrary one - each attribution is
created by the app it refers to and points to a chain of
attributions created by their corresponding apps.
This change also fixes a bug where all content provider accesses
were double counted in app ops due to double noting. While at
this it also fixes that apps can now access their own last ops.
There was a bug where one could not pass null getting the attributed
ops from a historical package ops while this is a valid use case
since if there is no attribution everything is mapped to the null
tag. There were some app op APIs not being piped thorough the app
ops delegate and by extension through the app ops policy. Also
now that we have nice way to express the permission chain in a
call we no longer need the special casing in activity manager to
handle content provider accesses through the OS. Fixed a bug
where we don't properly handle the android.os.shell calls with
an invlaid tag which was failing while the shell can do any tag.
Finally, to ensure the mechanims is validated and works end-to-end
we are adding support for a voice recognizer to blame the client
app for the mic access. The recognition service can create a blaming
context when opening the mic and if the mic is open, which would
do all permission checks, we would not do so again. Since changes
to PermissionChercker for handling attribution sources were made
the CL also hooks up renounced permissoins in the request permission
flow and in the permission checks.
bug:158792096
bug:180647319
Test:atest CtsPermissionsTestCases
atest CtsPermissions2TestCases
atest CtsPermissions3TestCases
atest CtsPermissions4TestCases
atest CtsPermissions5TestCases
atest CtsAppOpsTestCases
atest CtsAppOps2TestCases
Change-Id: Ib04585515d3dc3956966005ae9d94955b2f3ee08
diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java
index ca1d598..f0ac530 100644
--- a/cmds/content/src/com/android/commands/content/Content.java
+++ b/cmds/content/src/com/android/commands/content/Content.java
@@ -19,6 +19,7 @@
import android.app.ActivityManager;
import android.app.ContentProviderHolder;
import android.app.IActivityManager;
+import android.content.AttributionSource;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.IContentProvider;
@@ -562,7 +563,8 @@
@Override
public void onExecute(IContentProvider provider) throws Exception {
- provider.insert(resolveCallingPackage(), null, mUri, mContentValues, mExtras);
+ provider.insert(new AttributionSource(Binder.getCallingUid(),
+ resolveCallingPackage(), null), mUri, mContentValues, mExtras);
}
}
@@ -576,7 +578,8 @@
@Override
public void onExecute(IContentProvider provider) throws Exception {
- provider.delete(resolveCallingPackage(), null, mUri, mExtras);
+ provider.delete(new AttributionSource(Binder.getCallingUid(),
+ resolveCallingPackage(), null), mUri, mExtras);
}
}
@@ -593,7 +596,8 @@
@Override
public void onExecute(IContentProvider provider) throws Exception {
- Bundle result = provider.call(null, null, mUri.getAuthority(), mMethod, mArg, mExtras);
+ Bundle result = provider.call(new AttributionSource(Binder.getCallingUid(),
+ resolveCallingPackage(), null), mUri.getAuthority(), mMethod, mArg, mExtras);
if (result != null) {
result.size(); // unpack
}
@@ -620,7 +624,9 @@
@Override
public void onExecute(IContentProvider provider) throws Exception {
- try (ParcelFileDescriptor fd = provider.openFile(null, null, mUri, "r", null, null)) {
+ try (ParcelFileDescriptor fd = provider.openFile(
+ new AttributionSource(Binder.getCallingUid(),
+ resolveCallingPackage(), null), mUri, "r", null)) {
FileUtils.copy(fd.getFileDescriptor(), FileDescriptor.out);
}
}
@@ -633,7 +639,8 @@
@Override
public void onExecute(IContentProvider provider) throws Exception {
- try (ParcelFileDescriptor fd = provider.openFile(null, null, mUri, "w", null, null)) {
+ try (ParcelFileDescriptor fd = provider.openFile(new AttributionSource(
+ Binder.getCallingUid(), resolveCallingPackage(), null), mUri, "w", null)) {
FileUtils.copy(FileDescriptor.in, fd.getFileDescriptor());
}
}
@@ -651,8 +658,8 @@
@Override
public void onExecute(IContentProvider provider) throws Exception {
- Cursor cursor = provider.query(resolveCallingPackage(), null, mUri, mProjection,
- mExtras, null);
+ Cursor cursor = provider.query(new AttributionSource(Binder.getCallingUid(),
+ resolveCallingPackage(), null), mUri, mProjection, mExtras, null);
if (cursor == null) {
System.out.println("No result found.");
return;
@@ -716,7 +723,8 @@
@Override
public void onExecute(IContentProvider provider) throws Exception {
- provider.update(resolveCallingPackage(), null, mUri, mValues, mExtras);
+ provider.update(new AttributionSource(Binder.getCallingUid(),
+ resolveCallingPackage(), null), mUri, mValues, mExtras);
}
}
diff --git a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
index b23bf5d..bc95986 100644
--- a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
+++ b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
@@ -20,6 +20,7 @@
import android.app.ContentProviderHolder;
import android.app.IActivityManager;
import android.app.UiAutomation;
+import android.content.AttributionSource;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentProvider;
@@ -28,6 +29,7 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.IPowerManager;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -67,7 +69,8 @@
throw new IllegalStateException("Could not find provider: " + providerName);
}
provider = holder.provider;
- cursor = provider.query(null, null, Settings.Secure.CONTENT_URI,
+ cursor = provider.query(new AttributionSource(Binder.getCallingUid(),
+ resolveCallingPackage(), null), Settings.Secure.CONTENT_URI,
new String[] {
Settings.Secure.VALUE
},
@@ -123,4 +126,18 @@
}
return ret;
}
+
+ private static String resolveCallingPackage() {
+ switch (Binder.getCallingUid()) {
+ case Process.ROOT_UID: {
+ return "root";
+ }
+ case Process.SHELL_UID: {
+ return "com.android.shell";
+ }
+ default: {
+ return null;
+ }
+ }
+ }
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 9b8ea6b..eb5f648 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9907,6 +9907,27 @@
method @Deprecated public void setUpdateThrottle(long);
}
+ public final class AttributionSource implements android.os.Parcelable {
+ method public boolean checkCallingUid();
+ method public int describeContents();
+ method public void enforceCallingUid();
+ method @Nullable public String getAttributionTag();
+ method @Nullable public android.content.AttributionSource getNext();
+ method @Nullable public String getPackageName();
+ method public int getUid();
+ method public boolean isTrusted(@NonNull android.content.Context);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.AttributionSource> CREATOR;
+ }
+
+ public static final class AttributionSource.Builder {
+ ctor public AttributionSource.Builder(int);
+ method @NonNull public android.content.AttributionSource build();
+ method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@NonNull String);
+ method @NonNull public android.content.AttributionSource.Builder setNext(@NonNull android.content.AttributionSource);
+ method @NonNull public android.content.AttributionSource.Builder setPackageName(@NonNull String);
+ }
+
public abstract class BroadcastReceiver {
ctor public BroadcastReceiver();
method public final void abortBroadcast();
@@ -10076,6 +10097,7 @@
method public abstract int delete(@NonNull android.net.Uri, @Nullable String, @Nullable String[]);
method public int delete(@NonNull android.net.Uri, @Nullable android.os.Bundle);
method public void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
+ method @Nullable public final android.content.AttributionSource getCallingAttributionSource();
method @Nullable public final String getCallingAttributionTag();
method @Nullable public final String getCallingPackage();
method @Nullable public final String getCallingPackageUnchecked();
@@ -10438,6 +10460,7 @@
method public abstract android.content.Context getApplicationContext();
method public abstract android.content.pm.ApplicationInfo getApplicationInfo();
method public abstract android.content.res.AssetManager getAssets();
+ method @NonNull public android.content.AttributionSource getAttributionSource();
method @Nullable public String getAttributionTag();
method public abstract java.io.File getCacheDir();
method public abstract ClassLoader getClassLoader();
@@ -10643,8 +10666,7 @@
public final class ContextParams {
method @Nullable public String getAttributionTag();
- method @Nullable public String getReceiverAttributionTag();
- method @Nullable public String getReceiverPackage();
+ method @Nullable public android.content.AttributionSource getNextAttributionSource();
}
public static final class ContextParams.Builder {
@@ -10652,7 +10674,7 @@
ctor public ContextParams.Builder(@NonNull android.content.ContextParams);
method @NonNull public android.content.ContextParams build();
method @NonNull public android.content.ContextParams.Builder setAttributionTag(@Nullable String);
- method @NonNull public android.content.ContextParams.Builder setReceiverPackage(@Nullable String, @Nullable String);
+ method @NonNull public android.content.ContextParams.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
}
public class ContextWrapper extends android.content.Context {
@@ -39082,6 +39104,7 @@
method public void bufferReceived(byte[]) throws android.os.RemoteException;
method public void endOfSpeech() throws android.os.RemoteException;
method public void error(int) throws android.os.RemoteException;
+ method @NonNull public android.content.AttributionSource getCallingAttributionSource();
method public int getCallingUid();
method public void partialResults(android.os.Bundle) throws android.os.RemoteException;
method public void readyForSpeech(android.os.Bundle) throws android.os.RemoteException;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 92c21f3..3f26e14 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -596,7 +596,7 @@
public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
method public int describeContents();
- method @Nullable public android.app.AppOpsManager.AttributedHistoricalOps getAttributedOps(@NonNull String);
+ method @Nullable public android.app.AppOpsManager.AttributedHistoricalOps getAttributedOps(@Nullable String);
method @NonNull public android.app.AppOpsManager.AttributedHistoricalOps getAttributedOpsAt(@IntRange(from=0) int);
method @IntRange(from=0) public int getAttributedOpsCount();
method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
@@ -2217,6 +2217,14 @@
method @NonNull public java.io.File getDeviceProtectedDataDirForUser(@NonNull android.os.UserHandle);
}
+ public final class AttributionSource implements android.os.Parcelable {
+ method @NonNull @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public java.util.Set<java.lang.String> getRenouncedPermissions();
+ }
+
+ public static final class AttributionSource.Builder {
+ method @NonNull @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public android.content.AttributionSource.Builder setRenouncedPermissions(@NonNull java.util.Set<java.lang.String>);
+ }
+
public abstract class BroadcastReceiver {
method @NonNull public final android.os.UserHandle getSendingUser();
}
@@ -2291,11 +2299,11 @@
}
public final class ContextParams {
- method @Nullable @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public java.util.Set<java.lang.String> getRenouncedPermissions();
+ method @NonNull @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public java.util.Set<java.lang.String> getRenouncedPermissions();
}
public static final class ContextParams.Builder {
- method @NonNull @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public android.content.ContextParams.Builder setRenouncedPermissions(@Nullable java.util.Set<java.lang.String>);
+ method @NonNull @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public android.content.ContextParams.Builder setRenouncedPermissions(@NonNull java.util.Set<java.lang.String>);
}
public class ContextWrapper extends android.content.Context {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 21771df..7cfe782 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -220,6 +220,7 @@
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void rebootHistory(long);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void reloadNonHistoricalState();
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void resetHistoryParameters();
+ method @RequiresPermission("android.permission.MANAGE_APPOPS") public void resetPackageOpsNoHistory(@NonNull String);
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);
@@ -654,6 +655,11 @@
package android.content {
+ 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);
+ }
+
public final class AutofillOptions implements android.os.Parcelable {
ctor public AutofillOptions(int, boolean);
method public int describeContents();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 47843cc..0f38b5f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5246,13 +5246,40 @@
if (requestCode < 0) {
throw new IllegalArgumentException("requestCode should be >= 0");
}
+
if (mHasCurrentPermissionsRequest) {
Log.w(TAG, "Can request only one set of permissions at a time");
// Dispatch the callback with empty arrays which means a cancellation.
onRequestPermissionsResult(requestCode, new String[0], new int[0]);
return;
}
- Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
+
+ List<String> filteredPermissions = null;
+
+ if (!getAttributionSource().getRenouncedPermissions().isEmpty()) {
+ final int permissionCount = permissions.length;
+ for (int i = 0; i < permissionCount; i++) {
+ if (getAttributionSource().getRenouncedPermissions().contains(permissions[i])) {
+ if (filteredPermissions == null) {
+ filteredPermissions = new ArrayList<>(i);
+ for (int j = 0; j < i; j++) {
+ filteredPermissions.add(permissions[i]);
+ }
+ }
+ } else if (filteredPermissions != null) {
+ filteredPermissions.add(permissions[i]);
+ }
+ }
+ }
+
+ final Intent intent;
+ if (filteredPermissions == null) {
+ intent = getPackageManager().buildRequestPermissionsIntent(permissions);
+ } else {
+ intent = getPackageManager().buildRequestPermissionsIntent(
+ filteredPermissions.toArray(new String[0]));
+ }
+
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
mHasCurrentPermissionsRequest = true;
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7e4af1a..f76e1c0 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -50,6 +50,7 @@
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
+import android.content.AttributionSource;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
@@ -5955,7 +5956,7 @@
*
* @return The historical ops for the attribution.
*/
- public @Nullable AttributedHistoricalOps getAttributedOps(@NonNull String attributionTag) {
+ public @Nullable AttributedHistoricalOps getAttributedOps(@Nullable String attributionTag) {
if (mAttributedHistoricalOps == null) {
return null;
}
@@ -6480,7 +6481,7 @@
* Gets number of discrete historical app ops.
*
* @return The number historical app ops.
- * @see #getOpAt(int)
+ * @see #getDiscreteAccessAt(int)
*/
public @IntRange(from = 0) int getDiscreteAccessCount() {
if (mDiscreteAccesses == null) {
@@ -6494,7 +6495,7 @@
*
* @param index The index to lookup.
* @return The op at the given index.
- * @see #getOpCount()
+ * @see #getDiscreteAccessCount()
*/
public @NonNull AttributedOpEntry getDiscreteAccessAt(@IntRange(from = 0) int index) {
if (mDiscreteAccesses == null) {
@@ -7979,7 +7980,7 @@
try {
collectNoteOpCallsForValidation(op);
int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
- boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID ? true : false;
+ boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID;
if (collectionMode == COLLECT_ASYNC) {
if (message == null) {
// Set stack trace as default message
@@ -8033,14 +8034,9 @@
*/
public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
@Nullable String proxiedAttributionTag, @Nullable String message) {
- int mode = noteProxyOpNoThrow(op, proxiedPackageName, proxiedUid, proxiedAttributionTag,
- message);
- if (mode == MODE_ERRORED) {
- throw new SecurityException("Proxy package " + mContext.getOpPackageName()
- + " from uid " + Process.myUid() + " or calling package " + proxiedPackageName
- + " from uid " + proxiedUid + " not allowed to perform " + sOpNames[op]);
- }
- return mode;
+ return noteProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
+ new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)),
+ message, /*skipProxyOperation*/ false);
}
/**
@@ -8069,6 +8065,36 @@
}
/**
+ * Make note of an application performing an operation on behalf of another application(s).
+ *
+ * @param op The operation to note. One of the OPSTR_* constants.
+ * @param attributionSource The permission identity for which to note.
+ * @param message A message describing the reason the op was noted
+ * @param skipProxyOperation Whether to skip the proxy note.
+ *
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
+ * if it is not allowed and should be silently ignored (without causing the app to crash).
+ *
+ * @throws SecurityException If the any proxying operations in the permission identityf
+ * chain fails.
+ *
+ * @hide
+ */
+ public int noteProxyOp(@NonNull int op, @NonNull AttributionSource attributionSource,
+ @Nullable String message, boolean skipProxyOperation) {
+ final int mode = noteProxyOpNoThrow(op, attributionSource, message, skipProxyOperation);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException("Proxy package "
+ + attributionSource.getPackageName() + " from uid "
+ + attributionSource.getUid() + " or calling package "
+ + attributionSource.getNextPackageName() + " from uid "
+ + attributionSource.getNextUid() + " not allowed to perform "
+ + sOpNames[op]);
+ }
+ return mode;
+ }
+
+ /**
* @deprecated Use {@link #noteProxyOpNoThrow(String, String, int, String, String)} instead
*/
@Deprecated
@@ -8093,24 +8119,36 @@
*/
public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) {
- return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName, proxiedUid,
- proxiedAttributionTag, message);
+ return noteProxyOpNoThrow(strOpToOp(op), new AttributionSource(
+ mContext.getAttributionSource(), new AttributionSource(proxiedUid,
+ proxiedPackageName, proxiedAttributionTag)), message,
+ /*skipProxyOperation*/ false);
}
/**
- * @see #noteProxyOpNoThrow(String, String, int, String, String)
+ * Make note of an application performing an operation on behalf of another application(s).
+ *
+ * @param op The operation to note. One of the OPSTR_* constants.
+ * @param attributionSource The permission identity for which to note.
+ * @param message A message describing the reason the op was noted
+ * @param skipProxyOperation Whether to note op for the proxy
+ *
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
+ * if it is not allowed and should be silently ignored (without causing the app to crash).
*
* @hide
*/
@SuppressWarnings("AndroidFrameworkClientSidePermissionCheck")
- public int noteProxyOpNoThrow(int op, @Nullable String proxiedPackageName, int proxiedUid,
- @Nullable String proxiedAttributionTag, @Nullable String message) {
+ public int noteProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
+ @Nullable String message, boolean skipProxyOperation) {
int myUid = Process.myUid();
try {
collectNoteOpCallsForValidation(op);
- int collectionMode = getNotedOpCollectionMode(proxiedUid, proxiedPackageName, op);
- boolean shouldCollectMessage = myUid == Process.SYSTEM_UID ? true : false;
+ int collectionMode = getNotedOpCollectionMode(
+ attributionSource.getNextUid(),
+ attributionSource.getNextAttributionTag(), op);
+ boolean shouldCollectMessage = (myUid == Process.SYSTEM_UID);
if (collectionMode == COLLECT_ASYNC) {
if (message == null) {
// Set stack trace as default message
@@ -8119,20 +8157,19 @@
}
}
- int mode = mService.noteProxyOperation(op, proxiedUid, proxiedPackageName,
- proxiedAttributionTag, myUid, mContext.getOpPackageName(),
- mContext.getAttributionTag(), collectionMode == COLLECT_ASYNC, message,
- shouldCollectMessage);
+ int mode = mService.noteProxyOperation(op, attributionSource,
+ collectionMode == COLLECT_ASYNC, message,
+ shouldCollectMessage, skipProxyOperation);
if (mode == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
- collectNotedOpForSelf(op, proxiedAttributionTag);
+ collectNotedOpForSelf(op, attributionSource.getNextAttributionTag());
} else if (collectionMode == COLLECT_SYNC
// Only collect app-ops when the proxy is trusted
&& (mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1,
myUid) == PackageManager.PERMISSION_GRANTED ||
- Binder.getCallingUid() == proxiedUid)) {
- collectNotedOpSync(op, proxiedAttributionTag);
+ Binder.getCallingUid() == attributionSource.getNextUid())) {
+ collectNotedOpSync(op, attributionSource.getNextAttributionTag());
}
}
@@ -8424,7 +8461,7 @@
try {
collectNoteOpCallsForValidation(op);
int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
- boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID ? true : false;
+ boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID;
if (collectionMode == COLLECT_ASYNC) {
if (message == null) {
// Set stack trace as default message
@@ -8450,6 +8487,7 @@
throw e.rethrowFromSystemServer();
}
}
+
/**
* Report that an application has started executing a long-running operation on behalf of
* another application when handling an IPC. This function will verify that the calling uid and
@@ -8470,19 +8508,45 @@
*/
public int startProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName,
@Nullable String proxiedAttributionTag, @Nullable String message) {
- final int mode = startProxyOpNoThrow(op, proxiedUid, proxiedPackageName,
- proxiedAttributionTag, message);
+ return startProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
+ new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)),
+ message, /*skipProxyOperation*/ false);
+ }
+
+ /**
+ * Report that an application has started executing a long-running operation on behalf of
+ * another application for the attribution chain specified by the {@link AttributionSource}}.
+ *
+ * @param op The op to note
+ * @param attributionSource The permission identity for which to check
+ * @param message A message describing the reason the op was noted
+ * @param skipProxyOperation Whether to skip the proxy start.
+ *
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
+ * if it is not allowed and should be silently ignored (without causing the app to crash).
+ *
+ * @throws SecurityException If the any proxying operations in the permission identity
+ * chain fails.
+ *
+ * @hide
+ */
+ public int startProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource,
+ @Nullable String message, boolean skipProxyOperation) {
+ final int mode = startProxyOpNoThrow(AppOpsManager.strOpToOp(op), attributionSource,
+ message, skipProxyOperation);
if (mode == MODE_ERRORED) {
- throw new SecurityException("Proxy package " + mContext.getOpPackageName()
- + " from uid " + Process.myUid() + " or calling package " + proxiedPackageName
- + " from uid " + proxiedUid + " not allowed to perform "
- + sOpNames[strOpToOp(op)]);
+ throw new SecurityException("Proxy package "
+ + attributionSource.getPackageName() + " from uid "
+ + attributionSource.getUid() + " or calling package "
+ + attributionSource.getNextPackageName() + " from uid "
+ + attributionSource.getNextUid() + " not allowed to perform "
+ + op);
}
return mode;
}
/**
- *Like {@link #startProxyOp(String, int, String, String, String)} but instead
+ * Like {@link #startProxyOp(String, int, String, String, String)} but instead
* of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
*
* @see #startProxyOp(String, int, String, String, String)
@@ -8490,11 +8554,28 @@
public int startProxyOpNoThrow(@NonNull String op, int proxiedUid,
@NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag,
@Nullable String message) {
- try {
- int opInt = strOpToOp(op);
+ return startProxyOpNoThrow(AppOpsManager.strOpToOp(op), new AttributionSource(
+ mContext.getAttributionSource(), new AttributionSource(proxiedUid,
+ proxiedPackageName, proxiedAttributionTag)), message,
+ /*skipProxyOperation*/ false);
+ }
- collectNoteOpCallsForValidation(opInt);
- int collectionMode = getNotedOpCollectionMode(proxiedUid, proxiedPackageName, opInt);
+ /**
+ * 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) {
+ try {
+ collectNoteOpCallsForValidation(op);
+ int collectionMode = getNotedOpCollectionMode(
+ attributionSource.getNextUid(),
+ attributionSource.getNextPackageName(), op);
boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID;
if (collectionMode == COLLECT_ASYNC) {
if (message == null) {
@@ -8504,24 +8585,23 @@
}
}
- int mode = mService.startProxyOperation(getClientId(), opInt, proxiedUid,
- proxiedPackageName, proxiedAttributionTag, Process.myUid(),
- mContext.getOpPackageName(), mContext.getAttributionTag(), false,
- collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
+ int mode = mService.startProxyOperation(getClientId(), op,
+ attributionSource, false, collectionMode == COLLECT_ASYNC, message,
+ shouldCollectMessage, skipProxyOperation);
if (mode == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
- collectNotedOpForSelf(opInt, proxiedAttributionTag);
+ collectNotedOpForSelf(op,
+ attributionSource.getNextAttributionTag());
} else if (collectionMode == COLLECT_SYNC
// Only collect app-ops when the proxy is trusted
&& (mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1,
Process.myUid()) == PackageManager.PERMISSION_GRANTED
- || Binder.getCallingUid() == proxiedUid)) {
- collectNotedOpSync(opInt, proxiedAttributionTag);
+ || Binder.getCallingUid() == attributionSource.getNextUid())) {
+ collectNotedOpSync(op, attributionSource.getNextAttributionTag());
}
}
-
return mode;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -8580,22 +8660,37 @@
}
/**
- * Report that an application is no longer performing an operation that had previously
+ * Report that an application is no longer performing an operation that had previously
* been started with {@link #startProxyOp(String, int, String, String, 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.
+ *
* @param op The operation which was started
- * @param proxiedUid The uid the op was started on behalf of
- * @param proxiedPackageName The package the op was started on behalf of
- * @param proxiedAttributionTag The proxied {@link Context#createAttributionContext
- * attribution tag} or {@code null} for default attribution
+ * @param proxiedUid The proxied appp's UID
+ * @param proxiedPackageName The proxied appp's package name
+ * @param proxiedAttributionTag The proxied appp's attribution tag or
+ * {@code null} for default attribution
*/
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)));
+ }
+
+ /**
+ * 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.
+ *
+ * @param op The operation which was started
+ * @param attributionSource The permission identity for which to finish
+ *
+ * @hide
+ */
+ public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource) {
try {
- mService.finishProxyOperation(getClientId(), strOpToOp(op), proxiedUid,
- proxiedPackageName, proxiedAttributionTag, Process.myUid(),
- mContext.getOpPackageName(), mContext.getAttributionTag());
+ mService.finishProxyOperation(getClientId(), strOpToOp(op), attributionSource);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8616,6 +8711,46 @@
}
/**
+ * Get whether you are currently proxying to another package. That applies only
+ * for long running operations like {@link #OP_RECORD_AUDIO}.
+ *
+ * @param op The op.
+ * @param proxyAttributionTag Your attribution tag to query for.
+ * @param proxiedUid The proxied UID to query for.
+ * @param proxiedPackageName The proxied package to query for.
+ * @return Whether you are currently proxying to this target.
+ *
+ * @hide
+ */
+ public boolean isProxying(int op, @NonNull String proxyAttributionTag, int proxiedUid,
+ @NonNull String proxiedPackageName) {
+ try {
+ return mService.isProxying(op, mContext.getOpPackageName(),
+ mContext.getAttributionTag(), proxiedUid, proxiedPackageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears the op state (last accesses + op modes) for a package but not
+ * the historical state.
+ *
+ * @param packageName The package to reset.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+ public void resetPackageOpsNoHistory(@NonNull String packageName) {
+ try {
+ mService.resetPackageOpsNoHistory(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Start collection of noted appops on this thread.
*
* <p>Called at the beginning of a two way binder transaction.
@@ -8771,7 +8906,7 @@
packageName = "android";
}
- // check it the appops needs to be collected and cache result
+ // check if the appops needs to be collected and cache result
if (sAppOpsToNote[op] == SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED) {
boolean shouldCollectNotes;
try {
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index 5e032f0..a3d0cf2 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -18,12 +18,17 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.AttributionSource;
+import android.os.IBinder;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.util.function.HeptFunction;
+import com.android.internal.util.function.HexFunction;
+import com.android.internal.util.function.OctFunction;
import com.android.internal.util.function.QuadFunction;
+import com.android.internal.util.function.TriFunction;
/**
* App ops service local interface.
@@ -76,6 +81,55 @@
@Nullable String message, boolean shouldCollectMessage,
@NonNull HeptFunction<Integer, Integer, String, String, Boolean, String, Boolean,
Integer> superImpl);
+
+ /**
+ * Allows overriding note proxy operation behavior.
+ *
+ * @param code The op code to note.
+ * @param attributionSource The permission identity of the caller.
+ * @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 skipProxyOperation Whether to skip the proxy portion of the operation
+ * @param superImpl The super implementation.
+ * @return The app op note result.
+ */
+ int noteProxyOperation(int code, @NonNull AttributionSource attributionSource,
+ boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage, boolean skipProxyOperation,
+ @NonNull HexFunction<Integer, AttributionSource, Boolean, String, Boolean,
+ Boolean, Integer> superImpl);
+
+ /**
+ * Allows overriding start proxy operation behavior.
+ *
+ * @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.
+ * @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 skipProxyOperation Whether to skip the proxy portion of the operation
+ * @param superImpl The super implementation.
+ * @return The app op note result.
+ */
+ int 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,
+ Integer> 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);
}
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 03e95fc..f8165e9 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -60,6 +60,7 @@
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.content.AttributionSource;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -224,8 +225,8 @@
private final String mBasePackageName;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final String mOpPackageName;
-
private final @NonNull ContextParams mParams;
+ private @NonNull AttributionSource mAttributionSource;
private final @NonNull ResourcesManager mResourcesManager;
@UnsupportedAppUsage
@@ -467,13 +468,13 @@
/** @hide */
@Override
public String getOpPackageName() {
- return mOpPackageName != null ? mOpPackageName : getBasePackageName();
+ return mAttributionSource.getPackageName();
}
/** @hide */
@Override
public @Nullable String getAttributionTag() {
- return mParams.getAttributionTag();
+ return mAttributionSource.getAttributionTag();
}
@Override
@@ -482,6 +483,11 @@
}
@Override
+ public @NonNull AttributionSource getAttributionSource() {
+ return mAttributionSource;
+ }
+
+ @Override
public ApplicationInfo getApplicationInfo() {
if (mPackageInfo != null) {
return mPackageInfo.getApplicationInfo();
@@ -2074,13 +2080,7 @@
Log.v(TAG, "Treating renounced permission " + permission + " as denied");
return PERMISSION_DENIED;
}
-
- try {
- return ActivityManager.getService().checkPermissionWithToken(
- permission, pid, uid, callerToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return checkPermission(permission, pid, uid);
}
@Override
@@ -2415,8 +2415,10 @@
LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE);
if (pi != null) {
- ContextImpl c = new ContextImpl(this, mMainThread, pi, ContextParams.EMPTY, null,
- mToken, new UserHandle(UserHandle.getUserId(application.uid)),
+ ContextImpl c = new ContextImpl(this, mMainThread, pi, ContextParams.EMPTY,
+ mAttributionSource.getAttributionTag(),
+ mAttributionSource.getNext(),
+ null, mToken, new UserHandle(UserHandle.getUserId(application.uid)),
flags, null, null);
final int displayId = getDisplayId();
@@ -2446,15 +2448,19 @@
if (packageName.equals("system") || packageName.equals("android")) {
// The system resources are loaded in every application, so we can safely copy
// the context without reloading Resources.
- return new ContextImpl(this, mMainThread, mPackageInfo, mParams, null,
- mToken, user, flags, null, null);
+ return new ContextImpl(this, mMainThread, mPackageInfo, mParams,
+ mAttributionSource.getAttributionTag(),
+ mAttributionSource.getNext(),
+ null, mToken, user, flags, null, null);
}
LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
if (pi != null) {
- ContextImpl c = new ContextImpl(this, mMainThread, pi, mParams, null,
- mToken, user, flags, null, null);
+ ContextImpl c = new ContextImpl(this, mMainThread, pi, mParams,
+ mAttributionSource.getAttributionTag(),
+ mAttributionSource.getNext(),
+ null, mToken, user, flags, null, null);
final int displayId = getDisplayId();
final Integer overrideDisplayId = mForceDisplayOverrideInResources
@@ -2491,8 +2497,10 @@
final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName);
final String[] paths = mPackageInfo.getSplitPaths(splitName);
- final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo,
- mParams, splitName, mToken, mUser, mFlags, classLoader, null);
+ final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
+ mAttributionSource.getAttributionTag(),
+ mAttributionSource.getNext(),
+ splitName, mToken, mUser, mFlags, classLoader, null);
context.setResources(ResourcesManager.getInstance().getResources(
mToken,
@@ -2526,6 +2534,8 @@
}
ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
+ mAttributionSource.getAttributionTag(),
+ mAttributionSource.getNext(),
mSplitName, mToken, mUser, mFlags, mClassLoader, null);
final int displayId = getDisplayId();
@@ -2544,6 +2554,8 @@
}
ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
+ mAttributionSource.getAttributionTag(),
+ mAttributionSource.getNext(),
mSplitName, mToken, mUser, mFlags, mClassLoader, null);
final int displayId = display.getDisplayId();
@@ -2650,6 +2662,8 @@
@UiContext
ContextImpl createWindowContextBase(@NonNull IBinder token, @NonNull Display display) {
ContextImpl baseContext = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
+ mAttributionSource.getAttributionTag(),
+ mAttributionSource.getNext(),
mSplitName, token, mUser, mFlags, mClassLoader, null);
// Window contexts receive configurations directly from the server and as such do not
// need to override their display in ResourcesManager.
@@ -2695,9 +2709,10 @@
@NonNull
@Override
- public Context createContext(@NonNull ContextParams params) {
- return new ContextImpl(this, mMainThread, mPackageInfo, params, mSplitName,
- mToken, mUser, mFlags, mClassLoader, null);
+ public Context createContext(@NonNull ContextParams contextParams) {
+ return new ContextImpl(this, mMainThread, mPackageInfo, contextParams,
+ contextParams.getAttributionTag(), contextParams.getNextAttributionSource(),
+ mSplitName, mToken, mUser, mFlags, mClassLoader, null);
}
@Override
@@ -2710,16 +2725,20 @@
public Context createDeviceProtectedStorageContext() {
final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE)
| Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
- return new ContextImpl(this, mMainThread, mPackageInfo, mParams, mSplitName,
- mToken, mUser, flags, mClassLoader, null);
+ return new ContextImpl(this, mMainThread, mPackageInfo, mParams,
+ mAttributionSource.getAttributionTag(),
+ mAttributionSource.getNext(),
+ mSplitName, mToken, mUser, flags, mClassLoader, null);
}
@Override
public Context createCredentialProtectedStorageContext() {
final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE)
| Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
- return new ContextImpl(this, mMainThread, mPackageInfo, mParams, mSplitName,
- mToken, mUser, flags, mClassLoader, null);
+ return new ContextImpl(this, mMainThread, mPackageInfo, mParams,
+ mAttributionSource.getAttributionTag(),
+ mAttributionSource.getNext(),
+ mSplitName, mToken, mUser, flags, mClassLoader, null);
}
@Override
@@ -2893,7 +2912,7 @@
static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
ContextImpl context = new ContextImpl(null, mainThread, packageInfo,
- ContextParams.EMPTY, null, null, null, 0, null, null);
+ ContextParams.EMPTY, null, null, null, null, null, 0, null, null);
context.setResources(packageInfo.getResources());
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
context.mResourcesManager.getDisplayMetrics());
@@ -2911,7 +2930,7 @@
static ContextImpl createSystemUiContext(ContextImpl systemContext, int displayId) {
final LoadedApk packageInfo = systemContext.mPackageInfo;
ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo,
- ContextParams.EMPTY, null, null, null, 0, null, null);
+ ContextParams.EMPTY, null, null, null, null, null, 0, null, null);
context.setResources(createResources(null, packageInfo, null, displayId, null,
packageInfo.getCompatibilityInfo(), null));
context.updateDisplay(displayId);
@@ -2936,7 +2955,7 @@
String opPackageName) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
ContextImpl context = new ContextImpl(null, mainThread, packageInfo,
- ContextParams.EMPTY, null, null, null, 0, null, opPackageName);
+ ContextParams.EMPTY, null, null, null, null, null, 0, null, opPackageName);
context.setResources(packageInfo.getResources());
context.mContextType = isSystemOrSystemUI(context) ? CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI
: CONTEXT_TYPE_NON_UI;
@@ -2966,7 +2985,7 @@
}
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, ContextParams.EMPTY,
- activityInfo.splitName, activityToken, null, 0, classLoader, null);
+ null, null, activityInfo.splitName, activityToken, null, 0, classLoader, null);
context.mContextType = CONTEXT_TYPE_ACTIVITY;
// Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
@@ -2999,6 +3018,7 @@
private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
@NonNull LoadedApk packageInfo, @NonNull ContextParams params,
+ @Nullable String attributionTag, @Nullable AttributionSource nextAttributionSource,
@Nullable String splitName, @Nullable IBinder token, @Nullable UserHandle user,
int flags, @Nullable ClassLoader classLoader, @Nullable String overrideOpPackageName) {
mOuterContext = this;
@@ -3054,9 +3074,27 @@
mOpPackageName = overrideOpPackageName != null ? overrideOpPackageName : opPackageName;
mParams = Objects.requireNonNull(params);
+ initializeAttributionSource(attributionTag, nextAttributionSource);
mContentResolver = new ApplicationContentResolver(this, mainThread);
}
+ private void initializeAttributionSource(@Nullable String attributionTag,
+ @Nullable AttributionSource nextAttributionSource) {
+ mAttributionSource = new AttributionSource(Process.myUid(), mOpPackageName,
+ attributionTag, nextAttributionSource);
+ // 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 attributionSource = getSystemService(PermissionManager.class)
+ .registerAttributionSource(mAttributionSource);
+ if (attributionSource != null) {
+ mAttributionSource = attributionSource;
+ }
+ }
+ }
+
void setResources(Resources r) {
if (r instanceof CompatResources) {
((CompatResources) r).setContext(this);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 4c2433c..81e5e1d 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -475,8 +475,6 @@
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
boolean isTopOfTask(in IBinder token);
void bootAnimationComplete();
- int checkPermissionWithToken(in String permission, int pid, int uid,
- in IBinder callerToken);
@UnsupportedAppUsage
void registerTaskStackListener(in ITaskStackListener listener);
void unregisterTaskStackListener(in ITaskStackListener listener);
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index a3d19ca..0be7b73 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -745,7 +745,6 @@
* Use {@link #getDefaultAdapter} to get the BluetoothAdapter instance.
*/
BluetoothAdapter(IBluetoothManager managerService) {
-
if (managerService == null) {
throw new IllegalArgumentException("bluetooth manager service is null");
}
diff --git a/core/java/android/content/AttributionSource.aidl b/core/java/android/content/AttributionSource.aidl
new file mode 100644
index 0000000..10d5c27
--- /dev/null
+++ b/core/java/android/content/AttributionSource.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+package android.content;
+
+parcelable AttributionSource;
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
new file mode 100644
index 0000000..053bfc1
--- /dev/null
+++ b/core/java/android/content/AttributionSource.java
@@ -0,0 +1,612 @@
+/*
+ * 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.
+ */
+
+package android.content;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.AppGlobals;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.permission.PermissionManager;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class represents a source to which access to permission protected data should be
+ * attributed. Attribution sources can be chained to represent cases where the protected
+ * data would flow through several applications. For example, app A may ask app B for
+ * contacts and in turn app B may ask app C for contacts. In this case, the attribution
+ * chain would be A -> B -> C and the data flow would be C -> B -> A. There are two
+ * main benefits of using the attribution source mechanism: avoid doing explicit permission
+ * checks on behalf of the calling app if you are accessing private data on their behalf
+ * to send back; avoid double data access blaming which happens as you check the calling
+ * app's permissions and when you access the data behind these permissions (for runtime
+ * permissions). Also if not explicitly blaming the caller the data access would be
+ * counted towards your app vs to the previous app where yours was just a proxy.
+ * <p>
+ * Every {@link Context} has an attribution source and you can get it via {@link
+ * Context#getAttributionSource()} representing itself, which is a chain of one. You
+ * can attribute work to another app, or more precisely to a chain of apps, through
+ * which the data you would be accessing would flow, via {@link Context#createContext(
+ * ContextParams)} plus specifying an attribution source for the next app to receive
+ * the protected data you are accessing via {@link AttributionSource.Builder#setNext(
+ * AttributionSource)}. Creating this attribution chain ensures that the datasource would
+ * check whether every app in the attribution chain has permission to access the data
+ * before releasing it. The datasource will also record appropriately that this data was
+ * accessed by the apps in the sequence if the data is behind a sensitive permission
+ * (e.g. dangerous). Again, this is useful if you are accessing the data on behalf of another
+ * app, for example a speech recognizer using the mic so it can provide recognition to
+ * a calling app.
+ * <p>
+ * You can create an attribution chain of you and any other app without any verification
+ * as this is something already available via the {@link android.app.AppOpsManager} APIs.
+ * This is supported to handle cases where you don't have access to the caller's attribution
+ * source and you can directly use the {@link AttributionSource.Builder} APIs. However,
+ * if the data flows through more than two apps (more than you access the data for the
+ * caller - which you cannot know ahead of time) you need to have a handle to the {@link
+ * AttributionSource} for the calling app's context in order to create an attribution context.
+ * This means you either need to have an API for the other app to send you its attribution
+ * source or use a platform API that pipes the callers attribution source.
+ * <p>
+ * You cannot forge an attribution chain without the participation of every app in the
+ * attribution chain (aside of the special case mentioned above). To create an attribution
+ * source that is trusted you need to create an attribution context that points to an
+ * attribution source that was explicitly created by the app that it refers to, recursively.
+ * <p>
+ * Since creating an attribution context leads to all permissions for apps in the attribution
+ * chain being checked, you need to expect getting a security exception when accessing
+ * permission protected APIs since some app in the chain may not have the permission.
+ */
+@Immutable
+// TODO: Codegen doesn't properly verify the class if the parcelling is inner class
+// TODO: Codegen doesn't allow overriding the constructor to change its visibility
+// TODO: Codegen applies method level annotations to argument vs the generated member (@SystemApi)
+// TODO: Codegen doesn't properly read/write IBinder members
+// TODO: Codegen doesn't properly handle Set arguments
+// @DataClass(genEqualsHashCode = true, genConstructor = false, genBuilder = true)
+public final class AttributionSource implements Parcelable {
+ /**
+ * @hide
+ */
+ static class RenouncedPermissionsParcelling implements Parcelling<Set<String>> {
+
+ @Override
+ public void parcel(Set<String> item, Parcel dest, int parcelFlags) {
+ if (item == null) {
+ dest.writeInt(-1);
+ } else {
+ dest.writeInt(item.size());
+ for (String permission : item) {
+ dest.writeString8(permission);
+ }
+ }
+ }
+
+ @Override
+ public Set<String> unparcel(Parcel source) {
+ final int size = source.readInt();
+ if (size < 0) {
+ return null;
+ }
+ final ArraySet<String> result = new ArraySet<>(size);
+ for (int i = 0; i < size; i++) {
+ result.add(source.readString8());
+ }
+ return result;
+ }
+ }
+
+ /**
+ * The UID that is accessing the permission protected data.
+ */
+ private final int mUid;
+
+ /**
+ * The package that is accessing the permission protected data.
+ */
+ private @Nullable String mPackageName = null;
+
+ /**
+ * The attribution tag of the app accessing the permission protected data.
+ */
+ private @Nullable String mAttributionTag = null;
+
+ /**
+ * Unique token for that source.
+ *
+ * @hide
+ */
+ private @Nullable IBinder mToken = null;
+
+ /**
+ * Permissions that should be considered revoked regardless if granted.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
+ @DataClass.ParcelWith(RenouncedPermissionsParcelling.class)
+ private @Nullable Set<String> mRenouncedPermissions = null;
+
+ /**
+ * The next app to receive the permission protected data.
+ */
+ private @Nullable AttributionSource mNext = null;
+
+ /** @hide */
+ @TestApi
+ public AttributionSource(int uid, @Nullable String packageName,
+ @Nullable String attributionTag) {
+ this(uid, packageName, attributionTag, /*next*/ null);
+ }
+
+ /** @hide */
+ @TestApi
+ public AttributionSource(int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable AttributionSource next) {
+ this(uid, packageName, attributionTag, /*token*/ null,
+ /*renouncedPermissions*/ null, next);
+ }
+
+ /** @hide */
+ public AttributionSource(@NonNull AttributionSource current,
+ @Nullable AttributionSource next) {
+ this(current.getUid(), current.getPackageName(), current.getAttributionTag(),
+ /*token*/ null, /*renouncedPermissions*/ null, next);
+ }
+
+ /** @hide */
+ public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
+ return new AttributionSource(mUid, mPackageName, mAttributionTag, mToken,
+ mRenouncedPermissions, next);
+ }
+
+ /** @hide */
+ public AttributionSource withToken(@Nullable IBinder token) {
+ return new AttributionSource(mUid, mPackageName, mAttributionTag, token,
+ mRenouncedPermissions, mNext);
+ }
+
+ /**
+ * 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
+ * to pass you a source from another app without including themselves in the
+ * attribution chain.
+ *
+ * @throws SecurityException if the attribution source cannot be trusted to be
+ * from the caller.
+ */
+ public void enforceCallingUid() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID && callingUid != mUid) {
+ throw new SecurityException("Calling uid: " + callingUid
+ + " doesn't match source uid: " + mUid);
+ }
+ // No need to check package as app ops manager does it already.
+ }
+
+ /**
+ * 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
+ * to pass you a source from another app without including themselves in the
+ * attribution chain.
+ *f
+ * @return if the attribution source cannot be trusted to be from the caller.
+ */
+ public boolean checkCallingUid() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID && callingUid != mUid) {
+ return false;
+ }
+ // No need to check package as app ops manager does it already.
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ if (Build.IS_DEBUGGABLE) {
+ return "AttributionSource { " +
+ "uid = " + mUid + ", " +
+ "packageName = " + mPackageName + ", " +
+ "attributionTag = " + mAttributionTag + ", " +
+ "token = " + mToken + ", " +
+ "next = " + mNext +
+ " }";
+ }
+ return super.toString();
+ }
+
+ /**
+ * @return The next UID that would receive the permission protected data.
+ *
+ * @hide
+ */
+ public int getNextUid() {
+ if (mNext != null) {
+ return mNext.getUid();
+ }
+ return Process.INVALID_UID;
+ }
+
+ /**
+ * @return The next package that would receive the permission protected data.
+ *
+ * @hide
+ */
+ public @Nullable String getNextPackageName() {
+ if (mNext != null) {
+ return mNext.getPackageName();
+ }
+ return null;
+ }
+
+ /**
+ * @return The nexxt package's attribution tag that would receive
+ * the permission protected data.
+ *
+ * @hide
+ */
+ public @Nullable String getNextAttributionTag() {
+ if (mNext != null) {
+ return mNext.getAttributionTag();
+ }
+ 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.
+ *
+ * @param context Context handle.
+ * @return Whether this is a trusted source.
+ */
+ public boolean isTrusted(@NonNull Context context) {
+ return mToken != null && context.getSystemService(PermissionManager.class)
+ .isRegisteredAttributionSource(this);
+ }
+
+ /**
+ * Permissions that should be considered revoked regardless if granted.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
+ @NonNull
+ public Set<String> getRenouncedPermissions() {
+ return CollectionUtils.emptyIfNull(mRenouncedPermissions);
+ }
+
+ @DataClass.Suppress({"setUid", "setToken"})
+ static class BaseBuilder {}
+
+
+
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/AttributionSource.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /* package-private */ AttributionSource(
+ int uid,
+ @Nullable String packageName,
+ @Nullable String attributionTag,
+ @Nullable IBinder token,
+ @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) @Nullable Set<String> renouncedPermissions,
+ @Nullable AttributionSource next) {
+ this.mUid = uid;
+ this.mPackageName = packageName;
+ this.mAttributionTag = attributionTag;
+ this.mToken = token;
+ this.mRenouncedPermissions = renouncedPermissions;
+ com.android.internal.util.AnnotationValidations.validate(
+ SystemApi.class, null, mRenouncedPermissions);
+ com.android.internal.util.AnnotationValidations.validate(
+ RequiresPermission.class, null, mRenouncedPermissions,
+ "value", android.Manifest.permission.RENOUNCE_PERMISSIONS);
+ this.mNext = next;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The UID that is accessing the permission protected data.
+ */
+ public int getUid() {
+ return mUid;
+ }
+
+ /**
+ * The package that is accessing the permission protected data.
+ */
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * The attribution tag of the app accessing the permission protected data.
+ */
+ public @Nullable String getAttributionTag() {
+ return mAttributionTag;
+ }
+
+ /**
+ * Unique token for that source.
+ *
+ * @hide
+ */
+ public @Nullable IBinder getToken() {
+ return mToken;
+ }
+
+ /**
+ * The next app to receive the permission protected data.
+ */
+ public @Nullable AttributionSource getNext() {
+ return mNext;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(AttributionSource other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ AttributionSource that = (AttributionSource) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mUid == that.mUid
+ && Objects.equals(mPackageName, that.mPackageName)
+ && Objects.equals(mAttributionTag, that.mAttributionTag)
+ && Objects.equals(mToken, that.mToken)
+ && Objects.equals(mRenouncedPermissions, that.mRenouncedPermissions)
+ && Objects.equals(mNext, that.mNext);
+ }
+
+ @Override
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mUid;
+ _hash = 31 * _hash + Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + Objects.hashCode(mAttributionTag);
+ _hash = 31 * _hash + Objects.hashCode(mToken);
+ _hash = 31 * _hash + Objects.hashCode(mRenouncedPermissions);
+ _hash = 31 * _hash + Objects.hashCode(mNext);
+ return _hash;
+ }
+
+ static Parcelling<Set<String>> sParcellingForRenouncedPermissions =
+ Parcelling.Cache.get(
+ RenouncedPermissionsParcelling.class);
+ static {
+ if (sParcellingForRenouncedPermissions == null) {
+ sParcellingForRenouncedPermissions = Parcelling.Cache.put(
+ new RenouncedPermissionsParcelling());
+ }
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mPackageName != null) flg |= 0x2;
+ if (mAttributionTag != null) flg |= 0x4;
+ if (mToken != null) flg |= 0x8;
+ if (mRenouncedPermissions != null) flg |= 0x10;
+ if (mNext != null) flg |= 0x20;
+ dest.writeByte(flg);
+ dest.writeInt(mUid);
+ if (mPackageName != null) dest.writeString(mPackageName);
+ if (mAttributionTag != null) dest.writeString(mAttributionTag);
+ if (mToken != null) dest.writeStrongBinder(mToken);
+ sParcellingForRenouncedPermissions.parcel(mRenouncedPermissions, dest, flags);
+ if (mNext != null) dest.writeTypedObject(mNext, flags);
+ }
+
+ @Override
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ /* package-private */ AttributionSource(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int uid = in.readInt();
+ String packageName = (flg & 0x2) == 0 ? null : in.readString();
+ String attributionTag = (flg & 0x4) == 0 ? null : in.readString();
+ IBinder token = (flg & 0x8) == 0 ? null : in.readStrongBinder();
+ Set<String> renouncedPermissions = sParcellingForRenouncedPermissions.unparcel(in);
+ AttributionSource next = (flg & 0x20) == 0 ? null : (AttributionSource) in.readTypedObject(AttributionSource.CREATOR);
+
+ this.mUid = uid;
+ this.mPackageName = packageName;
+ this.mAttributionTag = attributionTag;
+ this.mToken = token;
+ this.mRenouncedPermissions = renouncedPermissions;
+ com.android.internal.util.AnnotationValidations.validate(
+ SystemApi.class, null, mRenouncedPermissions);
+ com.android.internal.util.AnnotationValidations.validate(
+ RequiresPermission.class, null, mRenouncedPermissions,
+ "value", android.Manifest.permission.RENOUNCE_PERMISSIONS);
+ this.mNext = next;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ public static final @NonNull Parcelable.Creator<AttributionSource> CREATOR
+ = new Parcelable.Creator<AttributionSource>() {
+ @Override
+ public AttributionSource[] newArray(int size) {
+ return new AttributionSource[size];
+ }
+
+ @Override
+ public AttributionSource createFromParcel(@NonNull Parcel in) {
+ return new AttributionSource(in);
+ }
+ };
+
+ /**
+ * A builder for {@link AttributionSource}
+ */
+ @SuppressWarnings("WeakerAccess")
+ public static final class Builder extends BaseBuilder {
+
+ private int mUid;
+ private @Nullable String mPackageName;
+ private @Nullable String mAttributionTag;
+ private @Nullable IBinder mToken;
+ private @SystemApi @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) @Nullable Set<String> mRenouncedPermissions;
+ private @Nullable AttributionSource mNext;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param uid
+ * The UID that is accessing the permission protected data.
+ */
+ public Builder(
+ int uid) {
+ mUid = uid;
+ }
+
+ /**
+ * The package that is accessing the permission protected data.
+ */
+ public @NonNull Builder setPackageName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mPackageName = value;
+ return this;
+ }
+
+ /**
+ * The attribution tag of the app accessing the permission protected data.
+ */
+ public @NonNull Builder setAttributionTag(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mAttributionTag = value;
+ return this;
+ }
+
+ /**
+ * Permissions that should be considered revoked regardless if granted.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
+ public @NonNull Builder setRenouncedPermissions(@NonNull Set<String> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10;
+ mRenouncedPermissions = value;
+ return this;
+ }
+
+ /**
+ * The next app to receive the permission protected data.
+ */
+ public @NonNull Builder setNext(@NonNull AttributionSource value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mNext = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull AttributionSource build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x40; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mPackageName = null;
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mAttributionTag = null;
+ }
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mToken = null;
+ }
+ if ((mBuilderFieldsSet & 0x10) == 0) {
+ mRenouncedPermissions = null;
+ }
+ if ((mBuilderFieldsSet & 0x20) == 0) {
+ mNext = null;
+ }
+ AttributionSource o = new AttributionSource(
+ mUid,
+ mPackageName,
+ mAttributionTag,
+ mToken,
+ mRenouncedPermissions,
+ mNext);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x40) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 73b4f62..8284203 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -18,11 +18,6 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_ERRORED;
-import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Trace.TRACE_TAG_DATABASE;
import android.annotation.NonNull;
@@ -45,7 +40,6 @@
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
@@ -57,7 +51,6 @@
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
@@ -141,7 +134,7 @@
private boolean mNoPerms;
private boolean mSingleUser;
- private ThreadLocal<Pair<String, String>> mCallingPackage;
+ private ThreadLocal<AttributionSource> mCallingAttributionSource;
private Transport mTransport = new Transport();
@@ -231,13 +224,13 @@
}
@Override
- public Cursor query(String callingPkg, @Nullable String attributionTag, Uri uri,
+ public Cursor query(@NonNull AttributionSource attributionSource, Uri uri,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable ICancellationSignal cancellationSignal) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- if (enforceReadPermission(callingPkg, attributionTag, uri, null)
- != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
// The caller has no access to the data, so return an empty cursor with
// the columns in the requested order. The caller may ask for an invalid
// column and we would not catch that but this is not a problem in practice.
@@ -253,8 +246,8 @@
// we have to execute the query as if allowed to get a cursor with the
// columns. We then use the column names to return an empty cursor.
Cursor cursor;
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
cursor = mInterface.query(
uri, projection, queryArgs,
@@ -262,7 +255,7 @@
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
}
if (cursor == null) {
return null;
@@ -272,8 +265,8 @@
return new MatrixCursor(cursor.getColumnNames(), 0);
}
Trace.traceBegin(TRACE_TAG_DATABASE, "query");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return mInterface.query(
uri, projection, queryArgs,
@@ -281,7 +274,7 @@
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
@@ -314,60 +307,59 @@
}
@Override
- public Uri insert(String callingPkg, @Nullable String attributionTag, Uri uri,
+ public Uri insert(@NonNull AttributionSource attributionSource, Uri uri,
ContentValues initialValues, Bundle extras) {
uri = validateIncomingUri(uri);
int userId = getUserIdFromUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- if (enforceWritePermission(callingPkg, attributionTag, uri, null)
- != AppOpsManager.MODE_ALLOWED) {
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ if (enforceWritePermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return rejectInsert(uri, initialValues);
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
}
}
Trace.traceBegin(TRACE_TAG_DATABASE, "insert");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return maybeAddUserId(mInterface.insert(uri, initialValues, extras), userId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
@Override
- public int bulkInsert(String callingPkg, @Nullable String attributionTag, Uri uri,
+ public int bulkInsert(@NonNull AttributionSource attributionSource, Uri uri,
ContentValues[] initialValues) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- if (enforceWritePermission(callingPkg, attributionTag, uri, null)
- != AppOpsManager.MODE_ALLOWED) {
+ if (enforceWritePermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
return 0;
}
Trace.traceBegin(TRACE_TAG_DATABASE, "bulkInsert");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return mInterface.bulkInsert(uri, initialValues);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
@Override
- public ContentProviderResult[] applyBatch(String callingPkg,
- @Nullable String attributionTag, String authority,
- ArrayList<ContentProviderOperation> operations)
+ public ContentProviderResult[] applyBatch(@NonNull AttributionSource attributionSource,
+ String authority, ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
validateIncomingAuthority(authority);
int numOperations = operations.size();
@@ -383,22 +375,24 @@
operation = new ContentProviderOperation(operation, uri);
operations.set(i, operation);
}
+ final AttributionSource accessAttributionSource =
+ attributionSource;
if (operation.isReadOperation()) {
- if (enforceReadPermission(callingPkg, attributionTag, uri, null)
- != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(accessAttributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
throw new OperationApplicationException("App op not allowed", 0);
}
}
if (operation.isWriteOperation()) {
- if (enforceWritePermission(callingPkg, attributionTag, uri, null)
- != AppOpsManager.MODE_ALLOWED) {
+ if (enforceWritePermission(accessAttributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
throw new OperationApplicationException("App op not allowed", 0);
}
}
}
Trace.traceBegin(TRACE_TAG_DATABASE, "applyBatch");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
ContentProviderResult[] results = mInterface.applyBatch(authority,
operations);
@@ -414,111 +408,111 @@
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
@Override
- public int delete(String callingPkg, @Nullable String attributionTag, Uri uri,
+ public int delete(@NonNull AttributionSource attributionSource, Uri uri,
Bundle extras) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- if (enforceWritePermission(callingPkg, attributionTag, uri, null)
- != AppOpsManager.MODE_ALLOWED) {
+ if (enforceWritePermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
return 0;
}
Trace.traceBegin(TRACE_TAG_DATABASE, "delete");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return mInterface.delete(uri, extras);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
@Override
- public int update(String callingPkg, @Nullable String attributionTag, Uri uri,
+ public int update(@NonNull AttributionSource attributionSource, Uri uri,
ContentValues values, Bundle extras) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- if (enforceWritePermission(callingPkg, attributionTag, uri, null)
- != AppOpsManager.MODE_ALLOWED) {
+ if (enforceWritePermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
return 0;
}
Trace.traceBegin(TRACE_TAG_DATABASE, "update");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return mInterface.update(uri, values, extras);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
@Override
- public ParcelFileDescriptor openFile(String callingPkg, @Nullable String attributionTag,
- Uri uri, String mode, ICancellationSignal cancellationSignal, IBinder callerToken)
+ public ParcelFileDescriptor openFile(@NonNull AttributionSource attributionSource,
+ Uri uri, String mode, ICancellationSignal cancellationSignal)
throws FileNotFoundException {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- enforceFilePermission(callingPkg, attributionTag, uri, mode, callerToken);
+ enforceFilePermission(attributionSource, uri, mode);
Trace.traceBegin(TRACE_TAG_DATABASE, "openFile");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return mInterface.openFile(
uri, mode, CancellationSignal.fromTransport(cancellationSignal));
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
@Override
- public AssetFileDescriptor openAssetFile(String callingPkg, @Nullable String attributionTag,
+ public AssetFileDescriptor openAssetFile(@NonNull AttributionSource attributionSource,
Uri uri, String mode, ICancellationSignal cancellationSignal)
throws FileNotFoundException {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- enforceFilePermission(callingPkg, attributionTag, uri, mode, null);
+ enforceFilePermission(attributionSource, uri, mode);
Trace.traceBegin(TRACE_TAG_DATABASE, "openAssetFile");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return mInterface.openAssetFile(
uri, mode, CancellationSignal.fromTransport(cancellationSignal));
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
@Override
- public Bundle call(String callingPkg, @Nullable String attributionTag, String authority,
+ public Bundle call(@NonNull AttributionSource attributionSource, String authority,
String method, @Nullable String arg, @Nullable Bundle extras) {
validateIncomingAuthority(authority);
Bundle.setDefusable(extras, true);
Trace.traceBegin(TRACE_TAG_DATABASE, "call");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return mInterface.call(authority, method, arg, extras);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
@@ -539,23 +533,23 @@
}
@Override
- public AssetFileDescriptor openTypedAssetFile(String callingPkg,
- @Nullable String attributionTag, Uri uri, String mimeType, Bundle opts,
- ICancellationSignal cancellationSignal) throws FileNotFoundException {
+ public AssetFileDescriptor openTypedAssetFile(
+ @NonNull AttributionSource attributionSource, Uri uri, String mimeType,
+ Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException {
Bundle.setDefusable(opts, true);
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- enforceFilePermission(callingPkg, attributionTag, uri, "r", null);
+ enforceFilePermission(attributionSource, uri, "r");
Trace.traceBegin(TRACE_TAG_DATABASE, "openTypedAssetFile");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return mInterface.openTypedAssetFile(
uri, mimeType, opts, CancellationSignal.fromTransport(cancellationSignal));
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
@@ -566,34 +560,34 @@
}
@Override
- public Uri canonicalize(String callingPkg, @Nullable String attributionTag, Uri uri) {
+ public Uri canonicalize(@NonNull AttributionSource attributionSource, Uri uri) {
uri = validateIncomingUri(uri);
int userId = getUserIdFromUri(uri);
uri = getUriWithoutUserId(uri);
- if (enforceReadPermission(callingPkg, attributionTag, uri, null)
- != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
return null;
}
Trace.traceBegin(TRACE_TAG_DATABASE, "canonicalize");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return maybeAddUserId(mInterface.canonicalize(uri), userId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
@Override
- public void canonicalizeAsync(String callingPkg, @Nullable String attributionTag, Uri uri,
+ public void canonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri,
RemoteCallback callback) {
final Bundle result = new Bundle();
try {
result.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
- canonicalize(callingPkg, attributionTag, uri));
+ canonicalize(attributionSource, uri));
} catch (Exception e) {
result.putParcelable(ContentResolver.REMOTE_CALLBACK_ERROR,
new ParcelableException(e));
@@ -602,34 +596,34 @@
}
@Override
- public Uri uncanonicalize(String callingPkg, String attributionTag, Uri uri) {
+ public Uri uncanonicalize(@NonNull AttributionSource attributionSource, Uri uri) {
uri = validateIncomingUri(uri);
int userId = getUserIdFromUri(uri);
uri = getUriWithoutUserId(uri);
- if (enforceReadPermission(callingPkg, attributionTag, uri, null)
- != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
return null;
}
Trace.traceBegin(TRACE_TAG_DATABASE, "uncanonicalize");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return maybeAddUserId(mInterface.uncanonicalize(uri), userId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
@Override
- public void uncanonicalizeAsync(String callingPkg, @Nullable String attributionTag, Uri uri,
+ public void uncanonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri,
RemoteCallback callback) {
final Bundle result = new Bundle();
try {
result.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
- uncanonicalize(callingPkg, attributionTag, uri));
+ uncanonicalize(attributionSource, uri));
} catch (Exception e) {
result.putParcelable(ContentResolver.REMOTE_CALLBACK_ERROR,
new ParcelableException(e));
@@ -638,92 +632,95 @@
}
@Override
- public boolean refresh(String callingPkg, String attributionTag, Uri uri, Bundle extras,
- ICancellationSignal cancellationSignal) throws RemoteException {
+ public boolean refresh(@NonNull AttributionSource attributionSource, Uri uri,
+ Bundle extras, ICancellationSignal cancellationSignal) throws RemoteException {
uri = validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
- if (enforceReadPermission(callingPkg, attributionTag, uri, null)
- != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
return false;
}
Trace.traceBegin(TRACE_TAG_DATABASE, "refresh");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return mInterface.refresh(uri, extras,
CancellationSignal.fromTransport(cancellationSignal));
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
@Override
- public int checkUriPermission(String callingPkg, @Nullable String attributionTag, Uri uri,
+ public int checkUriPermission(@NonNull AttributionSource attributionSource, Uri uri,
int uid, int modeFlags) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
Trace.traceBegin(TRACE_TAG_DATABASE, "checkUriPermission");
- final Pair<String, String> original = setCallingPackage(
- new Pair<>(callingPkg, attributionTag));
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
try {
return mInterface.checkUriPermission(uri, uid, modeFlags);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- setCallingPackage(original);
+ setCallingAttributionSource(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}
- private void enforceFilePermission(String callingPkg, @Nullable String attributionTag,
- Uri uri, String mode, IBinder callerToken)
+ @PermissionChecker.PermissionResult
+ private void enforceFilePermission(@NonNull AttributionSource attributionSource,
+ Uri uri, String mode)
throws FileNotFoundException, SecurityException {
if (mode != null && mode.indexOf('w') != -1) {
- if (enforceWritePermission(callingPkg, attributionTag, uri, callerToken)
- != AppOpsManager.MODE_ALLOWED) {
+ if (enforceWritePermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
throw new FileNotFoundException("App op not allowed");
}
} else {
- if (enforceReadPermission(callingPkg, attributionTag, uri, callerToken)
- != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
throw new FileNotFoundException("App op not allowed");
}
}
}
- private int enforceReadPermission(String callingPkg, @Nullable String attributionTag,
- Uri uri, IBinder callerToken)
+ @PermissionChecker.PermissionResult
+ private int enforceReadPermission(@NonNull AttributionSource attributionSource, Uri uri)
throws SecurityException {
- final int mode = enforceReadPermissionInner(uri, callingPkg, attributionTag,
- callerToken);
- if (mode != MODE_ALLOWED) {
- return mode;
+ final int result = enforceReadPermissionInner(uri, attributionSource);
+ if (result != PermissionChecker.PERMISSION_GRANTED) {
+ return result;
}
-
- return noteProxyOp(callingPkg, attributionTag, mReadOp);
+ // Only check the read op if it differs from the one for the permission
+ // we already checked above to avoid double attribution for every access.
+ if (mTransport.mReadOp != AppOpsManager.OP_NONE
+ && mTransport.mReadOp != AppOpsManager.permissionToOpCode(mReadPermission)) {
+ return PermissionChecker.checkOpForDataDelivery(getContext(),
+ AppOpsManager.opToPublicName(mTransport.mReadOp),
+ attributionSource, /*message*/ null);
+ }
+ return PermissionChecker.PERMISSION_GRANTED;
}
- private int enforceWritePermission(String callingPkg, String attributionTag, Uri uri,
- IBinder callerToken)
+ @PermissionChecker.PermissionResult
+ private int enforceWritePermission(@NonNull AttributionSource attributionSource, Uri uri)
throws SecurityException {
- final int mode = enforceWritePermissionInner(uri, callingPkg, attributionTag,
- callerToken);
- if (mode != MODE_ALLOWED) {
- return mode;
+ final int result = enforceWritePermissionInner(uri, attributionSource);
+ if (result != PermissionChecker.PERMISSION_GRANTED) {
+ return result;
}
-
- return noteProxyOp(callingPkg, attributionTag, mWriteOp);
- }
-
- private int noteProxyOp(String callingPkg, String attributionTag, int op) {
- if (op != AppOpsManager.OP_NONE) {
- int mode = mAppOpsManager.noteProxyOp(op, callingPkg, Binder.getCallingUid(),
- attributionTag, null);
- return mode == MODE_DEFAULT ? MODE_IGNORED : mode;
+ // Only check the write op if it differs from the one for the permission
+ // we already checked above to avoid double attribution for every access.
+ if (mTransport.mWriteOp != AppOpsManager.OP_NONE
+ && mTransport.mWriteOp != AppOpsManager.permissionToOpCode(mWritePermission)) {
+ return PermissionChecker.checkOpForDataDelivery(getContext(),
+ AppOpsManager.opToPublicName(mTransport.mWriteOp),
+ attributionSource, /*message*/ null);
}
-
- return AppOpsManager.MODE_ALLOWED;
+ return PermissionChecker.PERMISSION_GRANTED;
}
}
@@ -731,49 +728,53 @@
if (UserHandle.getUserId(uid) == context.getUserId() || mSingleUser) {
return true;
}
- return context.checkPermission(INTERACT_ACROSS_USERS, pid, uid) == PERMISSION_GRANTED
+ return context.checkPermission(INTERACT_ACROSS_USERS, pid, uid)
+ == PackageManager.PERMISSION_GRANTED
|| context.checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid)
- == PERMISSION_GRANTED;
+ == PackageManager.PERMISSION_GRANTED;
}
/**
* Verify that calling app holds both the given permission and any app-op
* associated with that permission.
*/
- private int checkPermissionAndAppOp(String permission, String callingPkg,
- @Nullable String attributionTag, IBinder callerToken) {
- if (getContext().checkPermission(permission, Binder.getCallingPid(), Binder.getCallingUid(),
- callerToken) != PERMISSION_GRANTED) {
- return MODE_ERRORED;
+ @PermissionChecker.PermissionResult
+ private int checkPermission(String permission,
+ @NonNull AttributionSource attributionSource) {
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return PermissionChecker.PERMISSION_GRANTED;
}
-
- return mTransport.noteProxyOp(callingPkg, attributionTag,
- AppOpsManager.permissionToOpCode(permission));
+ if (!attributionSource.checkCallingUid()) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+ return PermissionChecker.checkPermissionForDataDeliveryFromDataSource(getContext(),
+ permission, -1, new AttributionSource(getContext().getAttributionSource(),
+ attributionSource), /*message*/ null);
}
/** {@hide} */
- protected int enforceReadPermissionInner(Uri uri, String callingPkg,
- @Nullable String attributionTag, IBinder callerToken) throws SecurityException {
+ @PermissionChecker.PermissionResult
+ protected int enforceReadPermissionInner(Uri uri,
+ @NonNull AttributionSource attributionSource) throws SecurityException {
final Context context = getContext();
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
String missingPerm = null;
- int strongestMode = MODE_ALLOWED;
+ int strongestResult = PermissionChecker.PERMISSION_GRANTED;
if (UserHandle.isSameApp(uid, mMyUid)) {
- return MODE_ALLOWED;
+ return PermissionChecker.PERMISSION_GRANTED;
}
if (mExported && checkUser(pid, uid, context)) {
final String componentPerm = getReadPermission();
if (componentPerm != null) {
- final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, attributionTag,
- callerToken);
- if (mode == MODE_ALLOWED) {
- return MODE_ALLOWED;
+ final int result = checkPermission(componentPerm, attributionSource);
+ if (result == PermissionChecker.PERMISSION_GRANTED) {
+ return PermissionChecker.PERMISSION_GRANTED;
} else {
missingPerm = componentPerm;
- strongestMode = Math.max(strongestMode, mode);
+ strongestResult = Math.max(strongestResult, result);
}
}
@@ -787,16 +788,15 @@
for (PathPermission pp : pps) {
final String pathPerm = pp.getReadPermission();
if (pathPerm != null && pp.match(path)) {
- final int mode = checkPermissionAndAppOp(pathPerm, callingPkg,
- attributionTag, callerToken);
- if (mode == MODE_ALLOWED) {
- return MODE_ALLOWED;
+ final int result = checkPermission(pathPerm, attributionSource);
+ if (result == PermissionChecker.PERMISSION_GRANTED) {
+ return PermissionChecker.PERMISSION_GRANTED;
} else {
// any denied <path-permission> means we lose
// default <provider> access.
allowDefaultRead = false;
missingPerm = pathPerm;
- strongestMode = Math.max(strongestMode, mode);
+ strongestResult = Math.max(strongestResult, result);
}
}
}
@@ -804,22 +804,22 @@
// if we passed <path-permission> checks above, and no default
// <provider> permission, then allow access.
- if (allowDefaultRead) return MODE_ALLOWED;
+ if (allowDefaultRead) return PermissionChecker.PERMISSION_GRANTED;
}
// last chance, check against any uri grants
final int callingUserId = UserHandle.getUserId(uid);
final Uri userUri = (mSingleUser && !UserHandle.isSameUser(mMyUid, uid))
? maybeAddUserId(uri, callingUserId) : uri;
- if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION,
- callerToken) == PERMISSION_GRANTED) {
- return MODE_ALLOWED;
+ if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED) {
+ return PermissionChecker.PERMISSION_GRANTED;
}
// If the worst denial we found above was ignored, then pass that
// ignored through; otherwise we assume it should be a real error below.
- if (strongestMode == MODE_IGNORED) {
- return MODE_IGNORED;
+ if (strongestResult == PermissionChecker.PERMISSION_SOFT_DENIED) {
+ return PermissionChecker.PERMISSION_SOFT_DENIED;
}
final String suffix;
@@ -836,28 +836,28 @@
}
/** {@hide} */
- protected int enforceWritePermissionInner(Uri uri, String callingPkg,
- @Nullable String attributionTag, IBinder callerToken) throws SecurityException {
+ @PermissionChecker.PermissionResult
+ protected int enforceWritePermissionInner(Uri uri,
+ @NonNull AttributionSource attributionSource) throws SecurityException {
final Context context = getContext();
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
String missingPerm = null;
- int strongestMode = MODE_ALLOWED;
+ int strongestResult = PermissionChecker.PERMISSION_GRANTED;
if (UserHandle.isSameApp(uid, mMyUid)) {
- return MODE_ALLOWED;
+ return PermissionChecker.PERMISSION_GRANTED;
}
if (mExported && checkUser(pid, uid, context)) {
final String componentPerm = getWritePermission();
if (componentPerm != null) {
- final int mode = checkPermissionAndAppOp(componentPerm, callingPkg,
- attributionTag, callerToken);
- if (mode == MODE_ALLOWED) {
- return MODE_ALLOWED;
+ final int mode = checkPermission(componentPerm, attributionSource);
+ if (mode == PermissionChecker.PERMISSION_GRANTED) {
+ return PermissionChecker.PERMISSION_GRANTED;
} else {
missingPerm = componentPerm;
- strongestMode = Math.max(strongestMode, mode);
+ strongestResult = Math.max(strongestResult, mode);
}
}
@@ -871,16 +871,15 @@
for (PathPermission pp : pps) {
final String pathPerm = pp.getWritePermission();
if (pathPerm != null && pp.match(path)) {
- final int mode = checkPermissionAndAppOp(pathPerm, callingPkg,
- attributionTag, callerToken);
- if (mode == MODE_ALLOWED) {
- return MODE_ALLOWED;
+ final int mode = checkPermission(pathPerm, attributionSource);
+ if (mode == PermissionChecker.PERMISSION_GRANTED) {
+ return PermissionChecker.PERMISSION_GRANTED;
} else {
// any denied <path-permission> means we lose
// default <provider> access.
allowDefaultWrite = false;
missingPerm = pathPerm;
- strongestMode = Math.max(strongestMode, mode);
+ strongestResult = Math.max(strongestResult, mode);
}
}
}
@@ -888,19 +887,19 @@
// if we passed <path-permission> checks above, and no default
// <provider> permission, then allow access.
- if (allowDefaultWrite) return MODE_ALLOWED;
+ if (allowDefaultWrite) return PermissionChecker.PERMISSION_GRANTED;
}
// last chance, check against any uri grants
- if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
- callerToken) == PERMISSION_GRANTED) {
- return MODE_ALLOWED;
+ if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED) {
+ return PermissionChecker.PERMISSION_GRANTED;
}
// If the worst denial we found above was ignored, then pass that
// ignored through; otherwise we assume it should be a real error below.
- if (strongestMode == MODE_IGNORED) {
- return MODE_IGNORED;
+ if (strongestResult == PermissionChecker.PERMISSION_SOFT_DENIED) {
+ return PermissionChecker.PERMISSION_SOFT_DENIED;
}
final String failReason = mExported
@@ -941,9 +940,10 @@
* Set the calling package/feature, returning the current value (or {@code null})
* which can be used later to restore the previous state.
*/
- private Pair<String, String> setCallingPackage(Pair<String, String> callingPackage) {
- final Pair<String, String> original = mCallingPackage.get();
- mCallingPackage.set(callingPackage);
+ private @Nullable AttributionSource setCallingAttributionSource(
+ @Nullable AttributionSource attributionSource) {
+ final AttributionSource original = mCallingAttributionSource.get();
+ mCallingAttributionSource.set(attributionSource);
onCallingPackageChanged();
return original;
}
@@ -963,13 +963,30 @@
* calling UID.
*/
public final @Nullable String getCallingPackage() {
- final Pair<String, String> pkg = mCallingPackage.get();
- if (pkg != null) {
- mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg.first);
- return pkg.first;
- }
+ final AttributionSource callingAttributionSource = getCallingAttributionSource();
+ return (callingAttributionSource != null)
+ ? callingAttributionSource.getPackageName() : null;
+ }
- return null;
+ /**
+ * Gets the attribution source of the calling app. If you want to attribute
+ * the data access to the calling app you can create an attribution context
+ * via {@link android.content.Context#createContext(ContextParams)} and passing
+ * this identity to {@link ContextParams.Builder#setNextAttributionSource(
+ * AttributionSource)}.
+ *
+ * @return The identity of the caller for permission purposes.
+ *
+ * @see ContextParams.Builder#setNextAttributionSource(AttributionSource)
+ * @see AttributionSource
+ */
+ public final @Nullable AttributionSource getCallingAttributionSource() {
+ final AttributionSource attributionSource = mCallingAttributionSource.get();
+ if (attributionSource != null) {
+ mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(),
+ attributionSource.getPackageName());
+ }
+ return attributionSource;
}
/**
@@ -983,11 +1000,10 @@
* @see #getCallingPackage
*/
public final @Nullable String getCallingAttributionTag() {
- final Pair<String, String> pkg = mCallingPackage.get();
- if (pkg != null) {
- return pkg.second;
+ final AttributionSource attributionSource = mCallingAttributionSource.get();
+ if (attributionSource != null) {
+ return attributionSource.getAttributionTag();
}
-
return null;
}
@@ -1012,11 +1028,10 @@
* @see Context#grantUriPermission(String, Uri, int)
*/
public final @Nullable String getCallingPackageUnchecked() {
- final Pair<String, String> pkg = mCallingPackage.get();
- if (pkg != null) {
- return pkg.first;
+ final AttributionSource attributionSource = mCallingAttributionSource.get();
+ if (attributionSource != null) {
+ return attributionSource.getPackageName();
}
-
return null;
}
@@ -1038,12 +1053,12 @@
/** {@hide} */
public final long binderToken;
/** {@hide} */
- public final Pair<String, String> callingPackage;
+ public final @Nullable AttributionSource callingAttributionSource;
/** {@hide} */
- public CallingIdentity(long binderToken, Pair<String, String> callingPackage) {
+ public CallingIdentity(long binderToken, @Nullable AttributionSource attributionSource) {
this.binderToken = binderToken;
- this.callingPackage = callingPackage;
+ this.callingAttributionSource = attributionSource;
}
}
@@ -1059,7 +1074,8 @@
*/
@SuppressWarnings("AndroidFrameworkBinderIdentity")
public final @NonNull CallingIdentity clearCallingIdentity() {
- return new CallingIdentity(Binder.clearCallingIdentity(), setCallingPackage(null));
+ return new CallingIdentity(Binder.clearCallingIdentity(),
+ setCallingAttributionSource(null));
}
/**
@@ -1071,7 +1087,7 @@
*/
public final void restoreCallingIdentity(@NonNull CallingIdentity identity) {
Binder.restoreCallingIdentity(identity.binderToken);
- mCallingPackage.set(identity.callingPackage);
+ mCallingAttributionSource.set(identity.callingAttributionSource);
}
/**
@@ -2374,7 +2390,7 @@
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
mNoPerms = testing;
- mCallingPackage = new ThreadLocal<>();
+ mCallingAttributionSource = new ThreadLocal<>();
/*
* Only allow it to be set once, so after the content service gives
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 5af7861..518e753 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -79,7 +79,8 @@
private final IContentProvider mContentProvider;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final String mPackageName;
- private final @Nullable String mAttributionTag;
+ private final @NonNull AttributionSource mAttributionSource;
+
private final String mAuthority;
private final boolean mStable;
@@ -103,7 +104,7 @@
mContentResolver = contentResolver;
mContentProvider = contentProvider;
mPackageName = contentResolver.mPackageName;
- mAttributionTag = contentResolver.mAttributionTag;
+ mAttributionSource = contentResolver.getAttributionSource();
mAuthority = authority;
mStable = stable;
@@ -193,7 +194,7 @@
cancellationSignal.setRemote(remoteCancellationSignal);
}
final Cursor cursor = mContentProvider.query(
- mPackageName, mAttributionTag, uri, projection, queryArgs,
+ mAttributionSource, uri, projection, queryArgs,
remoteCancellationSignal);
if (cursor == null) {
return null;
@@ -254,7 +255,7 @@
beforeRemote();
try {
- return mContentProvider.canonicalize(mPackageName, mAttributionTag, url);
+ return mContentProvider.canonicalize(mAttributionSource, url);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -272,7 +273,7 @@
beforeRemote();
try {
- return mContentProvider.uncanonicalize(mPackageName, mAttributionTag, url);
+ return mContentProvider.uncanonicalize(mAttributionSource, url);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -297,7 +298,7 @@
remoteCancellationSignal = mContentProvider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal);
}
- return mContentProvider.refresh(mPackageName, mAttributionTag, url, extras,
+ return mContentProvider.refresh(mAttributionSource, url, extras,
remoteCancellationSignal);
} catch (DeadObjectException e) {
if (!mStable) {
@@ -317,7 +318,7 @@
beforeRemote();
try {
- return mContentProvider.checkUriPermission(mPackageName, mAttributionTag, uri, uid,
+ return mContentProvider.checkUriPermission(mAttributionSource, uri, uid,
modeFlags);
} catch (DeadObjectException e) {
if (!mStable) {
@@ -343,7 +344,7 @@
beforeRemote();
try {
- return mContentProvider.insert(mPackageName, mAttributionTag, url, initialValues,
+ return mContentProvider.insert(mAttributionSource, url, initialValues,
extras);
} catch (DeadObjectException e) {
if (!mStable) {
@@ -364,7 +365,7 @@
beforeRemote();
try {
- return mContentProvider.bulkInsert(mPackageName, mAttributionTag, url, initialValues);
+ return mContentProvider.bulkInsert(mAttributionSource, url, initialValues);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -388,7 +389,7 @@
beforeRemote();
try {
- return mContentProvider.delete(mPackageName, mAttributionTag, url, extras);
+ return mContentProvider.delete(mAttributionSource, url, extras);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -413,7 +414,7 @@
beforeRemote();
try {
- return mContentProvider.update(mPackageName, mAttributionTag, url, values, extras);
+ return mContentProvider.update(mAttributionSource, url, values, extras);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -457,8 +458,7 @@
remoteSignal = mContentProvider.createCancellationSignal();
signal.setRemote(remoteSignal);
}
- return mContentProvider.openFile(mPackageName, mAttributionTag, url, mode,
- remoteSignal, null);
+ return mContentProvider.openFile(mAttributionSource, url, mode, remoteSignal);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -502,7 +502,7 @@
remoteSignal = mContentProvider.createCancellationSignal();
signal.setRemote(remoteSignal);
}
- return mContentProvider.openAssetFile(mPackageName, mAttributionTag, url, mode,
+ return mContentProvider.openAssetFile(mAttributionSource, url, mode,
remoteSignal);
} catch (DeadObjectException e) {
if (!mStable) {
@@ -544,7 +544,7 @@
signal.setRemote(remoteSignal);
}
return mContentProvider.openTypedAssetFile(
- mPackageName, mAttributionTag, uri, mimeTypeFilter, opts, remoteSignal);
+ mAttributionSource, uri, mimeTypeFilter, opts, remoteSignal);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -571,7 +571,7 @@
beforeRemote();
try {
- return mContentProvider.applyBatch(mPackageName, mAttributionTag, authority,
+ return mContentProvider.applyBatch(mAttributionSource, authority,
operations);
} catch (DeadObjectException e) {
if (!mStable) {
@@ -598,7 +598,7 @@
beforeRemote();
try {
- return mContentProvider.call(mPackageName, mAttributionTag, authority, method, arg,
+ return mContentProvider.call(mAttributionSource, authority, method, arg,
extras);
} catch (DeadObjectException e) {
if (!mStable) {
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index 7d121d5..47c96699 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -16,6 +16,7 @@
package android.content;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetFileDescriptor;
@@ -83,8 +84,8 @@
{
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String callingFeatureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);
// String[] projection
@@ -103,7 +104,7 @@
ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface(
data.readStrongBinder());
- Cursor cursor = query(callingPkg, callingFeatureId, url, projection, queryArgs,
+ Cursor cursor = query(attributionSource, url, projection, queryArgs,
cancellationSignal);
if (cursor != null) {
CursorToBulkCursorAdaptor adaptor = null;
@@ -158,13 +159,13 @@
case INSERT_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);
ContentValues values = ContentValues.CREATOR.createFromParcel(data);
Bundle extras = data.readBundle();
- Uri out = insert(callingPkg, featureId, url, values, extras);
+ Uri out = insert(attributionSource, url, values, extras);
reply.writeNoException();
Uri.writeToParcel(reply, out);
return true;
@@ -173,12 +174,12 @@
case BULK_INSERT_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);
ContentValues[] values = data.createTypedArray(ContentValues.CREATOR);
- int count = bulkInsert(callingPkg, featureId, url, values);
+ int count = bulkInsert(attributionSource, url, values);
reply.writeNoException();
reply.writeInt(count);
return true;
@@ -187,8 +188,8 @@
case APPLY_BATCH_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
String authority = data.readString();
final int numOperations = data.readInt();
final ArrayList<ContentProviderOperation> operations =
@@ -196,7 +197,7 @@
for (int i = 0; i < numOperations; i++) {
operations.add(i, ContentProviderOperation.CREATOR.createFromParcel(data));
}
- final ContentProviderResult[] results = applyBatch(callingPkg, featureId,
+ final ContentProviderResult[] results = applyBatch(attributionSource,
authority, operations);
reply.writeNoException();
reply.writeTypedArray(results, 0);
@@ -206,12 +207,12 @@
case DELETE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);
Bundle extras = data.readBundle();
- int count = delete(callingPkg, featureId, url, extras);
+ int count = delete(attributionSource, url, extras);
reply.writeNoException();
reply.writeInt(count);
@@ -221,13 +222,13 @@
case UPDATE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);
ContentValues values = ContentValues.CREATOR.createFromParcel(data);
Bundle extras = data.readBundle();
- int count = update(callingPkg, featureId, url, values, extras);
+ int count = update(attributionSource, url, values, extras);
reply.writeNoException();
reply.writeInt(count);
@@ -237,16 +238,15 @@
case OPEN_FILE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);
String mode = data.readString();
ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
data.readStrongBinder());
- IBinder callerToken = data.readStrongBinder();
ParcelFileDescriptor fd;
- fd = openFile(callingPkg, featureId, url, mode, signal, callerToken);
+ fd = openFile(attributionSource, url, mode, signal);
reply.writeNoException();
if (fd != null) {
reply.writeInt(1);
@@ -261,15 +261,15 @@
case OPEN_ASSET_FILE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);
String mode = data.readString();
ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
data.readStrongBinder());
AssetFileDescriptor fd;
- fd = openAssetFile(callingPkg, featureId, url, mode, signal);
+ fd = openAssetFile(attributionSource, url, mode, signal);
reply.writeNoException();
if (fd != null) {
reply.writeInt(1);
@@ -285,14 +285,14 @@
{
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
String authority = data.readString();
String method = data.readString();
String stringArg = data.readString();
Bundle extras = data.readBundle();
- Bundle responseBundle = call(callingPkg, featureId, authority, method,
+ Bundle responseBundle = call(attributionSource, authority, method,
stringArg, extras);
reply.writeNoException();
@@ -315,8 +315,8 @@
case OPEN_TYPED_ASSET_FILE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);
String mimeType = data.readString();
Bundle opts = data.readBundle();
@@ -324,7 +324,7 @@
data.readStrongBinder());
AssetFileDescriptor fd;
- fd = openTypedAssetFile(callingPkg, featureId, url, mimeType, opts, signal);
+ fd = openTypedAssetFile(attributionSource, url, mimeType, opts, signal);
reply.writeNoException();
if (fd != null) {
reply.writeInt(1);
@@ -349,11 +349,11 @@
case CANONICALIZE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);
- Uri out = canonicalize(callingPkg, featureId, url);
+ Uri out = canonicalize(attributionSource, url);
reply.writeNoException();
Uri.writeToParcel(reply, out);
return true;
@@ -361,22 +361,22 @@
case CANONICALIZE_ASYNC_TRANSACTION: {
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri uri = Uri.CREATOR.createFromParcel(data);
RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data);
- canonicalizeAsync(callingPkg, featureId, uri, callback);
+ canonicalizeAsync(attributionSource, uri, callback);
return true;
}
case UNCANONICALIZE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);
- Uri out = uncanonicalize(callingPkg, featureId, url);
+ Uri out = uncanonicalize(attributionSource, url);
reply.writeNoException();
Uri.writeToParcel(reply, out);
return true;
@@ -384,24 +384,24 @@
case UNCANONICALIZE_ASYNC_TRANSACTION: {
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri uri = Uri.CREATOR.createFromParcel(data);
RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data);
- uncanonicalizeAsync(callingPkg, featureId, uri, callback);
+ uncanonicalizeAsync(attributionSource, uri, callback);
return true;
}
case REFRESH_TRANSACTION: {
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);
Bundle extras = data.readBundle();
ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
data.readStrongBinder());
- boolean out = refresh(callingPkg, featureId, url, extras, signal);
+ boolean out = refresh(attributionSource, url, extras, signal);
reply.writeNoException();
reply.writeInt(out ? 0 : -1);
return true;
@@ -409,13 +409,13 @@
case CHECK_URI_PERMISSION_TRANSACTION: {
data.enforceInterface(IContentProvider.descriptor);
- String callingPkg = data.readString();
- String featureId = data.readString();
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri uri = Uri.CREATOR.createFromParcel(data);
int uid = data.readInt();
int modeFlags = data.readInt();
- int out = checkUriPermission(callingPkg, featureId, uri, uid, modeFlags);
+ int out = checkUriPermission(attributionSource, uri, uid, modeFlags);
reply.writeNoException();
reply.writeInt(out);
return true;
@@ -451,7 +451,7 @@
}
@Override
- public Cursor query(String callingPkg, @Nullable String featureId, Uri url,
+ public Cursor query(@NonNull AttributionSource attributionSource, Uri url,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable ICancellationSignal cancellationSignal)
throws RemoteException {
@@ -461,8 +461,7 @@
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
url.writeToParcel(data, 0);
int length = 0;
if (projection != null) {
@@ -540,7 +539,7 @@
}
@Override
- public Uri insert(String callingPkg, @Nullable String featureId, Uri url,
+ public Uri insert(@NonNull AttributionSource attributionSource, Uri url,
ContentValues values, Bundle extras) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -548,8 +547,7 @@
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
url.writeToParcel(data, 0);
values.writeToParcel(data, 0);
data.writeBundle(extras);
@@ -566,15 +564,14 @@
}
@Override
- public int bulkInsert(String callingPkg, @Nullable String featureId, Uri url,
+ public int bulkInsert(@NonNull AttributionSource attributionSource, Uri url,
ContentValues[] values) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
url.writeToParcel(data, 0);
data.writeTypedArray(values, 0);
@@ -590,15 +587,14 @@
}
@Override
- public ContentProviderResult[] applyBatch(String callingPkg, @Nullable String featureId,
+ public ContentProviderResult[] applyBatch(@NonNull AttributionSource attributionSource,
String authority, ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
data.writeString(authority);
data.writeInt(operations.size());
for (ContentProviderOperation operation : operations) {
@@ -617,15 +613,14 @@
}
@Override
- public int delete(String callingPkg, @Nullable String featureId, Uri url, Bundle extras)
+ public int delete(@NonNull AttributionSource attributionSource, Uri url, Bundle extras)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
url.writeToParcel(data, 0);
data.writeBundle(extras);
@@ -641,15 +636,14 @@
}
@Override
- public int update(String callingPkg, @Nullable String featureId, Uri url,
+ public int update(@NonNull AttributionSource attributionSource, Uri url,
ContentValues values, Bundle extras) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
url.writeToParcel(data, 0);
values.writeToParcel(data, 0);
data.writeBundle(extras);
@@ -666,20 +660,18 @@
}
@Override
- public ParcelFileDescriptor openFile(String callingPkg, @Nullable String featureId, Uri url,
- String mode, ICancellationSignal signal, IBinder token)
+ public ParcelFileDescriptor openFile(@NonNull AttributionSource attributionSource, Uri url,
+ String mode, ICancellationSignal signal)
throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
url.writeToParcel(data, 0);
data.writeString(mode);
data.writeStrongBinder(signal != null ? signal.asBinder() : null);
- data.writeStrongBinder(token);
mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0);
@@ -695,7 +687,7 @@
}
@Override
- public AssetFileDescriptor openAssetFile(String callingPkg, @Nullable String featureId,
+ public AssetFileDescriptor openAssetFile(@NonNull AttributionSource attributionSource,
Uri url, String mode, ICancellationSignal signal)
throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
@@ -703,8 +695,7 @@
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
url.writeToParcel(data, 0);
data.writeString(mode);
data.writeStrongBinder(signal != null ? signal.asBinder() : null);
@@ -723,15 +714,14 @@
}
@Override
- public Bundle call(String callingPkg, @Nullable String featureId, String authority,
+ public Bundle call(@NonNull AttributionSource attributionSource, String authority,
String method, String request, Bundle extras) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
data.writeString(authority);
data.writeString(method);
data.writeString(request);
@@ -771,7 +761,7 @@
}
@Override
- public AssetFileDescriptor openTypedAssetFile(String callingPkg, @Nullable String featureId,
+ public AssetFileDescriptor openTypedAssetFile(@NonNull AttributionSource attributionSource,
Uri url, String mimeType, Bundle opts, ICancellationSignal signal)
throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
@@ -779,8 +769,7 @@
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
url.writeToParcel(data, 0);
data.writeString(mimeType);
data.writeBundle(opts);
@@ -820,15 +809,14 @@
}
@Override
- public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri url)
+ public Uri canonicalize(@NonNull AttributionSource attributionSource, Uri url)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
url.writeToParcel(data, 0);
mRemote.transact(IContentProvider.CANONICALIZE_TRANSACTION, data, reply, 0);
@@ -843,14 +831,13 @@
}
@Override
- /* oneway */ public void canonicalizeAsync(String callingPkg, @Nullable String featureId,
+ /* oneway */ public void canonicalizeAsync(@NonNull AttributionSource attributionSource,
Uri uri, RemoteCallback callback) throws RemoteException {
Parcel data = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
uri.writeToParcel(data, 0);
callback.writeToParcel(data, 0);
@@ -862,15 +849,14 @@
}
@Override
- public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri url)
+ public Uri uncanonicalize(@NonNull AttributionSource attributionSource, Uri url)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
url.writeToParcel(data, 0);
mRemote.transact(IContentProvider.UNCANONICALIZE_TRANSACTION, data, reply, 0);
@@ -885,14 +871,13 @@
}
@Override
- /* oneway */ public void uncanonicalizeAsync(String callingPkg, @Nullable String featureId,
+ /* oneway */ public void uncanonicalizeAsync(@NonNull AttributionSource attributionSource,
Uri uri, RemoteCallback callback) throws RemoteException {
Parcel data = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
uri.writeToParcel(data, 0);
callback.writeToParcel(data, 0);
@@ -904,15 +889,14 @@
}
@Override
- public boolean refresh(String callingPkg, @Nullable String featureId, Uri url, Bundle extras,
+ public boolean refresh(@NonNull AttributionSource attributionSource, Uri url, Bundle extras,
ICancellationSignal signal) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
url.writeToParcel(data, 0);
data.writeBundle(extras);
data.writeStrongBinder(signal != null ? signal.asBinder() : null);
@@ -929,15 +913,14 @@
}
@Override
- public int checkUriPermission(String callingPkg, @Nullable String featureId, Uri url, int uid,
+ public int checkUriPermission(@NonNull AttributionSource attributionSource, Uri url, int uid,
int modeFlags) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeString(callingPkg);
- data.writeString(featureId);
+ attributionSource.writeToParcel(data, 0);
url.writeToParcel(data, 0);
data.writeInt(uid);
data.writeInt(modeFlags);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 8ea417f..14b2a65 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -816,7 +816,6 @@
public ContentResolver(@Nullable Context context, @Nullable ContentInterface wrapped) {
mContext = context != null ? context : ActivityThread.currentApplication();
mPackageName = mContext.getOpPackageName();
- mAttributionTag = mContext.getAttributionTag();
mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
mWrapped = wrapped;
}
@@ -1217,7 +1216,7 @@
cancellationSignal.setRemote(remoteCancellationSignal);
}
try {
- qCursor = unstableProvider.query(mPackageName, mAttributionTag, uri, projection,
+ qCursor = unstableProvider.query(mContext.getAttributionSource(), uri, projection,
queryArgs, remoteCancellationSignal);
} catch (DeadObjectException e) {
// The remote process has died... but we only hold an unstable
@@ -1228,7 +1227,7 @@
if (stableProvider == null) {
return null;
}
- qCursor = stableProvider.query(mPackageName, mAttributionTag, uri, projection,
+ qCursor = stableProvider.query(mContext.getAttributionSource(), uri, projection,
queryArgs, remoteCancellationSignal);
}
if (qCursor == null) {
@@ -1320,7 +1319,7 @@
try {
final UriResultListener resultListener = new UriResultListener();
- provider.canonicalizeAsync(mPackageName, mAttributionTag, url,
+ provider.canonicalizeAsync(mContext.getAttributionSource(), url,
new RemoteCallback(resultListener));
resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS);
if (resultListener.exception != null) {
@@ -1371,7 +1370,7 @@
try {
final UriResultListener resultListener = new UriResultListener();
- provider.uncanonicalizeAsync(mPackageName, mAttributionTag, url,
+ provider.uncanonicalizeAsync(mContext.getAttributionSource(), url,
new RemoteCallback(resultListener));
resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS);
if (resultListener.exception != null) {
@@ -1429,7 +1428,7 @@
remoteCancellationSignal = provider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal);
}
- return provider.refresh(mPackageName, mAttributionTag, url, extras,
+ return provider.refresh(mContext.getAttributionSource(), url, extras,
remoteCancellationSignal);
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
@@ -1858,7 +1857,7 @@
try {
fd = unstableProvider.openAssetFile(
- mPackageName, mAttributionTag, uri, mode,
+ mContext.getAttributionSource(), uri, mode,
remoteCancellationSignal);
if (fd == null) {
// The provider will be released by the finally{} clause
@@ -1873,8 +1872,8 @@
if (stableProvider == null) {
throw new FileNotFoundException("No content provider: " + uri);
}
- fd = stableProvider.openAssetFile(
- mPackageName, mAttributionTag, uri, mode, remoteCancellationSignal);
+ fd = stableProvider.openAssetFile(mContext.getAttributionSource(),
+ uri, mode, remoteCancellationSignal);
if (fd == null) {
// The provider will be released by the finally{} clause
return null;
@@ -2025,7 +2024,7 @@
try {
fd = unstableProvider.openTypedAssetFile(
- mPackageName, mAttributionTag, uri, mimeType, opts,
+ mContext.getAttributionSource(), uri, mimeType, opts,
remoteCancellationSignal);
if (fd == null) {
// The provider will be released by the finally{} clause
@@ -2041,7 +2040,7 @@
throw new FileNotFoundException("No content provider: " + uri);
}
fd = stableProvider.openTypedAssetFile(
- mPackageName, mAttributionTag, uri, mimeType, opts,
+ mContext.getAttributionSource(), uri, mimeType, opts,
remoteCancellationSignal);
if (fd == null) {
// The provider will be released by the finally{} clause
@@ -2190,7 +2189,7 @@
}
try {
long startTime = SystemClock.uptimeMillis();
- Uri createdRow = provider.insert(mPackageName, mAttributionTag, url, values, extras);
+ Uri createdRow = provider.insert(mContext.getAttributionSource(), url, values, extras);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);
return createdRow;
@@ -2271,7 +2270,7 @@
}
try {
long startTime = SystemClock.uptimeMillis();
- int rowsCreated = provider.bulkInsert(mPackageName, mAttributionTag, url, values);
+ int rowsCreated = provider.bulkInsert(mContext.getAttributionSource(), url, values);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "bulkinsert", null /* where */);
return rowsCreated;
@@ -2330,7 +2329,7 @@
}
try {
long startTime = SystemClock.uptimeMillis();
- int rowsDeleted = provider.delete(mPackageName, mAttributionTag, url, extras);
+ int rowsDeleted = provider.delete(mContext.getAttributionSource(), url, extras);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "delete", null);
return rowsDeleted;
@@ -2397,7 +2396,8 @@
}
try {
long startTime = SystemClock.uptimeMillis();
- int rowsUpdated = provider.update(mPackageName, mAttributionTag, uri, values, extras);
+ int rowsUpdated = provider.update(mContext.getAttributionSource(),
+ uri, values, extras);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, uri, "update", null);
return rowsUpdated;
@@ -2446,8 +2446,8 @@
throw new IllegalArgumentException("Unknown authority " + authority);
}
try {
- final Bundle res = provider.call(mPackageName, mAttributionTag, authority, method, arg,
- extras);
+ final Bundle res = provider.call(mContext.getAttributionSource(),
+ authority, method, arg, extras);
Bundle.setDefusable(res, true);
return res;
} catch (RemoteException e) {
@@ -3866,12 +3866,17 @@
/** @hide */
@UnsupportedAppUsage
public String getPackageName() {
- return mPackageName;
+ return mContext.getOpPackageName();
}
/** @hide */
public @Nullable String getAttributionTag() {
- return mAttributionTag;
+ return mContext.getAttributionTag();
+ }
+
+ /** @hide */
+ public @NonNull AttributionSource getAttributionSource() {
+ return mContext.getAttributionSource();
}
@UnsupportedAppUsage
@@ -3881,7 +3886,6 @@
@UnsupportedAppUsage
final String mPackageName;
- final @Nullable String mAttributionTag;
final int mTargetSdkVersion;
final ContentInterface mWrapped;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6ff296c..8531d34 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -889,6 +889,15 @@
return null;
}
+ /**
+ * @return The identity of this context for permission purposes.
+ *
+ * @see AttributionSource
+ */
+ public @NonNull AttributionSource getAttributionSource() {
+ return null;
+ }
+
// TODO moltmann: Remove
/**
* @removed
@@ -6465,8 +6474,10 @@
* @removed
*/
@Deprecated
- public @NonNull Context createFeatureContext(@Nullable String featureId) {
- return createAttributionContext(featureId);
+ public @NonNull Context createFeatureContext(@Nullable String attributionTag) {
+ return createContext(new ContextParams.Builder()
+ .setAttributionTag(attributionTag)
+ .build());
}
/**
diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java
index fad905b..2b2db8f 100644
--- a/core/java/android/content/ContextParams.java
+++ b/core/java/android/content/ContextParams.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import java.util.Collections;
@@ -38,36 +37,26 @@
* is an arbitrary string your app specifies for the purposes of tracking permission
* accesses from a given portion of your app; against another package and optionally
* its attribution tag if you are accessing the data on behalf of another app and
- * you will be passing that data to this app. Both attributions are not mutually
- * exclusive.
- *
- * <p>For example if you have a feature "foo" in your app which accesses
- * permissions on behalf of app "foo.bar.baz" with feature "bar" you need to
- * create a context like this:
- *
- * <pre class="prettyprint">
- * context.createContext(new ContextParams.Builder()
- * .setAttributionTag("foo")
- * .setReceiverPackage("foo.bar.baz", "bar")
- * .build())
- * </pre>
+ * you will be passing that data to this app, recursively. Both attributions are
+ * not mutually exclusive.
*
* @see Context#createContext(ContextParams)
+ * @see AttributionSource
*/
public final class ContextParams {
- private final String mAttributionTag;
- private final String mReceiverPackage;
- private final String mReceiverAttributionTag;
- private final Set<String> mRenouncedPermissions;
+ private final @Nullable String mAttributionTag;
+ private final @Nullable AttributionSource mNext;
+ private final @NonNull Set<String> mRenouncedPermissions;
/** {@hide} */
public static final ContextParams EMPTY = new ContextParams.Builder().build();
- private ContextParams(@NonNull ContextParams.Builder builder) {
- mAttributionTag = builder.mAttributionTag;
- mReceiverPackage = builder.mReceiverPackage;
- mReceiverAttributionTag = builder.mReceiverAttributionTag;
- mRenouncedPermissions = builder.mRenouncedPermissions;
+ private ContextParams(@Nullable String attributionTag,
+ @Nullable AttributionSource next,
+ @NonNull Set<String> renouncedPermissions) {
+ mAttributionTag = attributionTag;
+ mNext = next;
+ mRenouncedPermissions = renouncedPermissions;
}
/**
@@ -79,45 +68,35 @@
}
/**
- * @return The receiving package.
- */
- @Nullable
- public String getReceiverPackage() {
- return mReceiverPackage;
- }
-
- /**
- * @return The receiving package's attribution tag.
- */
- @Nullable
- public String getReceiverAttributionTag() {
- return mReceiverAttributionTag;
- }
-
- /**
* @return The set of permissions to treat as renounced.
* @hide
*/
@SystemApi
- @SuppressLint("NullableCollection")
@RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
- public @Nullable Set<String> getRenouncedPermissions() {
+ public @NonNull Set<String> getRenouncedPermissions() {
return mRenouncedPermissions;
}
/** @hide */
public boolean isRenouncedPermission(@NonNull String permission) {
- return mRenouncedPermissions != null && mRenouncedPermissions.contains(permission);
+ return mRenouncedPermissions.contains(permission);
+ }
+
+ /**
+ * @return The receiving attribution source.
+ */
+ @Nullable
+ public AttributionSource getNextAttributionSource() {
+ return mNext;
}
/**
* Builder for creating a {@link ContextParams}.
*/
public static final class Builder {
- private String mAttributionTag;
- private String mReceiverPackage;
- private String mReceiverAttributionTag;
- private Set<String> mRenouncedPermissions;
+ private @Nullable String mAttributionTag;
+ private @NonNull Set<String> mRenouncedPermissions = Collections.emptySet();
+ private @Nullable AttributionSource mNext;
/**
* Create a new builder.
@@ -145,9 +124,8 @@
public Builder(@NonNull ContextParams params) {
Objects.requireNonNull(params);
mAttributionTag = params.mAttributionTag;
- mReceiverPackage = params.mReceiverPackage;
- mReceiverAttributionTag = params.mReceiverAttributionTag;
mRenouncedPermissions = params.mRenouncedPermissions;
+ mNext = params.mNext;
}
/**
@@ -163,18 +141,16 @@
}
/**
- * Sets the package and its optional attribution tag that would be receiving
- * the permission protected data.
+ * Sets the attribution source for the app on whose behalf you are doing the work.
*
- * @param packageName The package name receiving the permission protected data.
- * @param attributionTag An attribution tag of the receiving package.
+ * @param next The permission identity of the receiving app.
* @return This builder.
+ *
+ * @see AttributionSource
*/
@NonNull
- public Builder setReceiverPackage(@Nullable String packageName,
- @Nullable String attributionTag) {
- mReceiverPackage = packageName;
- mReceiverAttributionTag = attributionTag;
+ public Builder setNextAttributionSource(@NonNull AttributionSource next) {
+ mNext = Objects.requireNonNull(next);
return this;
}
@@ -194,19 +170,16 @@
* permissions are supported by this mechanism.
*
* @param renouncedPermissions The set of permissions to treat as
- * renounced.
+ * renounced, which is as if not granted.
* @return This builder.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
public @NonNull Builder setRenouncedPermissions(
- @Nullable Set<String> renouncedPermissions) {
- if (renouncedPermissions != null) {
- mRenouncedPermissions = Collections.unmodifiableSet(renouncedPermissions);
- } else {
- mRenouncedPermissions = null;
- }
+ @NonNull Set<String> renouncedPermissions) {
+ mRenouncedPermissions = Collections.unmodifiableSet(
+ Objects.requireNonNull(renouncedPermissions));
return this;
}
@@ -217,7 +190,8 @@
*/
@NonNull
public ContextParams build() {
- return new ContextParams(this);
+ return new ContextParams(mAttributionTag, mNext,
+ mRenouncedPermissions);
}
}
}
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 609f417..de0d65f 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -1060,6 +1060,12 @@
return mBase.createAttributionContext(attributionTag);
}
+ @NonNull
+ @Override
+ public AttributionSource getAttributionSource() {
+ return mBase.getAttributionSource();
+ }
+
@Override
public boolean isRestricted() {
return mBase.isRestricted();
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 9210b13..e0315a3 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -16,11 +16,13 @@
package android.content;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -38,11 +40,11 @@
* @hide
*/
public interface IContentProvider extends IInterface {
- public Cursor query(String callingPkg, @Nullable String attributionTag, Uri url,
+ Cursor query(@NonNull AttributionSource attributionSource, Uri url,
@Nullable String[] projection,
@Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal)
throws RemoteException;
- public String getType(Uri url) throws RemoteException;
+ String getType(Uri url) throws RemoteException;
/**
* A oneway version of getType. The functionality is exactly the same, except that the
@@ -55,54 +57,56 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ "ContentProviderClient#insert(android.net.Uri, android.content.ContentValues)} "
+ "instead")
- public default Uri insert(String callingPkg, Uri url, ContentValues initialValues)
+ default Uri insert(String callingPkg, Uri url, ContentValues initialValues)
throws RemoteException {
- return insert(callingPkg, null, url, initialValues, null);
+ return insert(new AttributionSource(Binder.getCallingUid(), callingPkg, null),
+ url, initialValues, null);
}
- public Uri insert(String callingPkg, String attributionTag, Uri url,
+ Uri insert(@NonNull AttributionSource attributionSource, Uri url,
ContentValues initialValues, Bundle extras) throws RemoteException;
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ "ContentProviderClient#bulkInsert(android.net.Uri, android.content.ContentValues[])"
+ "} instead")
- public default int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues)
+ default int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues)
throws RemoteException {
- return bulkInsert(callingPkg, null, url, initialValues);
+ return bulkInsert(new AttributionSource(Binder.getCallingUid(), callingPkg, null),
+ url, initialValues);
}
- public int bulkInsert(String callingPkg, String attributionTag, Uri url,
+ int bulkInsert(@NonNull AttributionSource attributionSource, Uri url,
ContentValues[] initialValues) throws RemoteException;
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ "ContentProviderClient#delete(android.net.Uri, java.lang.String, java.lang"
+ ".String[])} instead")
- public default int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
+ default int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
throws RemoteException {
- return delete(callingPkg, null, url,
- ContentResolver.createSqlQueryBundle(selection, selectionArgs));
+ return delete(new AttributionSource(Binder.getCallingUid(), callingPkg, null),
+ url, ContentResolver.createSqlQueryBundle(selection, selectionArgs));
}
- public int delete(String callingPkg, String attributionTag, Uri url, Bundle extras)
+ int delete(@NonNull AttributionSource attributionSource, Uri url, Bundle extras)
throws RemoteException;
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ "ContentProviderClient#update(android.net.Uri, android.content.ContentValues, java"
+ ".lang.String, java.lang.String[])} instead")
- public default int update(String callingPkg, Uri url, ContentValues values, String selection,
+ default int update(String callingPkg, Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException {
- return update(callingPkg, null, url, values,
- ContentResolver.createSqlQueryBundle(selection, selectionArgs));
+ return update(new AttributionSource(Binder.getCallingUid(), callingPkg, null),
+ url, values, ContentResolver.createSqlQueryBundle(selection, selectionArgs));
}
- public int update(String callingPkg, String attributionTag, Uri url, ContentValues values,
+ int update(@NonNull AttributionSource attributionSource, Uri url, ContentValues values,
Bundle extras) throws RemoteException;
- public ParcelFileDescriptor openFile(String callingPkg, @Nullable String attributionTag,
- Uri url, String mode, ICancellationSignal signal, IBinder callerToken)
- throws RemoteException, FileNotFoundException;
-
- public AssetFileDescriptor openAssetFile(String callingPkg, @Nullable String attributionTag,
+ ParcelFileDescriptor openFile(@NonNull AttributionSource attributionSource,
Uri url, String mode, ICancellationSignal signal)
throws RemoteException, FileNotFoundException;
- public ContentProviderResult[] applyBatch(String callingPkg, @Nullable String attributionTag,
+ AssetFileDescriptor openAssetFile(@NonNull AttributionSource attributionSource,
+ Uri url, String mode, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException;
+
+ ContentProviderResult[] applyBatch(@NonNull AttributionSource attributionSource,
String authority, ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException;
@@ -112,18 +116,19 @@
+ "instead")
public default Bundle call(String callingPkg, String method,
@Nullable String arg, @Nullable Bundle extras) throws RemoteException {
- return call(callingPkg, null, "unknown", method, arg, extras);
+ return call(new AttributionSource(Binder.getCallingUid(), callingPkg, null),
+ "unknown", method, arg, extras);
}
- public Bundle call(String callingPkg, @Nullable String attributionTag, String authority,
+ Bundle call(@NonNull AttributionSource attributionSource, String authority,
String method, @Nullable String arg, @Nullable Bundle extras) throws RemoteException;
- public int checkUriPermission(String callingPkg, @Nullable String attributionTag, Uri uri,
+ int checkUriPermission(@NonNull AttributionSource attributionSource, Uri uri,
int uid, int modeFlags) throws RemoteException;
- public ICancellationSignal createCancellationSignal() throws RemoteException;
+ ICancellationSignal createCancellationSignal() throws RemoteException;
- public Uri canonicalize(String callingPkg, @Nullable String attributionTag, Uri uri)
+ Uri canonicalize(@NonNull AttributionSource attributionSource, Uri uri)
throws RemoteException;
/**
@@ -131,10 +136,10 @@
* call returns immediately, and the resulting type is returned when available via
* a binder callback.
*/
- void canonicalizeAsync(String callingPkg, @Nullable String attributionTag, Uri uri,
+ void canonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri,
RemoteCallback callback) throws RemoteException;
- public Uri uncanonicalize(String callingPkg, @Nullable String attributionTag, Uri uri)
+ Uri uncanonicalize(@NonNull AttributionSource attributionSource, Uri uri)
throws RemoteException;
/**
@@ -142,18 +147,17 @@
* call returns immediately, and the resulting type is returned when available via
* a binder callback.
*/
- void uncanonicalizeAsync(String callingPkg, @Nullable String attributionTag, Uri uri,
+ void uncanonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri,
RemoteCallback callback) throws RemoteException;
- public boolean refresh(String callingPkg, @Nullable String attributionTag, Uri url,
+ public boolean refresh(@NonNull AttributionSource attributionSource, Uri url,
@Nullable Bundle extras, ICancellationSignal cancellationSignal) throws RemoteException;
// Data interchange.
public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
- public AssetFileDescriptor openTypedAssetFile(String callingPkg,
- @Nullable String attributionTag, Uri url, String mimeType, Bundle opts,
- ICancellationSignal signal)
+ public AssetFileDescriptor openTypedAssetFile(@NonNull AttributionSource attributionSource,
+ Uri url, String mimeType, Bundle opts, ICancellationSignal signal)
throws RemoteException, FileNotFoundException;
/* IPC constants */
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 159db92..08eac5a 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -27,6 +27,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* This class provides permission check APIs that verify both the
@@ -68,8 +70,14 @@
* @hide
*/
public final class PermissionChecker {
+ private static final String PLATFORM_PACKAGE_NAME = "android";
+
/** The permission is granted. */
- public static final int PERMISSION_GRANTED = PackageManager.PERMISSION_GRANTED;
+ public static final int PERMISSION_GRANTED = AppOpsManager.MODE_ALLOWED;
+
+ /** Only for runtime permissions, its returned when the runtime permission
+ * is granted, but the corresponding app op is denied. */
+ public static final int PERMISSION_SOFT_DENIED = AppOpsManager.MODE_IGNORED;
/** Returned when:
* <ul>
@@ -79,15 +87,14 @@
* </ul>
*
*/
- public static final int PERMISSION_HARD_DENIED = PackageManager.PERMISSION_DENIED;
-
- /** Only for runtime permissions, its returned when the runtime permission
- * is granted, but the corresponding app op is denied. */
- public static final int PERMISSION_SOFT_DENIED = PackageManager.PERMISSION_DENIED - 1;
+ public static final int PERMISSION_HARD_DENIED = AppOpsManager.MODE_ERRORED;
/** Constant when the PID for which we check permissions is unknown. */
public static final int PID_UNKNOWN = -1;
+ private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions
+ = new ConcurrentHashMap<>();
+
/** @hide */
@IntDef({PERMISSION_GRANTED,
PERMISSION_SOFT_DENIED,
@@ -131,6 +138,50 @@
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
* or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
* @param message A message describing the reason the permission was checked
+ * @param startDataDelivery Whether this is the start of data delivery.
+ *
+ * @see #checkPermissionForPreflight(Context, String, int, int, String)
+ */
+ @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) {
+ return checkPermissionForDataDelivery(context, permission, pid, new AttributionSource(uid,
+ packageName, attributionTag), message, startDataDelivery);
+ }
+
+ /**
+ * Checks whether a given package in a UID and PID has a given permission
+ * and whether the app op that corresponds to this permission is allowed.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkPermissionForPreflight(Context, String, int, int, String)}
+ * to determine if the app has or may have location permission (if app has only foreground
+ * location the grant state depends on the app's fg/gb state) and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the location data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * <p>For more details how to determine the {@code packageName}, {@code attributionTag}, and
+ * {@code message}, please check the description in
+ * {@link AppOpsManager#noteOp(String, int, String, String, String)}
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID
+ * is not known.
+ * @param uid The uid for which to check.
+ * @param packageName The package name for which to check. If null the
+ * the first package for the calling UID will be used.
+ * @param attributionTag attribution tag
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ * @param message A message describing the reason the permission was checked
*
* @see #checkPermissionForPreflight(Context, String, int, int, String)
*/
@@ -138,8 +189,303 @@
public static int checkPermissionForDataDelivery(@NonNull Context context,
@NonNull String permission, int pid, int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable String message) {
- return checkPermissionCommon(context, permission, pid, uid, packageName, attributionTag,
- message, true /*forDataDelivery*/);
+ return checkPermissionForDataDelivery(context, permission, pid, uid,
+ packageName, attributionTag, message, false /*startDataDelivery*/);
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has a given permission and whether the app op that corresponds to this permission
+ * is allowed. Call this method if you are the datasource which would not blame you for
+ * access to the data since you are the data.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkPermissionForPreflight(Context, String, int, int, String)}
+ * to determine if the app has or may have location permission (if app has only foreground
+ * location the grant state depends on the app's fg/gb state) and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the location data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID
+ * is not known.
+ * @param attributionSource the permission identity
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ * @param message A message describing the reason the permission was checked
+ *
+ * @see #checkPermissionForPreflight(Context, String, AttributionSource)
+ */
+ @PermissionResult
+ public static int checkPermissionForDataDeliveryFromDataSource(@NonNull Context context,
+ @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
+ @Nullable String message) {
+ return checkPermissionForDataDeliveryCommon(context, permission, pid, attributionSource,
+ message, false /*startDataDelivery*/, /*fromDatasource*/ true);
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has a given permission and whether the app op that corresponds to this permission
+ * is allowed.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkPermissionForPreflight(Context, String, AttributionSource)}
+ * to determine if the app has or may have location permission (if app has only foreground
+ * location the grant state depends on the app's fg/gb state) and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the location data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID
+ * is not known.
+ * @param attributionSource the permission identity
+ * @param message A message describing the reason the permission was checked
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkPermissionForPreflight(Context, String, AttributionSource)
+ */
+ @PermissionResult
+ public static int checkPermissionForDataDelivery(@NonNull Context context,
+ @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
+ @Nullable String message) {
+ return checkPermissionForDataDelivery(context, permission, pid, attributionSource,
+ message, false /*startDataDelivery*/);
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has a given permission and whether the app op that corresponds to this permission
+ * is allowed.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a data listener it should have the required
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkPermissionForPreflight(Context, String,
+ * AttributionSource)}
+ * to determine if the app has or may have permission and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID
+ * is not known.
+ * @param attributionSource The identity for which to check the permission.
+ * @param message A message describing the reason the permission was checked
+ * @param startDataDelivery Whether this is the start of data delivery.
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkPermissionForPreflight(Context, String, AttributionSource)
+ */
+ @PermissionResult
+ public static int checkPermissionForDataDelivery(@NonNull Context context,
+ @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
+ @Nullable String message, boolean startDataDelivery) {
+ return checkPermissionForDataDeliveryCommon(context, permission, pid, attributionSource,
+ message, startDataDelivery, /*fromDatasource*/ false);
+ }
+
+ private static int checkPermissionForDataDeliveryCommon(@NonNull Context context,
+ @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
+ @Nullable String message, boolean startDataDelivery, boolean fromDatasource) {
+ // If the check failed in the middle of the chain, finish any started op.
+ final int result = checkPermissionCommon(context, permission, attributionSource,
+ message, true /*forDataDelivery*/, startDataDelivery, fromDatasource);
+ if (startDataDelivery && result != PERMISSION_GRANTED) {
+ finishDataDelivery(context, AppOpsManager.permissionToOp(permission),
+ attributionSource);
+ }
+ return result;
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has a given permission and whether the app op that corresponds to this permission
+ * is allowed. The app ops area also marked as started. This is useful for long running
+ * permissions like camera.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a data listener it should have the required
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkPermissionForPreflight(Context, String,
+ * AttributionSource)}
+ * to determine if the app has or may have permission and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param attributionSource The identity for which to check the permission.
+ * @param message A message describing the reason the permission was checked
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkPermissionForPreflight(Context, String, AttributionSource)
+ */
+ @PermissionResult
+ public static int checkPermissionAndStartDataDelivery(@NonNull Context context,
+ @NonNull String permission, @NonNull AttributionSource attributionSource,
+ @Nullable String message) {
+ return checkPermissionCommon(context, permission, attributionSource,
+ message, true /*forDataDelivery*/, /*startDataDelivery*/ true,
+ /*fromDatasource*/ false);
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link
+ * AttributionSource} has a given app op allowed and marks the op as started.
+ *
+ * <strong>NOTE:</strong> Use this method only for app op checks at the
+ * point where you will deliver the protected data to clients.
+ *
+ * <p>For example, if an app registers a data listener it should have the data
+ * op but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkOpForPreflight(Context, String, AttributionSource, String)}
+ * to determine if the app has or may have op access and this check will not
+ * leave a trace that op protected data was delivered. When you are about to
+ * deliver the data to a registered listener you should use this method which
+ * will evaluate the op access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param opName THe op to start.
+ * @param attributionSource The identity for which to check the permission.
+ * @param message A message describing the reason the permission was checked
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #finishDataDelivery(Context, String, AttributionSource)
+ */
+ @PermissionResult
+ public static int startOpForDataDelivery(@NonNull Context context,
+ @NonNull String opName, @NonNull AttributionSource attributionSource,
+ @Nullable String message) {
+ final int result = checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
+ message, true /*forDataDelivery*/, true /*startDataDelivery*/);
+ // It is important to finish any started op if some step in the attribution chain failed.
+ if (result != PERMISSION_GRANTED) {
+ finishDataDelivery(context, opName, attributionSource);
+ }
+ return result;
+ }
+
+ /**
+ * Finishes an ongoing op for data access chain described by the given {@link
+ * AttributionSource}.
+ *
+ * @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)
+ */
+ public static void finishDataDelivery(@NonNull Context context, @NonNull String op,
+ @NonNull AttributionSource attributionSource) {
+ if (op == null || attributionSource.getPackageName() == null) {
+ return;
+ }
+
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ appOpsManager.finishProxyOp(op, attributionSource);
+
+ if (attributionSource.getNext() != null) {
+ finishDataDelivery(context, op, attributionSource.getNext());
+ }
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link
+ * AttributionSource} has a given app op allowed.
+ *
+ * <strong>NOTE:</strong> Use this method only for op checks at the
+ * preflight point where you will not deliver the protected data
+ * to clients but schedule a data delivery, apps register listeners,
+ * etc.
+ *
+ * <p>For example, if an app registers a data listener it should have the op
+ * but no data is actually sent to the app at the moment of registration
+ * and you should use this method to determine if the app has or may have data
+ * access and this check will not leave a trace that protected data
+ * was delivered. When you are about to deliver the data to a registered
+ * listener you should use {@link #checkOpForDataDelivery(Context, String,
+ * AttributionSource, String)} which will evaluate the op access based
+ * on the current fg/bg state of the app and leave a record that the data was
+ * accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param opName The op to check.
+ * @param attributionSource The identity for which to check the permission.
+ * @param message A message describing the reason the permission was checked
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkOpForDataDelivery(Context, String, AttributionSource, String)
+ */
+ @PermissionResult
+ public static int checkOpForPreflight(@NonNull Context context,
+ @NonNull String opName, @NonNull AttributionSource attributionSource,
+ @Nullable String message) {
+ return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
+ message, false /*forDataDelivery*/, false /*startDataDelivery*/);
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has an allowed app op.
+ *
+ * <strong>NOTE:</strong> Use this method only for op checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a data listener it should have the data
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkOpForPreflight(Context, String, AttributionSource, String)}
+ * to determine if the app has or may have data access and this check will not
+ * leave a trace that op protected data was delivered. When you are about to
+ * deliver the data to a registered listener you should use this method which
+ * will evaluate the op access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param opName The op to check.
+ * @param attributionSource The identity for which to check the op.
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ * @param message A message describing the reason the permission was checked
+ *
+ * @see #checkOpForPreflight(Context, String, AttributionSource, String)
+ */
+ @PermissionResult
+ public static int checkOpForDataDelivery(@NonNull Context context,
+ @NonNull String opName, @NonNull AttributionSource attributionSource,
+ @Nullable String message) {
+ return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
+ message, true /*forDataDelivery*/, false /*startDataDelivery*/);
}
/**
@@ -158,8 +504,8 @@
* fg/gb state) and this check will not leave a trace that permission protected data
* was delivered. When you are about to deliver the location data to a registered
* listener you should use {@link #checkPermissionForDataDelivery(Context, String,
- * int, int, String, String)} which will evaluate the permission access based on the current
- * fg/bg state of the app and leave a record that the data was accessed.
+ * int, int, String, String, String)} which will evaluate the permission access based
+ * on the currentfg/bg state of the app and leave a record that the data was accessed.
*
* @param context Context for accessing resources.
* @param permission The permission to check.
@@ -170,13 +516,49 @@
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
* or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
*
- * @see #checkPermissionForDataDelivery(Context, String, int, int, String, String)
+ * @see #checkPermissionForDataDelivery(Context, String, int, int, String, String, String)
*/
@PermissionResult
public static int checkPermissionForPreflight(@NonNull Context context,
@NonNull String permission, int pid, int uid, @Nullable String packageName) {
- return checkPermissionCommon(context, permission, pid, uid, packageName,
- null /*attributionTag*/, null /*message*/, false /*forDataDelivery*/);
+ return checkPermissionForPreflight(context, permission, new AttributionSource(
+ uid, packageName, null /*attributionTag*/));
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has a given permission and whether the app op that corresponds to this permission
+ * is allowed.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * preflight point where you will not deliver the permission protected data
+ * to clients but schedule permission data delivery, apps register listeners,
+ * etc.
+ *
+ * <p>For example, if an app registers a data listener it should have the required
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use this method to determine if the app has or may have the
+ * permission and this check will not leave a trace that permission protected data
+ * was delivered. When you are about to deliver the protected data to a registered
+ * listener you should use {@link #checkPermissionForDataDelivery(Context, String,
+ * int, AttributionSource, String, boolean)} which will evaluate the permission access based
+ * on the current fg/bg state of the app and leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param attributionSource The identity for which to check the permission.
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkPermissionForDataDelivery(Context, String, int, AttributionSource,
+ * String, boolean)
+ */
+ @PermissionResult
+ public static int checkPermissionForPreflight(@NonNull Context context,
+ @NonNull String permission, @NonNull AttributionSource attributionSource) {
+ return checkPermissionCommon(context, permission, attributionSource,
+ null /*message*/, false /*forDataDelivery*/, /*startDataDelivery*/ false,
+ /*fromDatasource*/ false);
}
/**
@@ -211,7 +593,8 @@
public static int checkSelfPermissionForDataDelivery(@NonNull Context context,
@NonNull String permission, @Nullable String message) {
return checkPermissionForDataDelivery(context, permission, Process.myPid(),
- Process.myUid(), context.getPackageName(), context.getAttributionTag(), message);
+ Process.myUid(), context.getPackageName(), context.getAttributionTag(), message,
+ /*startDataDelivery*/ false);
}
/**
@@ -289,7 +672,8 @@
return PERMISSION_HARD_DENIED;
}
return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(),
- Binder.getCallingUid(), callingPackageName, callingAttributionTag, message);
+ Binder.getCallingUid(), callingPackageName, callingAttributionTag, message,
+ /*startDataDelivery*/ false);
}
/**
@@ -308,8 +692,8 @@
* fg/gb state) and this check will not leave a trace that permission protected data
* was delivered. When you are about to deliver the location data to a registered
* listener you should use {@link #checkCallingOrSelfPermissionForDataDelivery(Context,
- * String, String)} which will evaluate the permission access based on the current fg/bg state
- * of the app and leave a record that the data was accessed.
+ * String, String, String, String)} which will evaluate the permission access based on the
+ * current fg/bg stateof the app and leave a record that the data was accessed.
*
* @param context Context for accessing resources.
* @param permission The permission to check.
@@ -318,7 +702,7 @@
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
* or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
*
- * @see #checkCallingPermissionForDataDelivery(Context, String, String, String)
+ * @see #checkCallingPermissionForDataDelivery(Context, String, String, String, String)
*/
@PermissionResult
public static int checkCallingPermissionForPreflight(@NonNull Context context,
@@ -370,7 +754,8 @@
callingAttributionTag = (Binder.getCallingPid() == Process.myPid())
? context.getAttributionTag() : callingAttributionTag;
return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(),
- Binder.getCallingUid(), callingPackageName, callingAttributionTag, message);
+ Binder.getCallingUid(), callingPackageName, callingAttributionTag, message,
+ /*startDataDelivery*/ false);
}
/**
@@ -408,88 +793,325 @@
Binder.getCallingUid(), packageName);
}
- static int checkPermissionCommon(@NonNull Context context, @NonNull String permission,
- int pid, int uid, @Nullable String packageName, @Nullable String attributionTag,
- @Nullable String message, boolean forDataDelivery) {
- final PermissionInfo permissionInfo;
- try {
- // TODO(b/147869157): Cache platform defined app op and runtime permissions to avoid
- // calling into the package manager every time.
- permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
- } catch (PackageManager.NameNotFoundException ignored) {
- return PERMISSION_HARD_DENIED;
- }
+ @PermissionResult
+ private static int checkPermissionCommon(@NonNull Context context, @NonNull String permission,
+ @NonNull AttributionSource attributionSource,
+ @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
+ boolean fromDatasource) {
+ PermissionInfo permissionInfo = sPlatformPermissions.get(permission);
- if (packageName == null) {
- String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
- if (packageNames != null && packageNames.length > 0) {
- packageName = packageNames[0];
+ if (permissionInfo == null) {
+ try {
+ permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
+ if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) {
+ // Double addition due to concurrency is fine - the backing store is concurrent.
+ sPlatformPermissions.put(permission, permissionInfo);
+ }
+ } catch (PackageManager.NameNotFoundException ignored) {
+ return PERMISSION_HARD_DENIED;
}
}
if (permissionInfo.isAppOp()) {
- return checkAppOpPermission(context, permission, pid, uid, packageName, attributionTag,
- message, forDataDelivery);
+ return checkAppOpPermission(context, permission, attributionSource, message,
+ forDataDelivery, fromDatasource);
}
if (permissionInfo.isRuntime()) {
- return checkRuntimePermission(context, permission, pid, uid, packageName,
- attributionTag, message, forDataDelivery);
+ return checkRuntimePermission(context, permission, attributionSource, message,
+ forDataDelivery, startDataDelivery, fromDatasource);
}
- return context.checkPermission(permission, pid, uid);
+
+ if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(),
+ attributionSource.getRenouncedPermissions())) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ if (attributionSource.getNext() != null) {
+ return checkPermissionCommon(context, permission,
+ attributionSource.getNext(), message, forDataDelivery,
+ startDataDelivery, /*fromDatasource*/ false);
+ }
+
+ return PERMISSION_GRANTED;
}
+ @PermissionResult
private static int checkAppOpPermission(@NonNull Context context, @NonNull String permission,
- int pid, int uid, @Nullable String packageName, @Nullable String attributionTag,
- @Nullable String message, boolean forDataDelivery) {
- final String op = AppOpsManager.permissionToOp(permission);
- if (op == null || packageName == null) {
+ @NonNull AttributionSource attributionSource, @Nullable String message,
+ boolean forDataDelivery, boolean fromDatasource) {
+ final int op = AppOpsManager.permissionToOpCode(permission);
+ if (op < 0 || attributionSource.getPackageName() == null) {
return PERMISSION_HARD_DENIED;
}
final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
- final int opMode = (forDataDelivery)
- ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, attributionTag, message)
- : appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName);
- switch (opMode) {
- case AppOpsManager.MODE_ALLOWED:
- case AppOpsManager.MODE_FOREGROUND: {
- return PERMISSION_GRANTED;
- }
- case AppOpsManager.MODE_DEFAULT: {
- return context.checkPermission(permission, pid, uid)
- == PackageManager.PERMISSION_GRANTED
- ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
- }
- default: {
+ AttributionSource current = attributionSource;
+ AttributionSource next = null;
+
+ while (true) {
+ final boolean skipCurrentChecks = (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 != attributionSource)
+ && next != null && !current.isTrusted(context)) {
return PERMISSION_HARD_DENIED;
}
+
+ int opMode;
+ if (forDataDelivery) {
+ if (next == null) {
+ opMode = appOpsManager.noteOpNoThrow(op, current.getUid(),
+ current.getPackageName(), current.getAttributionTag(), message);
+ } else {
+ opMode = appOpsManager.noteProxyOpNoThrow(op, current, message,
+ skipCurrentChecks);
+ }
+ } else {
+ opMode = appOpsManager.unsafeCheckOpRawNoThrow(op, current.getUid(),
+ current.getPackageName());
+ if (next != null && opMode == AppOpsManager.MODE_ALLOWED) {
+ opMode = appOpsManager.unsafeCheckOpRawNoThrow(op, next.getUid(),
+ next.getPackageName());
+ }
+ }
+
+ switch (opMode) {
+ case AppOpsManager.MODE_IGNORED:
+ case AppOpsManager.MODE_ERRORED: {
+ return PERMISSION_HARD_DENIED;
+ }
+ case AppOpsManager.MODE_DEFAULT: {
+ if (!skipCurrentChecks && !checkPermission(context, permission,
+ attributionSource.getUid(), attributionSource
+ .getRenouncedPermissions())) {
+ return PERMISSION_HARD_DENIED;
+ }
+ if (next != null && !checkPermission(context, permission,
+ next.getUid(), next.getRenouncedPermissions())) {
+ return PERMISSION_HARD_DENIED;
+ }
+ }
+ }
+
+ if (next == null || next.getNext() == null) {
+ return PERMISSION_GRANTED;
+ }
+
+ current = next;
}
}
private static int checkRuntimePermission(@NonNull Context context, @NonNull String permission,
- int pid, int uid, @Nullable String packageName, @Nullable String attributionTag,
- @Nullable String message, boolean forDataDelivery) {
- if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
+ @NonNull AttributionSource attributionSource, @Nullable String message,
+ boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
+ // Now let's check the identity chain...
+ final int op = AppOpsManager.permissionToOpCode(permission);
+
+ AttributionSource current = attributionSource;
+ AttributionSource next = null;
+
+ while (true) {
+ final boolean skipCurrentChecks = (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 != attributionSource)
+ && next != null && !current.isTrusted(context)) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ // If we already checked the permission for this one, skip the work
+ if (!skipCurrentChecks && !checkPermission(context, permission,
+ current.getUid(), current.getRenouncedPermissions())) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ if (next != null && !checkPermission(context, permission,
+ next.getUid(), next.getRenouncedPermissions())) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ if (op < 0) {
+ continue;
+ }
+
+ // 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 == attributionSource && next != null && next.getNext() == null);
+ final boolean selfAccess = singleReceiverFromDatasource || next == null;
+
+ final int opMode = performOpTransaction(context, op, current, message,
+ forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+ singleReceiverFromDatasource);
+
+ switch (opMode) {
+ case AppOpsManager.MODE_ERRORED: {
+ return PERMISSION_HARD_DENIED;
+ }
+ case AppOpsManager.MODE_IGNORED: {
+ return PERMISSION_SOFT_DENIED;
+ }
+ }
+
+ if (next == null || next.getNext() == null) {
+ return PERMISSION_GRANTED;
+ }
+
+ current = next;
+ }
+ }
+
+ private static boolean checkPermission(@NonNull Context context, @NonNull String permission,
+ int uid, @NonNull Set<String> renouncedPermissions) {
+ final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1,
+ uid) == PackageManager.PERMISSION_GRANTED;
+ if (permissionGranted && renouncedPermissions.contains(permission)) {
+ return false;
+ }
+ return permissionGranted;
+ }
+
+ private static int checkOp(@NonNull Context context, @NonNull int op,
+ @NonNull AttributionSource attributionSource, @Nullable String message,
+ boolean forDataDelivery, boolean startDataDelivery) {
+ if (op < 0 || attributionSource.getPackageName() == null) {
return PERMISSION_HARD_DENIED;
}
- final String op = AppOpsManager.permissionToOp(permission);
- if (op == null || packageName == null) {
- return PERMISSION_GRANTED;
- }
+ AttributionSource current = attributionSource;
+ AttributionSource next = null;
- final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
- final int opMode = (forDataDelivery)
- ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, attributionTag, message)
- : appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName);
+ while (true) {
+ final boolean skipCurrentChecks = (next != null);
+ next = current.getNext();
- switch (opMode) {
- case AppOpsManager.MODE_ALLOWED:
- case AppOpsManager.MODE_FOREGROUND:
+ // 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 (next != null && !current.isTrusted(context)) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ // The access is for oneself if this is the single attribution source in the chain.
+ final boolean selfAccess = (next == null);
+
+ final int opMode = performOpTransaction(context, op, current, message,
+ forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+ /*fromDatasource*/ false);
+
+ switch (opMode) {
+ case AppOpsManager.MODE_ERRORED: {
+ return PERMISSION_HARD_DENIED;
+ }
+ case AppOpsManager.MODE_IGNORED: {
+ return PERMISSION_SOFT_DENIED;
+ }
+ }
+
+ if (next == null || next.getNext() == null) {
return PERMISSION_GRANTED;
- default:
- return PERMISSION_SOFT_DENIED;
+ }
+
+ current = next;
}
}
+ // If from data source and there is next app after that we need to note SELF of (noteOp) for the app vs proxy
+ 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) {
+ // 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
+ // of generality - they are in the same security sandbox).
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ if (!forDataDelivery) {
+ final String resolvedPackageName = resolvePackageName(context, attributionSource);
+ if (resolvedPackageName == null) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op,
+ attributionSource.getUid(), resolvedPackageName);
+ final AttributionSource previous = attributionSource.getNext();
+ if (opMode == AppOpsManager.MODE_ALLOWED && previous != null) {
+ final String resolvedPreviousPackageName = resolvePackageName(context,
+ previous);
+ if (resolvedPreviousPackageName == null) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ return appOpsManager.unsafeCheckOpRawNoThrow(op, previous.getUid(),
+ resolvedPreviousPackageName);
+ }
+ return opMode;
+ } else if (startDataDelivery) {
+ final AttributionSource accessorSource = (!singleReceiverFromDatasource)
+ ? attributionSource : attributionSource.getNext();
+ final AttributionSource resolvedAttributionSource = resolveAttributionSource(
+ context, accessorSource);
+ if (resolvedAttributionSource.getPackageName() == null) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ if (selfAccess) {
+ return appOpsManager.startOpNoThrow(op, resolvedAttributionSource.getUid(),
+ resolvedAttributionSource.getPackageName(),
+ /*startIfModeDefault*/ false,
+ resolvedAttributionSource.getAttributionTag(),
+ message);
+ } else {
+ return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message,
+ skipProxyOperation);
+ }
+ } else {
+ final AttributionSource accessorSource = (!singleReceiverFromDatasource)
+ ? attributionSource : attributionSource.getNext();
+ final AttributionSource resolvedAttributionSource = resolveAttributionSource(
+ context, accessorSource);
+ if (resolvedAttributionSource.getPackageName() == null) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ if (selfAccess) {
+ return appOpsManager.noteOpNoThrow(op, resolvedAttributionSource.getUid(),
+ resolvedAttributionSource.getPackageName(),
+ resolvedAttributionSource.getAttributionTag(),
+ message);
+ } else {
+ return appOpsManager.noteProxyOpNoThrow(op, resolvedAttributionSource, message,
+ skipProxyOperation);
+ }
+ }
+ }
+
+ private static @Nullable String resolvePackageName(@NonNull Context context,
+ @NonNull AttributionSource attributionSource) {
+ if (attributionSource.getPackageName() != null) {
+ return attributionSource.getPackageName();
+ }
+ final String[] packageNames = context.getPackageManager().getPackagesForUid(
+ attributionSource.getUid());
+ if (packageNames != null) {
+ // This is best effort if the caller doesn't pass a package. The security
+ // sandbox is UID, therefore we pick an arbitrary package.
+ return packageNames[0];
+ }
+ return null;
+ }
+
+ private static @NonNull AttributionSource resolveAttributionSource(
+ @NonNull Context context, @NonNull AttributionSource attributionSource) {
+ if (attributionSource.getPackageName() != null) {
+ return attributionSource;
+ }
+ return new AttributionSource(attributionSource.getUid(),
+ resolvePackageName(context, attributionSource),
+ attributionSource.getAttributionTag(),
+ attributionSource.getToken(),
+ attributionSource.getRenouncedPermissions(),
+ attributionSource.getNext());
+ }
}
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 8c105be..ef075e1 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -16,6 +16,7 @@
package android.permission;
+import android.content.AttributionSource;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
@@ -85,4 +86,8 @@
boolean setAutoRevokeExempted(String packageName, boolean exempted, int userId);
boolean isAutoRevokeExempted(String packageName, int userId);
+
+ AttributionSource registerAttributionSource(in AttributionSource source);
+
+ boolean isRegisteredAttributionSource(in AttributionSource source);
}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index baa25f0..936cbfc 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -44,6 +44,7 @@
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.location.LocationManager;
import android.media.AudioManager;
+import android.content.AttributionSource;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -1099,6 +1100,48 @@
callingFeatureId, pid, uid);
}
+ /**
+ * Registers an attribution source with the OS. An app can only register an attribution
+ * source for itself. Once an attribution source has been registered another app can
+ * check whether this registration exists and thus trust the payload in the source
+ * object. This is important for permission checking and specifically for app op blaming
+ * since a malicious app should not be able to force the OS to blame another app
+ * that doesn't participate in an attribution chain.
+ *
+ * @param source The attribution source to register.
+ *
+ * @see #isRegisteredAttributionSource(AttributionSource)
+ *
+ * @hide
+ */
+ public @NonNull AttributionSource registerAttributionSource(@NonNull AttributionSource source) {
+ try {
+ return mPermissionManager.registerAttributionSource(source);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return null;
+ }
+
+ /**
+ * Checks whether an attribution source is registered.
+ *
+ * @param source The attribution source to check.
+ * @return Whether this is a registered source.
+ *
+ * @see #registerAttributionSource(AttributionSource)
+ *
+ * @hide
+ */
+ public boolean isRegisteredAttributionSource(@NonNull AttributionSource source) {
+ try {
+ return mPermissionManager.isRegisteredAttributionSource(source);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
/* @hide */
private static int checkPermissionUncached(@Nullable String permission, int pid, int uid) {
final IActivityManager am = ActivityManager.getService();
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index b704d66..a5a24c0 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -1102,8 +1102,7 @@
// signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
// MANAGE_DOCUMENTS or associated URI permission here instead
final Uri rootUri = extraUri;
- enforceWritePermissionInner(rootUri, getCallingPackage(), getCallingAttributionTag(),
- null);
+ enforceWritePermissionInner(rootUri, getCallingAttributionSource());
final String rootId = DocumentsContract.getRootId(rootUri);
ejectRoot(rootId);
@@ -1121,8 +1120,7 @@
}
if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
- enforceReadPermissionInner(documentUri, getCallingPackage(),
- getCallingAttributionTag(), null);
+ enforceReadPermissionInner(documentUri, getCallingAttributionSource());
final Uri childUri = extraTargetUri;
final String childAuthority = childUri.getAuthority();
@@ -1134,8 +1132,7 @@
&& isChildDocument(documentId, childId));
} else if (METHOD_CREATE_DOCUMENT.equals(method)) {
- enforceWritePermissionInner(documentUri, getCallingPackage(),
- getCallingAttributionTag(), null);
+ enforceWritePermissionInner(documentUri, getCallingAttributionSource());
final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
@@ -1149,8 +1146,7 @@
out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
} else if (METHOD_CREATE_WEB_LINK_INTENT.equals(method)) {
- enforceWritePermissionInner(documentUri, getCallingPackage(),
- getCallingAttributionTag(), null);
+ enforceWritePermissionInner(documentUri, getCallingAttributionSource());
final Bundle options = extras.getBundle(DocumentsContract.EXTRA_OPTIONS);
final IntentSender intentSender = createWebLinkIntent(documentId, options);
@@ -1158,8 +1154,7 @@
out.putParcelable(DocumentsContract.EXTRA_RESULT, intentSender);
} else if (METHOD_RENAME_DOCUMENT.equals(method)) {
- enforceWritePermissionInner(documentUri, getCallingPackage(),
- getCallingAttributionTag(), null);
+ enforceWritePermissionInner(documentUri, getCallingAttributionSource());
final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
final String newDocumentId = renameDocument(documentId, displayName);
@@ -1183,8 +1178,7 @@
}
} else if (METHOD_DELETE_DOCUMENT.equals(method)) {
- enforceWritePermissionInner(documentUri, getCallingPackage(),
- getCallingAttributionTag(), null);
+ enforceWritePermissionInner(documentUri, getCallingAttributionSource());
deleteDocument(documentId);
// Document no longer exists, clean up any grants.
@@ -1194,10 +1188,8 @@
final Uri targetUri = extraTargetUri;
final String targetId = DocumentsContract.getDocumentId(targetUri);
- enforceReadPermissionInner(documentUri, getCallingPackage(),
- getCallingAttributionTag(), null);
- enforceWritePermissionInner(targetUri, getCallingPackage(), getCallingAttributionTag(),
- null);
+ enforceReadPermissionInner(documentUri, getCallingAttributionSource());
+ enforceWritePermissionInner(targetUri, getCallingAttributionSource());
final String newDocumentId = copyDocument(documentId, targetId);
@@ -1220,12 +1212,9 @@
final Uri targetUri = extraTargetUri;
final String targetId = DocumentsContract.getDocumentId(targetUri);
- enforceWritePermissionInner(documentUri, getCallingPackage(),
- getCallingAttributionTag(), null);
- enforceReadPermissionInner(parentSourceUri, getCallingPackage(),
- getCallingAttributionTag(), null);
- enforceWritePermissionInner(targetUri, getCallingPackage(), getCallingAttributionTag(),
- null);
+ enforceWritePermissionInner(documentUri, getCallingAttributionSource());
+ enforceReadPermissionInner(parentSourceUri, getCallingAttributionSource());
+ enforceWritePermissionInner(targetUri, getCallingAttributionSource());
final String newDocumentId = moveDocument(documentId, parentSourceId, targetId);
@@ -1246,10 +1235,8 @@
final Uri parentSourceUri = extraParentUri;
final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
- enforceReadPermissionInner(parentSourceUri, getCallingPackage(),
- getCallingAttributionTag(), null);
- enforceWritePermissionInner(documentUri, getCallingPackage(),
- getCallingAttributionTag(), null);
+ enforceReadPermissionInner(parentSourceUri, getCallingAttributionSource());
+ enforceWritePermissionInner(documentUri, getCallingAttributionSource());
removeDocument(documentId, parentSourceId);
// It's responsibility of the provider to revoke any grants, as the document may be
@@ -1258,8 +1245,7 @@
final boolean isTreeUri = isTreeUri(documentUri);
if (isTreeUri) {
- enforceReadPermissionInner(documentUri, getCallingPackage(),
- getCallingAttributionTag(), null);
+ enforceReadPermissionInner(documentUri, getCallingAttributionSource());
} else {
getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5d924cc..12ff640 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2754,7 +2754,7 @@
arg.putBoolean(CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY, true);
}
IContentProvider cp = mProviderHolder.getProvider(cr);
- cp.call(cr.getPackageName(), cr.getAttributionTag(),
+ cp.call(cr.getAttributionSource(),
mProviderHolder.mUri.getAuthority(), mCallSetCommand, name, arg);
} catch (RemoteException e) {
Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
@@ -2774,7 +2774,7 @@
args.putString(CALL_METHOD_PREFIX_KEY, prefix);
args.putSerializable(CALL_METHOD_FLAGS_KEY, keyValues);
IContentProvider cp = mProviderHolder.getProvider(cr);
- Bundle bundle = cp.call(cr.getPackageName(), cr.getAttributionTag(),
+ Bundle bundle = cp.call(cr.getAttributionSource(),
mProviderHolder.mUri.getAuthority(),
mCallSetAllCommand, null, args);
return bundle.getBoolean(KEY_CONFIG_SET_RETURN);
@@ -2862,14 +2862,14 @@
if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) {
final long token = Binder.clearCallingIdentity();
try {
- b = cp.call(cr.getPackageName(), cr.getAttributionTag(),
+ b = cp.call(cr.getAttributionSource(),
mProviderHolder.mUri.getAuthority(), mCallGetCommand, name,
args);
} finally {
Binder.restoreCallingIdentity(token);
}
} else {
- b = cp.call(cr.getPackageName(), cr.getAttributionTag(),
+ b = cp.call(cr.getAttributionSource(),
mProviderHolder.mUri.getAuthority(), mCallGetCommand, name, args);
}
if (b != null) {
@@ -2939,13 +2939,13 @@
if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) {
final long token = Binder.clearCallingIdentity();
try {
- c = cp.query(cr.getPackageName(), cr.getAttributionTag(), mUri,
+ c = cp.query(cr.getAttributionSource(), mUri,
SELECT_VALUE_PROJECTION, queryArgs, null);
} finally {
Binder.restoreCallingIdentity(token);
}
} else {
- c = cp.query(cr.getPackageName(), cr.getAttributionTag(), mUri,
+ c = cp.query(cr.getAttributionSource(), mUri,
SELECT_VALUE_PROJECTION, queryArgs, null);
}
if (c == null) {
@@ -3051,7 +3051,7 @@
}
// Fetch all flags for the namespace at once for caching purposes
- Bundle b = cp.call(cr.getPackageName(), cr.getAttributionTag(),
+ Bundle b = cp.call(cr.getAttributionSource(),
mProviderHolder.mUri.getAuthority(), mCallListCommand, null, args);
if (b == null) {
// Invalid response, return an empty map
@@ -5877,7 +5877,7 @@
}
arg.putInt(CALL_METHOD_RESET_MODE_KEY, mode);
IContentProvider cp = sProviderHolder.getProvider(resolver);
- cp.call(resolver.getPackageName(), resolver.getAttributionTag(),
+ cp.call(resolver.getAttributionSource(),
sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_SECURE, null, arg);
} catch (RemoteException e) {
Log.w(TAG, "Can't reset do defaults for " + CONTENT_URI, e);
@@ -14914,7 +14914,7 @@
}
arg.putInt(CALL_METHOD_RESET_MODE_KEY, mode);
IContentProvider cp = sProviderHolder.getProvider(resolver);
- cp.call(resolver.getPackageName(), resolver.getAttributionTag(),
+ cp.call(resolver.getAttributionSource(),
sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_GLOBAL, null, arg);
} catch (RemoteException e) {
Log.w(TAG, "Can't reset do defaults for " + CONTENT_URI, e);
@@ -16053,7 +16053,7 @@
arg.putString(Settings.CALL_METHOD_PREFIX_KEY, createPrefix(namespace));
}
IContentProvider cp = sProviderHolder.getProvider(resolver);
- cp.call(resolver.getPackageName(), resolver.getAttributionTag(),
+ cp.call(resolver.getAttributionSource(),
sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_CONFIG, null, arg);
} catch (RemoteException e) {
Log.w(TAG, "Can't reset to defaults for " + DeviceConfig.CONTENT_URI, e);
@@ -16082,7 +16082,7 @@
arg.putInt(CALL_METHOD_USER_KEY, userHandle);
arg.putParcelable(CALL_METHOD_MONITOR_CALLBACK_KEY, callback);
IContentProvider cp = sProviderHolder.getProvider(resolver);
- cp.call(resolver.getPackageName(), resolver.getAttributionTag(),
+ cp.call(resolver.getAttributionSource(),
sProviderHolder.mUri.getAuthority(),
CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG, null, arg);
} catch (RemoteException e) {
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index cc1cded..9a5e534 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -17,6 +17,7 @@
package android.speech;
import android.os.Bundle;
+import android.content.AttributionSource;
import android.content.Intent;
import android.speech.IRecognitionListener;
@@ -39,11 +40,10 @@
* this intent can contain extra parameters to manipulate the behavior of the recognition
* client. For more information see {@link RecognizerIntent}.
* @param listener to receive callbacks, note that this must be non-null
- * @param packageName the package name calling this API
- * @param featureId The feature in the package
+ * @param attributionSource The attribution source of the caller.
*/
void startListening(in Intent recognizerIntent, in IRecognitionListener listener,
- String packageName, String featureId, int callingUid);
+ in AttributionSource attributionSource);
/**
* Stops listening for speech. Speech captured so far will be recognized as
@@ -51,18 +51,14 @@
* is called during the speech capturing.
*
* @param listener to receive callbacks, note that this must be non-null
- * @param packageName the package name calling this API
- * @param featureId The feature in the package
*/
- void stopListening(in IRecognitionListener listener, String packageName, String featureId);
+ void stopListening(in IRecognitionListener listener);
/**
* Cancels the speech recognition.
*
* @param listener to receive callbacks, note that this must be non-null
* @param packageName the package name calling this API
- * @param featureId The feature in the package
- * @param isShutdown Whether the cancellation is caused by a client calling #shutdown
*/
- void cancel(in IRecognitionListener listener, String packageName, String featureId, boolean isShutdown);
+ void cancel(in IRecognitionListener listener, boolean isShutdown);
}
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index fd584f1..4afa9473 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -16,11 +16,16 @@
package android.speech;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
+import android.app.AppOpsManager;
import android.app.Service;
+import android.content.Context;
+import android.content.ContextParams;
import android.content.Intent;
import android.content.PermissionChecker;
import android.os.Binder;
@@ -28,13 +33,13 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.content.AttributionSource;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
-import com.android.internal.util.Preconditions;
-
import java.lang.ref.WeakReference;
+import java.util.Objects;
/**
* This class provides a base class for recognition service implementations. This class should be
@@ -86,13 +91,13 @@
switch (msg.what) {
case MSG_START_LISTENING:
StartListeningArgs args = (StartListeningArgs) msg.obj;
- dispatchStartListening(args.mIntent, args.mListener, args.mCallingUid);
+ dispatchStartListening(args.mIntent, args.mListener, args.mAttributionSource);
break;
case MSG_STOP_LISTENING:
dispatchStopListening((IRecognitionListener) msg.obj);
break;
case MSG_CANCEL:
- dispatchCancel((IRecognitionListener) msg.obj);
+ dispatchCancel((IRecognitionListener) msg.obj, msg.arg1 == 1);
break;
case MSG_RESET:
dispatchClearCallback();
@@ -102,10 +107,11 @@
};
private void dispatchStartListening(Intent intent, final IRecognitionListener listener,
- int callingUid) {
+ @NonNull AttributionSource attributionSource) {
if (mCurrentCallback == null) {
if (DBG) Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder());
- mCurrentCallback = new Callback(listener, callingUid);
+ mCurrentCallback = new Callback(listener, attributionSource);
+
RecognitionService.this.onStartListening(intent, mCurrentCallback);
} else {
try {
@@ -133,13 +139,16 @@
}
}
- private void dispatchCancel(IRecognitionListener listener) {
+ private void dispatchCancel(IRecognitionListener listener, boolean shutDown) {
if (mCurrentCallback == null) {
if (DBG) Log.d(TAG, "cancel called with no preceding startListening - ignoring");
} else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
} else { // the correct state
RecognitionService.this.onCancel(mCurrentCallback);
+ if (shutDown) {
+ mCurrentCallback.finishRecordAudioOpAttributionToCallerIfNeeded();
+ }
mCurrentCallback = null;
if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
}
@@ -153,12 +162,13 @@
public final Intent mIntent;
public final IRecognitionListener mListener;
- public final int mCallingUid;
+ public final @NonNull AttributionSource mAttributionSource;
- public StartListeningArgs(Intent intent, IRecognitionListener listener, int callingUid) {
+ public StartListeningArgs(Intent intent, IRecognitionListener listener,
+ @NonNull AttributionSource attributionSource) {
this.mIntent = intent;
this.mListener = listener;
- this.mCallingUid = callingUid;
+ this.mAttributionSource = attributionSource;
}
}
@@ -247,18 +257,19 @@
*/
public class Callback {
private final IRecognitionListener mListener;
- private final int mCallingUid;
+ private final @NonNull AttributionSource mCallingAttributionSource;
+ private @Nullable Context mAttributionContext;
- private Callback(IRecognitionListener listener, int callingUid) {
+ private Callback(IRecognitionListener listener,
+ @NonNull AttributionSource attributionSource) {
mListener = listener;
- mCallingUid = callingUid;
+ mCallingAttributionSource = attributionSource;
}
/**
* The service should call this method when the user has started to speak.
*/
public void beginningOfSpeech() throws RemoteException {
- if (DBG) Log.d(TAG, "beginningOfSpeech");
mListener.onBeginningOfSpeech();
}
@@ -270,6 +281,7 @@
* single channel audio stream. The sample rate is implementation dependent.
*/
public void bufferReceived(byte[] buffer) throws RemoteException {
+ startRecordAudioOpAttributionToCallerIfNeeded();
mListener.onBufferReceived(buffer);
}
@@ -302,6 +314,7 @@
* {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
*/
public void partialResults(Bundle partialResults) throws RemoteException {
+ startRecordAudioOpAttributionToCallerIfNeeded();
mListener.onPartialResults(partialResults);
}
@@ -323,6 +336,7 @@
* {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
*/
public void results(Bundle results) throws RemoteException {
+ startRecordAudioOpAttributionToCallerIfNeeded();
Message.obtain(mHandler, MSG_RESET).sendToTarget();
mListener.onResults(results);
}
@@ -342,7 +356,65 @@
* is being processed. This is obtained from {@link Binder#getCallingUid()}.
*/
public int getCallingUid() {
- return mCallingUid;
+ return mCallingAttributionSource.getUid();
+ }
+
+ /**
+ * Gets the permission identity of the calling app. If you want to attribute
+ * the mic access to the calling app you can create an attribution context
+ * via {@link android.content.Context#createContext(android.content.ContextParams)}
+ * and passing this identity to {@link
+ * android.content.ContextParams.Builder#setNextAttributionSource(AttributionSource)}.
+ *
+ *
+ *
+ *
+ * @return The permission identity of the calling app.
+ *
+ * @see android.content.ContextParams.Builder#setNextAttributionSource(
+ * AttributionSource)
+ */
+ @SuppressLint("CallbackMethodName")
+ public @NonNull AttributionSource getCallingAttributionSource() {
+ return mCallingAttributionSource;
+ }
+
+ private void startRecordAudioOpAttributionToCallerIfNeeded() throws RemoteException {
+ if (!isProxyingRecordAudioToCaller()) {
+ final int result = PermissionChecker.checkPermissionAndStartDataDelivery(
+ RecognitionService.this, Manifest.permission.RECORD_AUDIO,
+ getAttributionContextForCaller().getAttributionSource(),
+ /*message*/ null);
+ if (result == PermissionChecker.PERMISSION_GRANTED) {
+ return;
+ }
+ error(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
+ }
+ }
+
+ private @NonNull Context getAttributionContextForCaller() {
+ if (mAttributionContext == null) {
+ mAttributionContext = createContext(new ContextParams.Builder()
+ .setNextAttributionSource(mCallingAttributionSource)
+ .build());
+ }
+ return mAttributionContext;
+ }
+
+ void finishRecordAudioOpAttributionToCallerIfNeeded() {
+ if (isProxyingRecordAudioToCaller()) {
+ final String op = AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
+ PermissionChecker.finishDataDelivery(RecognitionService.this,
+ op, getAttributionContextForCaller().getAttributionSource());
+ }
+ }
+
+ private boolean isProxyingRecordAudioToCaller() {
+ final int op = AppOpsManager.permissionToOpCode(Manifest.permission.RECORD_AUDIO);
+ final AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
+ return appOpsManager.isProxying(op, getAttributionTag(),
+ mCallingAttributionSource.getUid(),
+ mCallingAttributionSource.getPackageName());
}
}
@@ -356,44 +428,35 @@
@Override
public void startListening(Intent recognizerIntent, IRecognitionListener listener,
- String packageName, String featureId, int callingUid) {
- Preconditions.checkNotNull(packageName);
-
+ @NonNull AttributionSource attributionSource) {
+ Objects.requireNonNull(attributionSource);
+ attributionSource.enforceCallingUid();
if (DBG) Log.d(TAG, "startListening called by:" + listener.asBinder());
final RecognitionService service = mServiceRef.get();
- if (service != null && service.checkPermissions(listener, true /*forDataDelivery*/,
- packageName, featureId)) {
+ if (service != null) {
service.mHandler.sendMessage(Message.obtain(service.mHandler,
MSG_START_LISTENING, service.new StartListeningArgs(
- recognizerIntent, listener, callingUid)));
+ recognizerIntent, listener, attributionSource)));
}
}
@Override
- public void stopListening(IRecognitionListener listener, String packageName,
- String featureId) {
- Preconditions.checkNotNull(packageName);
-
+ public void stopListening(IRecognitionListener listener) {
if (DBG) Log.d(TAG, "stopListening called by:" + listener.asBinder());
final RecognitionService service = mServiceRef.get();
- if (service != null && service.checkPermissions(listener, false /*forDataDelivery*/,
- packageName, featureId)) {
+ if (service != null) {
service.mHandler.sendMessage(Message.obtain(service.mHandler,
MSG_STOP_LISTENING, listener));
}
}
@Override
- public void cancel(IRecognitionListener listener, String packageName,
- String featureId, boolean isShutdown) {
- Preconditions.checkNotNull(packageName);
-
+ public void cancel(IRecognitionListener listener, boolean isShutdown) {
if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder());
final RecognitionService service = mServiceRef.get();
- if (service != null && service.checkPermissions(listener, false /*forDataDelivery*/,
- packageName, featureId)) {
+ if (service != null) {
service.mHandler.sendMessage(Message.obtain(service.mHandler,
- MSG_CANCEL, listener));
+ MSG_CANCEL, isShutdown ? 1 : 0, 0, listener));
}
}
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index 9b93a64..7aa5ee5 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -386,8 +386,7 @@
return;
}
try {
- mService.startListening(recognizerIntent, mListener, mContext.getOpPackageName(),
- mContext.getAttributionTag(), android.os.Process.myUid());
+ mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource());
if (DBG) Log.d(TAG, "service start listening command succeded");
} catch (final RemoteException e) {
Log.e(TAG, "startListening() failed", e);
@@ -401,8 +400,7 @@
return;
}
try {
- mService.stopListening(mListener, mContext.getOpPackageName(),
- mContext.getAttributionTag());
+ mService.stopListening(mListener);
if (DBG) Log.d(TAG, "service stop listening command succeded");
} catch (final RemoteException e) {
Log.e(TAG, "stopListening() failed", e);
@@ -416,11 +414,7 @@
return;
}
try {
- mService.cancel(
- mListener,
- mContext.getOpPackageName(),
- mContext.getAttributionTag(),
- false /* isShutdown */);
+ mService.cancel(mListener, /*isShutdown*/ false);
if (DBG) Log.d(TAG, "service cancel command succeded");
} catch (final RemoteException e) {
Log.e(TAG, "cancel() failed", e);
@@ -463,8 +457,7 @@
public void destroy() {
if (mService != null) {
try {
- mService.cancel(mListener, mContext.getOpPackageName(),
- mContext.getAttributionTag(), true /* isShutdown */);
+ mService.cancel(mListener, /*isShutdown*/ true);
} catch (final RemoteException e) {
// Not important
}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index eecd0cf..01bb199 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -20,6 +20,7 @@
import android.app.AsyncNotedAppOp;
import android.app.SyncNotedAppOp;
import android.app.RuntimeAppOpAccessMessage;
+import android.content.AttributionSource;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
import android.os.RemoteCallback;
@@ -52,17 +53,13 @@
// End of methods also called by native code.
// Any new method exposed to native must be added after the last one, do not reorder
- int noteProxyOperation(int code, int proxiedUid, String proxiedPackageName,
- String proxiedAttributionTag, int proxyUid, String proxyPackageName,
- String proxyAttributionTag, boolean shouldCollectAsyncNotedOp, String message,
- boolean shouldCollectMessage);
- int startProxyOperation(IBinder clientId, int code, int proxiedUid, String proxiedPackageName,
- @nullable String proxiedAttributionTag, int proxyUid, String proxyPackageName,
- @nullable String proxyAttributionTag, boolean startIfModeDefault,
- boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage);
- void finishProxyOperation(IBinder clientId, int code, int proxiedUid, String proxiedPackageName,
- @nullable String proxiedAttributionTag, int proxyUid, String proxyPackageName,
- @nullable String proxyAttributionTag);
+ int noteProxyOperation(int code, in AttributionSource attributionSource,
+ boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+ boolean skipProxyOperation);
+ int startProxyOperation(IBinder clientId, int code, in AttributionSource attributionSource,
+ boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
+ boolean shouldCollectMessage, boolean skipProxyOperation);
+ void finishProxyOperation(IBinder clientId, int code, in AttributionSource attributionSource);
// Remaining methods are only used in Java.
int checkPackage(int uid, String packageName);
@@ -83,6 +80,7 @@
void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep);
void addHistoricalOps(in AppOpsManager.HistoricalOps ops);
void resetHistoryParameters();
+ void resetPackageOpsNoHistory(String packageName);
void clearHistory();
void rebootHistory(long offlineDurationMillis);
List<AppOpsManager.PackageOps> getUidOps(int uid, in int[] ops);
@@ -100,6 +98,8 @@
void startWatchingActive(in int[] ops, IAppOpsActiveCallback callback);
void stopWatchingActive(IAppOpsActiveCallback callback);
boolean isOperationActive(int code, int uid, String packageName);
+ boolean isProxying(int op, String proxyPackageName, String proxyAttributionTag, int proxiedUid,
+ String proxiedPackageName);
void startWatchingStarted(in int[] ops, IAppOpsStartedCallback callback);
void stopWatchingStarted(IAppOpsStartedCallback callback);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index be306e0..ada6704 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1313,7 +1313,7 @@
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_recordBackgroundAudio"
android:description="@string/permdesc_recordBackgroundAudio"
- android:protectionLevel="internal" />
+ android:protectionLevel="internal|role" />
<!-- ====================================================================== -->
<!-- Permissions for activity recognition -->
@@ -1405,7 +1405,7 @@
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_backgroundCamera"
android:description="@string/permdesc_backgroundCamera"
- android:protectionLevel="internal" />
+ android:protectionLevel="internal|role" />
<!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
system only camera devices.
diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java
index b517428..01e240a 100644
--- a/core/tests/coretests/src/android/content/ContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/ContentResolverTest.java
@@ -86,7 +86,7 @@
final AssetFileDescriptor afd = new AssetFileDescriptor(
new ParcelFileDescriptor(mImage.getFileDescriptor()), 0, mSize, null);
- when(mProvider.openTypedAssetFile(any(), any(), any(), any(), any(), any())).thenReturn(
+ when(mProvider.openTypedAssetFile(any(), any(), any(), any(), any())).thenReturn(
afd);
}
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index 4212ef2..97e66c4 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -33,6 +33,7 @@
import android.util.MemoryIntArray;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
@@ -73,7 +74,8 @@
Settings.Config.clearProviderForTest();
MockitoAnnotations.initMocks(this);
when(mMockContentProvider.getIContentProvider()).thenReturn(mMockIContentProvider);
- mMockContentResolver = new MockContentResolver();
+ mMockContentResolver = new MockContentResolver(InstrumentationRegistry
+ .getInstrumentation().getContext());
mMockContentResolver.addProvider(DeviceConfig.CONTENT_URI.getAuthority(),
mMockContentProvider);
mCacheGenerationStore = new MemoryIntArray(1);
@@ -82,10 +84,10 @@
// Stores keyValues for a given prefix and increments the generation. (Note that this
// increments the generation no matter what, it doesn't pay attention to if anything
// actually changed).
- when(mMockIContentProvider.call(any(), any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
+ when(mMockIContentProvider.call(any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
- Bundle incomingBundle = invocationOnMock.getArgument(5);
+ Bundle incomingBundle = invocationOnMock.getArgument(4);
HashMap<String, String> keyValues =
(HashMap<String, String>) incomingBundle.getSerializable(
Settings.CALL_METHOD_FLAGS_KEY);
@@ -101,10 +103,10 @@
// Returns the keyValues corresponding to a namespace, or an empty map if the namespace
// doesn't have anything stored for it. Returns the generation key if the caller asked
// for one.
- when(mMockIContentProvider.call(any(), any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
+ when(mMockIContentProvider.call(any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
eq(Settings.CALL_METHOD_LIST_CONFIG),
any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
- Bundle incomingBundle = invocationOnMock.getArgument(5);
+ Bundle incomingBundle = invocationOnMock.getArgument(4);
String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
@@ -132,14 +134,14 @@
HashMap<String, String> keyValues = new HashMap<>();
keyValues.put("a", "b");
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
- verify(mMockIContentProvider).call(any(), any(), any(),
+ verify(mMockIContentProvider).call(any(), any(),
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
any(), any(Bundle.class));
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
NAMESPACE,
Collections.emptyList());
- verify(mMockIContentProvider).call(any(), any(), any(),
+ verify(mMockIContentProvider).call(any(), any(),
eq(Settings.CALL_METHOD_LIST_CONFIG),
any(), any(Bundle.class));
assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
@@ -152,13 +154,13 @@
// Modify the value to invalidate the cache.
keyValues.put("a", "c");
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
- verify(mMockIContentProvider, times(2)).call(any(), any(), any(),
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
any(), any(Bundle.class));
Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
NAMESPACE, Collections.emptyList());
- verify(mMockIContentProvider, times(2)).call(any(), any(), any(),
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
eq(Settings.CALL_METHOD_LIST_CONFIG),
any(), any(Bundle.class));
assertThat(returnedValues2).containsExactlyEntriesIn(keyValues);
@@ -174,7 +176,7 @@
HashMap<String, String> keyValues = new HashMap<>();
keyValues.put("a", "b");
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
- verify(mMockIContentProvider).call(any(), any(), any(),
+ verify(mMockIContentProvider).call(any(), any(),
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
any(), any(Bundle.class));
@@ -182,14 +184,14 @@
keyValues2.put("c", "d");
keyValues2.put("e", "f");
Settings.Config.setStrings(mMockContentResolver, NAMESPACE2, keyValues2);
- verify(mMockIContentProvider, times(2)).call(any(), any(), any(),
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
any(), any(Bundle.class));
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
NAMESPACE,
Collections.emptyList());
- verify(mMockIContentProvider).call(any(), any(), any(),
+ verify(mMockIContentProvider).call(any(), any(),
eq(Settings.CALL_METHOD_LIST_CONFIG),
any(), any(Bundle.class));
assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
@@ -197,7 +199,7 @@
Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
NAMESPACE2,
Collections.emptyList());
- verify(mMockIContentProvider, times(2)).call(any(), any(), any(),
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
eq(Settings.CALL_METHOD_LIST_CONFIG),
any(), any(Bundle.class));
assertThat(returnedValues2).containsExactlyEntriesIn(keyValues2);
@@ -218,7 +220,7 @@
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
NAMESPACE,
Collections.emptyList());
- verify(mMockIContentProvider).call(any(), any(), any(),
+ verify(mMockIContentProvider).call(any(), any(),
eq(Settings.CALL_METHOD_LIST_CONFIG),
any(), any(Bundle.class));
assertThat(returnedValues).isEmpty();
diff --git a/core/tests/coretests/src/android/provider/TestDocumentsProvider.java b/core/tests/coretests/src/android/provider/TestDocumentsProvider.java
index 5f640be..4496983 100644
--- a/core/tests/coretests/src/android/provider/TestDocumentsProvider.java
+++ b/core/tests/coretests/src/android/provider/TestDocumentsProvider.java
@@ -18,13 +18,13 @@
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.content.AttributionSource;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.CancellationSignal;
-import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract.Path;
@@ -93,14 +93,12 @@
}
@Override
- protected int enforceReadPermissionInner(Uri uri, String callingPkg,
- @Nullable String callingFeatureId, IBinder callerToken) {
+ protected int enforceReadPermissionInner(Uri uri, AttributionSource attributionSource) {
return AppOpsManager.MODE_ALLOWED;
}
@Override
- protected int enforceWritePermissionInner(Uri uri, String callingPkg,
- @Nullable String callingFeatureId, IBinder callerToken) {
+ protected int enforceWritePermissionInner(Uri uri, AttributionSource attributionSource) {
return AppOpsManager.MODE_ALLOWED;
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
index de353bf..a0d4492 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
@@ -24,8 +24,10 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.AttributionSource;
import android.content.ContentProviderClient;
import android.content.ContentValues;
+import android.content.Context;
import android.content.IContentProvider;
import android.media.MediaInserter;
import android.net.Uri;
@@ -46,7 +48,7 @@
private MediaInserter mMediaInserter;
private static final int TEST_BUFFER_SIZE = 10;
private @Mock IContentProvider mMockProvider;
- private String mPackageName;
+ private AttributionSource mAttributionSource;
private int mFilesCounter;
private int mAudioCounter;
@@ -86,11 +88,14 @@
super.setUp();
MockitoAnnotations.initMocks(this);
- final ContentProviderClient client = new ContentProviderClient(
- getInstrumentation().getContext().createFeatureContext(TEST_FEATURE_ID)
+ final Context attributionContext = getInstrumentation().getContext()
+ .createFeatureContext(TEST_FEATURE_ID);
+
+ final ContentProviderClient client = new ContentProviderClient(attributionContext
.getContentResolver(), mMockProvider, true);
+
mMediaInserter = new MediaInserter(client, TEST_BUFFER_SIZE);
- mPackageName = getInstrumentation().getContext().getPackageName();
+ mAttributionSource = attributionContext.getAttributionSource();
mFilesCounter = 0;
mAudioCounter = 0;
mVideoCounter = 0;
@@ -144,13 +149,13 @@
fillBuffer(sVideoUri, TEST_BUFFER_SIZE - 2);
fillBuffer(sImagesUri, TEST_BUFFER_SIZE - 1);
- verify(mMockProvider, never()).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+ verify(mMockProvider, never()).bulkInsert(eq(mAttributionSource), any(),
any());
}
@SmallTest
public void testInsertContentsEqualToBufferSize() throws Exception {
- when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+ when(mMockProvider.bulkInsert(eq(mAttributionSource), any(),
any())).thenReturn(1);
fillBuffer(sFilesUri, TEST_BUFFER_SIZE);
@@ -158,13 +163,13 @@
fillBuffer(sVideoUri, TEST_BUFFER_SIZE);
fillBuffer(sImagesUri, TEST_BUFFER_SIZE);
- verify(mMockProvider, times(4)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+ verify(mMockProvider, times(4)).bulkInsert(eq(mAttributionSource), any(),
any());
}
@SmallTest
public void testInsertContentsMoreThanBufferSize() throws Exception {
- when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+ when(mMockProvider.bulkInsert(eq(mAttributionSource), any(),
any())).thenReturn(1);
fillBuffer(sFilesUri, TEST_BUFFER_SIZE + 1);
@@ -172,7 +177,7 @@
fillBuffer(sVideoUri, TEST_BUFFER_SIZE + 3);
fillBuffer(sImagesUri, TEST_BUFFER_SIZE + 4);
- verify(mMockProvider, times(4)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+ verify(mMockProvider, times(4)).bulkInsert(eq(mAttributionSource), any(),
any());
}
@@ -183,7 +188,7 @@
@SmallTest
public void testFlushAllWithSomeContents() throws Exception {
- when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+ when(mMockProvider.bulkInsert(eq(mAttributionSource), any(),
any())).thenReturn(1);
fillBuffer(sFilesUri, TEST_BUFFER_SIZE - 4);
@@ -192,13 +197,13 @@
fillBuffer(sImagesUri, TEST_BUFFER_SIZE - 1);
mMediaInserter.flushAll();
- verify(mMockProvider, times(4)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+ verify(mMockProvider, times(4)).bulkInsert(eq(mAttributionSource), any(),
any());
}
@SmallTest
public void testInsertContentsAfterFlushAll() throws Exception {
- when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+ when(mMockProvider.bulkInsert(eq(mAttributionSource), any(),
any())).thenReturn(1);
fillBuffer(sFilesUri, TEST_BUFFER_SIZE - 4);
@@ -212,19 +217,19 @@
fillBuffer(sVideoUri, TEST_BUFFER_SIZE + 3);
fillBuffer(sImagesUri, TEST_BUFFER_SIZE + 4);
- verify(mMockProvider, times(8)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), any(),
+ verify(mMockProvider, times(8)).bulkInsert(eq(mAttributionSource), any(),
any());
}
@SmallTest
public void testInsertContentsWithDifferentSizePerContentType() throws Exception {
- when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), eqUri(sFilesUri),
+ when(mMockProvider.bulkInsert(eq(mAttributionSource), eqUri(sFilesUri),
any())).thenReturn(1);
- when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), eqUri(sAudioUri),
+ when(mMockProvider.bulkInsert(eq(mAttributionSource), eqUri(sAudioUri),
any())).thenReturn(1);
- when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), eqUri(sVideoUri),
+ when(mMockProvider.bulkInsert(eq(mAttributionSource), eqUri(sVideoUri),
any())).thenReturn(1);
- when(mMockProvider.bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID), eqUri(sImagesUri),
+ when(mMockProvider.bulkInsert(eq(mAttributionSource), eqUri(sImagesUri),
any())).thenReturn(1);
for (int i = 0; i < TEST_BUFFER_SIZE; ++i) {
@@ -234,13 +239,13 @@
fillBuffer(sImagesUri, 4);
}
- verify(mMockProvider, times(1)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID),
+ verify(mMockProvider, times(1)).bulkInsert(eq(mAttributionSource),
eqUri(sFilesUri), any());
- verify(mMockProvider, times(2)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID),
+ verify(mMockProvider, times(2)).bulkInsert(eq(mAttributionSource),
eqUri(sAudioUri), any());
- verify(mMockProvider, times(3)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID),
+ verify(mMockProvider, times(3)).bulkInsert(eq(mAttributionSource),
eqUri(sVideoUri), any());
- verify(mMockProvider, times(4)).bulkInsert(eq(mPackageName), eq(TEST_FEATURE_ID),
+ verify(mMockProvider, times(4)).bulkInsert(eq(mAttributionSource),
eqUri(sImagesUri), any());
}
}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 087275e..801b490 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.usage.StorageStatsManager;
+import android.content.AttributionSource;
import android.content.ContentResolver;
import android.content.UriPermission;
import android.database.Cursor;
@@ -28,7 +29,6 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
-import android.os.IBinder;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.DiskInfo;
@@ -141,17 +141,17 @@
}
@Override
- protected int enforceReadPermissionInner(Uri uri, String callingPkg,
- @Nullable String featureId, IBinder callerToken) throws SecurityException {
+ protected int enforceReadPermissionInner(Uri uri,
+ @NonNull AttributionSource attributionSource) throws SecurityException {
enforceShellRestrictions();
- return super.enforceReadPermissionInner(uri, callingPkg, featureId, callerToken);
+ return super.enforceReadPermissionInner(uri, attributionSource);
}
@Override
- protected int enforceWritePermissionInner(Uri uri, String callingPkg,
- @Nullable String featureId, IBinder callerToken) throws SecurityException {
+ protected int enforceWritePermissionInner(Uri uri,
+ @NonNull AttributionSource attributionSource) throws SecurityException {
enforceShellRestrictions();
- return super.enforceWritePermissionInner(uri, callingPkg, featureId, callerToken);
+ return super.enforceWritePermissionInner(uri, attributionSource);
}
public void updateVolumes() {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index 49f6bd8..a2bec33 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -555,7 +555,7 @@
bundle.putString(META_DATA_PREFERENCE_KEYHINT, key);
}
try {
- return provider.call(context.getPackageName(), context.getAttributionTag(),
+ return provider.call(context.getAttributionSource(),
uri.getAuthority(), method, uri.toString(), bundle);
} catch (RemoteException e) {
return null;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index fdbbc39..df6ff73 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -18,6 +18,7 @@
import android.annotation.SystemApi;
import android.app.ActivityManager;
+import android.content.AttributionSource;
import android.content.IContentProvider;
import android.os.Binder;
import android.os.Bundle;
@@ -250,7 +251,8 @@
Bundle args = new Bundle();
args.putInt(Settings.CALL_METHOD_USER_KEY,
ActivityManager.getService().getCurrentUser().id);
- Bundle b = provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
+ Bundle b = provider.call(new AttributionSource(Process.myUid(),
+ resolveCallingPackage(), null), Settings.AUTHORITY,
Settings.CALL_METHOD_DELETE_CONFIG, compositeKey, args);
success = (b != null && b.getInt(SettingsProvider.RESULT_ROWS_DELETED) == 1);
} catch (RemoteException e) {
@@ -266,7 +268,8 @@
Bundle args = new Bundle();
args.putInt(Settings.CALL_METHOD_USER_KEY,
ActivityManager.getService().getCurrentUser().id);
- Bundle b = provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
+ Bundle b = provider.call(new AttributionSource(Process.myUid(),
+ resolveCallingPackage(), null), Settings.AUTHORITY,
Settings.CALL_METHOD_LIST_CONFIG, null, args);
if (b != null) {
Map<String, String> flagsToValues =
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
index 3b3ca5b..17ebf6f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
@@ -17,6 +17,7 @@
package com.android.providers.settings;
import android.app.ActivityManager;
+import android.content.AttributionSource;
import android.content.IContentProvider;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -309,7 +310,9 @@
try {
Bundle arg = new Bundle();
arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
- Bundle result = provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
+ final AttributionSource attributionSource = new AttributionSource(
+ Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null);
+ Bundle result = provider.call(attributionSource, Settings.AUTHORITY,
callListCommand, null, arg);
lines.addAll(result.getStringArrayList(SettingsProvider.RESULT_SETTINGS_LIST));
Collections.sort(lines);
@@ -334,7 +337,9 @@
try {
Bundle arg = new Bundle();
arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
- Bundle b = provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
+ final AttributionSource attributionSource = new AttributionSource(
+ Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null);
+ Bundle b = provider.call(attributionSource, Settings.AUTHORITY,
callGetCommand, key, arg);
if (b != null) {
result = b.getPairValue();
@@ -372,7 +377,9 @@
if (makeDefault) {
arg.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
- provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
+ final AttributionSource attributionSource = new AttributionSource(
+ Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null);
+ provider.call(attributionSource, Settings.AUTHORITY,
callPutCommand, key, arg);
} catch (RemoteException e) {
throw new RuntimeException("Failed in IPC", e);
@@ -396,7 +403,9 @@
try {
Bundle arg = new Bundle();
arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
- Bundle result = provider.call(resolveCallingPackage(), null, Settings.AUTHORITY,
+ final AttributionSource attributionSource = new AttributionSource(
+ Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null);
+ Bundle result = provider.call(attributionSource, Settings.AUTHORITY,
callDeleteCommand, key, arg);
return result.getInt(SettingsProvider.RESULT_ROWS_DELETED);
} catch (RemoteException e) {
@@ -423,7 +432,9 @@
}
String packageName = mPackageName != null ? mPackageName : resolveCallingPackage();
arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
- provider.call(packageName, null, Settings.AUTHORITY, callResetCommand, null, arg);
+ final AttributionSource attributionSource = new AttributionSource(
+ Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null);
+ provider.call(attributionSource, Settings.AUTHORITY, callResetCommand, null, arg);
} catch (RemoteException e) {
throw new RuntimeException("Failed in IPC", e);
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index cff8ad1..4c56db4 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -35,6 +35,8 @@
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
<uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
@@ -194,11 +196,14 @@
<uses-permission android:name="android.permission.MANAGE_CAMERA" />
<!-- Permissions needed to test system only camera devices -->
<uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.BACKGROUND_CAMERA" />
<uses-permission android:name="android.permission.SYSTEM_CAMERA" />
<!-- Permissions needed to test onCameraOpened/Closed callbacks -->
<uses-permission android:name="android.permission.CAMERA_OPEN_CLOSE_LISTENER" />
<!-- Permissions needed for CTS camera test: RecordingTest.java when assuming shell id -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.RECORD_BACKGROUND_AUDIO" />
+
<!-- Permission needed to enable/disable Bluetooth/Wifi -->
<uses-permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED" />
<uses-permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED" />
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 036b88e..fa88d66 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -188,6 +188,7 @@
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
+import android.content.AttributionSource;
import android.content.AutofillOptions;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
@@ -340,7 +341,11 @@
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.HeptFunction;
+import com.android.internal.util.function.HexFunction;
+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.server.AlarmManagerInternal;
import com.android.server.DeviceIdleInternal;
import com.android.server.DisplayThread;
@@ -367,6 +372,7 @@
import com.android.server.job.JobSchedulerInternal;
import com.android.server.os.NativeTombstoneManager;
import com.android.server.pm.Installer;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.uri.GrantUri;
import com.android.server.uri.NeededUriGrants;
@@ -1123,24 +1129,6 @@
CoreSettingsObserver mCoreSettingsObserver;
/**
- * Thread-local storage used to carry caller permissions over through
- * indirect content-provider access.
- */
- private class Identity {
- public final IBinder token;
- public final int pid;
- public final int uid;
-
- Identity(IBinder _token, int _pid, int _uid) {
- token = _token;
- pid = _pid;
- uid = _uid;
- }
- }
-
- private static final ThreadLocal<Identity> sCallerIdentity = new ThreadLocal<Identity>();
-
- /**
* All information we have collected about the runtime performance of
* any user id that can impact battery performance.
*/
@@ -5344,26 +5332,6 @@
return checkComponentPermission(permission, pid, uid, -1, true);
}
- @Override
- public int checkPermissionWithToken(String permission, int pid, int uid, IBinder callerToken) {
- if (permission == null) {
- return PackageManager.PERMISSION_DENIED;
- }
-
- // We might be performing an operation on behalf of an indirect binder
- // invocation, e.g. via {@link #openContentUri}. Check and adjust the
- // client identity accordingly before proceeding.
- Identity tlsIdentity = sCallerIdentity.get();
- if (tlsIdentity != null && tlsIdentity.token == callerToken) {
- Slog.d(TAG, "checkComponentPermission() adjusting {pid,uid} to {"
- + tlsIdentity.pid + "," + tlsIdentity.uid + "}");
- uid = tlsIdentity.uid;
- pid = tlsIdentity.pid;
- }
-
- return checkComponentPermission(permission, pid, uid, -1, true);
- }
-
/**
* Binder IPC calls go through the public entry point.
* This can be called with or without the global lock held.
@@ -5618,14 +5586,6 @@
final int modeFlags, int userId, IBinder callerToken) {
enforceNotIsolatedCaller("checkUriPermission");
- // Another redirected-binder-call permissions check as in
- // {@link checkPermissionWithToken}.
- Identity tlsIdentity = sCallerIdentity.get();
- if (tlsIdentity != null && tlsIdentity.token == callerToken) {
- uid = tlsIdentity.uid;
- pid = tlsIdentity.pid;
- }
-
// Our own process gets to do everything.
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
@@ -6159,23 +6119,22 @@
Binder.getCallingUid(), "*opencontent*", userId);
ParcelFileDescriptor pfd = null;
if (cph != null) {
- // We record the binder invoker's uid in thread-local storage before
- // going to the content provider to open the file. Later, in the code
- // that handles all permissions checks, we look for this uid and use
- // that rather than the Activity Manager's own uid. The effect is that
- // we do the check against the caller's permissions even though it looks
- // to the content provider like the Activity Manager itself is making
- // the request.
- Binder token = new Binder();
- sCallerIdentity.set(new Identity(
- token, Binder.getCallingPid(), Binder.getCallingUid()));
try {
- pfd = cph.provider.openFile(null, null, uri, "r", null, token);
+ // This method is exposed to the VNDK and to avoid changing its
+ // signature we just use the first package in the UID. For shared
+ // UIDs we may blame the wrong app but that is Okay as they are
+ // in the same security/privacy sandbox.
+ final AndroidPackage androidPackage = mPackageManagerInt
+ .getPackage(Binder.getCallingUid());
+ if (androidPackage == null) {
+ return null;
+ }
+ final AttributionSource attributionSource = new AttributionSource(
+ Binder.getCallingUid(), androidPackage.getPackageName(), null);
+ pfd = cph.provider.openFile(attributionSource, uri, "r", null);
} catch (FileNotFoundException e) {
// do nothing; pfd will be returned null
} finally {
- // Ensure that whatever happens, we clean up the identity state
- sCallerIdentity.remove();
// Ensure we're done with the provider.
mCpHelper.removeContentProviderExternalUnchecked(name, null, userId);
}
@@ -16639,6 +16598,74 @@
message, shouldCollectMessage);
}
+ @Override
+ public int noteProxyOperation(int code, @NonNull AttributionSource attributionSource,
+ boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage, boolean skiProxyOperation,
+ @NonNull HexFunction<Integer, AttributionSource, Boolean, String, Boolean,
+ Boolean, Integer> 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(code, new AttributionSource(shellUid,
+ "com.android.shell", attributionSource.getAttributionTag(),
+ attributionSource.getNext()),
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ skiProxyOperation);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ return superImpl.apply(code, attributionSource, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage, skiProxyOperation);
+ }
+
+ @Override
+ public int startProxyOperation(IBinder token, 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,
+ Integer> 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);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ return superImpl.apply(token, code, attributionSource, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProsyOperation);
+ }
+
+ @Override
+ public void finishProxyOperation(IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource,
+ @NonNull TriFunction<IBinder, Integer, AttributionSource, 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,
+ "com.android.shell", attributionSource.getAttributionTag(),
+ attributionSource.getNext()));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ superImpl.apply(clientId, code, attributionSource);
+ }
+
private boolean isTargetOp(int code) {
// null permissions means all ops are targeted
if (mPermissions == null) {
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index ee4526b..b44699b 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -33,6 +33,7 @@
import android.app.ContentProviderHolder;
import android.app.IApplicationThread;
import android.app.usage.UsageEvents.Event;
+import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -42,6 +43,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
import android.database.ContentObserver;
@@ -67,7 +69,9 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
import com.android.server.RescueParty;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -1037,7 +1041,19 @@
holder = getContentProviderExternalUnchecked(name, null, callingUid,
"*checkContentProviderUriPermission*", userId);
if (holder != null) {
- return holder.provider.checkUriPermission(null, null, uri, callingUid, modeFlags);
+
+ final PackageManagerInternal packageManagerInt = LocalServices.getService(
+ PackageManagerInternal.class);
+ final AndroidPackage androidPackage = packageManagerInt
+ .getPackage(Binder.getCallingUid());
+ if (androidPackage == null) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ final AttributionSource attributionSource = new AttributionSource(
+ callingUid, androidPackage.getPackageName(), null);
+ return holder.provider.checkUriPermission(attributionSource, uri, callingUid,
+ modeFlags);
}
} catch (RemoteException e) {
Log.w(TAG, "Content provider dead retrieving " + uri, e);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7bc7105..07ee5a2 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -94,6 +94,7 @@
import android.app.RuntimeAppOpAccessMessage;
import android.app.SyncNotedAppOp;
import android.app.admin.DevicePolicyManagerInternal;
+import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -131,6 +132,7 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.KeyValueListParser;
+import android.util.Log;
import android.util.LongSparseArray;
import android.util.Pair;
import android.util.Pools;
@@ -159,6 +161,9 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
+import com.android.internal.util.function.HeptFunction;
+import com.android.internal.util.function.QuintFunction;
+import com.android.internal.util.function.TriFunction;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
@@ -1999,8 +2004,10 @@
@Override
public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
- mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
+ final int callingUid = Binder.getCallingUid();
+ final boolean hasAllPackageAccess = mContext.checkPermission(
+ Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
+ Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
ArrayList<AppOpsManager.PackageOps> res = null;
synchronized (this) {
final int uidStateCount = mUidStates.size();
@@ -2016,11 +2023,14 @@
ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
if (resOps != null) {
if (res == null) {
- res = new ArrayList<AppOpsManager.PackageOps>();
+ res = new ArrayList<>();
}
AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
pkgOps.packageName, pkgOps.uidState.uid, resOps);
- res.add(resPackage);
+ // Caller can always see their packages and with a permission all.
+ if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
+ res.add(resPackage);
+ }
}
}
}
@@ -2031,8 +2041,7 @@
@Override
public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
int[] ops) {
- mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
+ enforceGetAppOpsStatsPermissionIfNeeded(uid,packageName);
String resolvedPackageName = resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
return Collections.emptyList();
@@ -2054,6 +2063,22 @@
}
}
+ private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ // We get to access everything
+ if (callingUid == Process.myPid()) {
+ return;
+ }
+ // Apps can access their own data
+ if (uid == callingUid && packageName != null
+ && checkPackage(uid, packageName) == MODE_ALLOWED) {
+ return;
+ }
+ // Otherwise, you need a permission...
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), callingUid, null);
+ }
+
/**
* Verify that historical appop request arguments are valid.
*/
@@ -3078,15 +3103,52 @@
}
@Override
- public int noteProxyOperation(int code, int proxiedUid, String proxiedPackageName,
- String proxiedAttributionTag, int proxyUid, String proxyPackageName,
- String proxyAttributionTag, boolean shouldCollectAsyncNotedOp, String message,
- boolean shouldCollectMessage) {
- verifyIncomingUid(proxyUid);
+ public int noteProxyOperation(int code, AttributionSource attributionSource,
+ boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+ boolean skipProxyOperation) {
+ final CheckOpsDelegate policy;
+ final CheckOpsDelegateDispatcher delegateDispatcher;
+ synchronized (AppOpsService.this) {
+ policy = mAppOpsPolicy;
+ delegateDispatcher = mCheckOpsDelegateDispatcher;
+ }
+ if (policy != null) {
+ if (delegateDispatcher != null) {
+ return policy.noteProxyOperation(code, attributionSource,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ skipProxyOperation, delegateDispatcher::noteProxyOperationImpl);
+ } else {
+ return policy.noteProxyOperation(code, attributionSource,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ skipProxyOperation, AppOpsService.this::noteProxyOperationImpl);
+ }
+ } else if (delegateDispatcher != null) {
+ delegateDispatcher.getCheckOpsDelegate().noteProxyOperation(code,
+ attributionSource, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skipProxyOperation,
+ AppOpsService.this::noteProxyOperationImpl);
+ }
+ return noteProxyOperationImpl(code, attributionSource, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage,skipProxyOperation);
+ }
+
+ private int noteProxyOperationImpl(int code, AttributionSource attributionSource,
+ boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+ boolean skipProxyOperation) {
+ final int proxyUid = attributionSource.getUid();
+ final String proxyPackageName = attributionSource.getPackageName();
+ final String proxyAttributionTag = attributionSource.getAttributionTag();
+ final int proxiedUid = attributionSource.getNextUid();
+ final String proxiedPackageName = attributionSource.getNextPackageName();
+ final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+
+ verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid));
verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid));
+ skipProxyOperation = resolveSkipProxyOperation(skipProxyOperation, attributionSource);
+
String resolveProxyPackageName = resolvePackageName(proxyUid, proxyPackageName);
if (resolveProxyPackageName == null) {
return AppOpsManager.MODE_IGNORED;
@@ -3097,19 +3159,23 @@
Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
== PackageManager.PERMISSION_GRANTED || isSelfBlame;
- final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
- : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
- final int proxyMode = noteOperationUnchecked(code, proxyUid, resolveProxyPackageName,
- proxyAttributionTag, Process.INVALID_UID, null, null, proxyFlags,
- !isProxyTrusted, "proxy " + message, shouldCollectMessage);
- if (proxyMode != AppOpsManager.MODE_ALLOWED) {
- return proxyMode;
+ if (!skipProxyOperation) {
+ final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
+ : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
+
+ final int proxyMode = noteOperationUnchecked(code, proxyUid, resolveProxyPackageName,
+ proxyAttributionTag, Process.INVALID_UID, null, null, proxyFlags,
+ !isProxyTrusted, "proxy " + message, shouldCollectMessage);
+ if (proxyMode != AppOpsManager.MODE_ALLOWED) {
+ return proxyMode;
+ }
}
String resolveProxiedPackageName = resolvePackageName(proxiedUid, proxiedPackageName);
if (resolveProxiedPackageName == null) {
return AppOpsManager.MODE_IGNORED;
}
+
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
@@ -3558,16 +3624,56 @@
}
@Override
- public int startProxyOperation(IBinder clientId, int code, int proxiedUid,
- String proxiedPackageName, @Nullable String proxiedAttributionTag, int proxyUid,
- String proxyPackageName, @Nullable String proxyAttributionTag,
+ public int startProxyOperation(IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
+ boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+ boolean skipProxyOperation) {
+ final CheckOpsDelegate policy;
+ final CheckOpsDelegateDispatcher delegateDispatcher;
+ synchronized (AppOpsService.this) {
+ policy = mAppOpsPolicy;
+ delegateDispatcher = mCheckOpsDelegateDispatcher;
+ }
+ if (policy != null) {
+ if (delegateDispatcher != null) {
+ return policy.startProxyOperation(clientId, code, attributionSource,
+ startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skipProxyOperation,
+ delegateDispatcher::startProxyOperationImpl);
+ } else {
+ return policy.startProxyOperation(clientId, code, attributionSource,
+ startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skipProxyOperation,
+ AppOpsService.this::startProxyOperationImpl);
+ }
+ } else if (delegateDispatcher != null) {
+ delegateDispatcher.getCheckOpsDelegate().startProxyOperation(clientId, code,
+ attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skipProxyOperation,
+ AppOpsService.this::startProxyOperationImpl);
+ }
+ return startProxyOperationImpl(clientId, code, attributionSource, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation);
+ }
+
+ private int startProxyOperationImpl(IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
- boolean shouldCollectMessage) {
- verifyIncomingUid(proxyUid);
+ boolean shouldCollectMessage, boolean skipProxyOperation) {
+ final int proxyUid = attributionSource.getUid();
+ final String proxyPackageName = attributionSource.getPackageName();
+ final String proxyAttributionTag = attributionSource.getAttributionTag();
+ final int proxiedUid = attributionSource.getNextUid();
+ final String proxiedPackageName = attributionSource.getNextPackageName();
+ final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+
+ verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid));
verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid));
+ skipProxyOperation = resolveSkipProxyOperation(skipProxyOperation, attributionSource);
+
String resolvedProxyPackageName = resolvePackageName(proxyUid, proxyPackageName);
if (resolvedProxyPackageName == null) {
return AppOpsManager.MODE_IGNORED;
@@ -3578,31 +3684,34 @@
Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
== PackageManager.PERMISSION_GRANTED || isSelfBlame;
- final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
- : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
-
String resolvedProxiedPackageName = resolvePackageName(proxiedUid, proxiedPackageName);
if (resolvedProxiedPackageName == null) {
return AppOpsManager.MODE_IGNORED;
}
+
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
- // Test if the proxied operation will succeed before starting the proxy operation
- final int testProxiedMode = startOperationUnchecked(clientId, code, proxiedUid,
- resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
- resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage, true);
- if (!shouldStartForMode(testProxiedMode, startIfModeDefault)) {
- return testProxiedMode;
- }
+ if (!skipProxyOperation) {
+ // Test if the proxied operation will succeed before starting the proxy operation
+ final int testProxiedMode = startOperationUnchecked(clientId, code, proxiedUid,
+ resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
+ resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, true);
+ if (!shouldStartForMode(testProxiedMode, startIfModeDefault)) {
+ return testProxiedMode;
+ }
- final int proxyMode = startOperationUnchecked(clientId, code, proxyUid,
- resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
- proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
- shouldCollectMessage, false);
- if (!shouldStartForMode(proxyMode, startIfModeDefault)) {
- return proxyMode;
+ final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
+ : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
+
+ final int proxyMode = startOperationUnchecked(clientId, code, proxyUid,
+ resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
+ proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
+ shouldCollectMessage, false);
+ if (!shouldStartForMode(proxyMode, startIfModeDefault)) {
+ return proxyMode;
+ }
}
return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
@@ -3723,9 +3832,38 @@
}
@Override
- public void finishProxyOperation(IBinder clientId, int code, int proxiedUid,
- String proxiedPackageName, @Nullable String proxiedAttributionTag, int proxyUid,
- @Nullable String proxyPackageName, @Nullable String proxyAttributionTag) {
+ public void finishProxyOperation(IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource) {
+ final CheckOpsDelegate policy;
+ final CheckOpsDelegateDispatcher delegateDispatcher;
+ synchronized (AppOpsService.this) {
+ policy = mAppOpsPolicy;
+ delegateDispatcher = mCheckOpsDelegateDispatcher;
+ }
+ if (policy != null) {
+ if (delegateDispatcher != null) {
+ policy.finishProxyOperation(clientId, code, attributionSource,
+ delegateDispatcher::finishProxyOperationImpl);
+ } else {
+ policy.finishProxyOperation(clientId, code, attributionSource,
+ AppOpsService.this::finishProxyOperationImpl);
+ }
+ } else if (delegateDispatcher != null) {
+ delegateDispatcher.getCheckOpsDelegate().finishProxyOperation(clientId, code,
+ attributionSource, AppOpsService.this::finishProxyOperationImpl);
+ }
+ finishProxyOperationImpl(clientId, code, attributionSource);
+ }
+
+ private Void finishProxyOperationImpl(IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource) {
+ final int proxyUid = attributionSource.getUid();
+ final String proxyPackageName = attributionSource.getPackageName();
+ final String proxyAttributionTag = attributionSource.getAttributionTag();
+ final int proxiedUid = attributionSource.getNextUid();
+ final String proxiedPackageName = attributionSource.getNextPackageName();
+ final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+
verifyIncomingUid(proxyUid);
verifyIncomingOp(code);
verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid));
@@ -3733,7 +3871,7 @@
String resolvedProxyPackageName = resolvePackageName(proxyUid, proxyPackageName);
if (resolvedProxyPackageName == null) {
- return;
+ return null;
}
finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
@@ -3741,11 +3879,13 @@
String resolvedProxiedPackageName = resolvePackageName(proxiedUid, proxiedPackageName);
if (resolvedProxiedPackageName == null) {
- return;
+ return null;
}
finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
proxiedAttributionTag);
+
+ return null;
}
private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
@@ -3953,6 +4093,20 @@
|| (permInfo.getProtectionFlags() & PROTECTION_FLAG_APPOP) != 0;
}
+ private void verifyIncomingProxyUid(@NonNull AttributionSource attributionSource) {
+ if (attributionSource.getUid() == Binder.getCallingUid()) {
+ return;
+ }
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return;
+ }
+ if (attributionSource.isTrusted(mContext)) {
+ return;
+ }
+ mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ }
+
private void verifyIncomingUid(int uid) {
if (uid == Binder.getCallingUid()) {
return;
@@ -3979,6 +4133,20 @@
}
}
+ private boolean resolveSkipProxyOperation(boolean requestsSkipProxyOperation,
+ @NonNull AttributionSource attributionSource) {
+ if (!requestsSkipProxyOperation) {
+ return false;
+ }
+ if (attributionSource.getUid() != Binder.getCallingUid()
+ && attributionSource.isTrusted(mContext)) {
+ return true;
+ }
+ return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
UidState uidState = mUidStates.get(uid);
if (uidState == null) {
@@ -4098,7 +4266,6 @@
/**
* Create a restriction description matching the properties of the package.
*
- * @param context A context to use
* @param pkg The package to create the restriction description for
*
* @return The restriction matching the package
@@ -4141,15 +4308,25 @@
int callingUid = Binder.getCallingUid();
int userId = UserHandle.getUserId(uid);
-
RestrictionBypass bypass = null;
+
+ // Allow any attribution tag for resolvable uids
+ int pkgUid = resolveUid(packageName);
+ if (pkgUid != Process.INVALID_UID) {
+ // Special case for the shell which is a package but should be able
+ // to bypass app attribution tag restrictions.
+ if (pkgUid != UserHandle.getAppId(uid)) {
+ throw new SecurityException("Specified package " + packageName + " under uid "
+ + UserHandle.getAppId(uid) + " but it is really " + pkgUid);
+ }
+ return RestrictionBypass.UNRESTRICTED;
+ }
+
final long ident = Binder.clearCallingIdentity();
try {
- int pkgUid;
- AndroidPackage pkg = LocalServices.getService(PackageManagerInternal.class).getPackage(
- packageName);
boolean isAttributionTagValid = false;
-
+ AndroidPackage pkg = LocalServices.getService(PackageManagerInternal.class)
+ .getPackage(packageName);
if (pkg != null) {
if (attributionTag == null) {
isAttributionTagValid = true;
@@ -4166,20 +4343,7 @@
pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
bypass = getBypassforPackage(pkg);
- } else {
- // Allow any attribution tag for resolvable uids
- isAttributionTagValid = true;
-
- pkgUid = resolveUid(packageName);
- if (pkgUid >= 0) {
- bypass = RestrictionBypass.UNRESTRICTED;
- }
}
- if (pkgUid != uid) {
- throw new SecurityException("Specified package " + packageName + " under uid " + uid
- + " but it is really " + pkgUid);
- }
-
if (!isAttributionTagValid) {
String msg = "attributionTag " + attributionTag + " not declared in"
+ " manifest of " + packageName;
@@ -4187,7 +4351,7 @@
if (mPlatformCompat.isChangeEnabledByPackageName(
SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
userId) && mPlatformCompat.isChangeEnabledByUid(
- SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, callingUid)) {
+ SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, callingUid)) {
throw new SecurityException(msg);
} else {
Slog.e(TAG, msg);
@@ -4199,6 +4363,11 @@
Binder.restoreCallingIdentity(ident);
}
+ if (pkgUid != uid) {
+ throw new SecurityException("Specified package " + packageName + " under uid " + uid
+ + " but it is really " + pkgUid);
+ }
+
return bypass;
}
@@ -6103,6 +6272,57 @@
}
@Override
+ public boolean isProxying(int op, @NonNull String proxyPackageName,
+ @NonNull String proxyAttributionTag, int proxiedUid,
+ @NonNull String proxiedPackageName) {
+ Objects.requireNonNull(proxyPackageName);
+ Objects.requireNonNull(proxiedPackageName);
+ Binder.withCleanCallingIdentity(() -> {
+ final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
+ proxiedPackageName, new int[] {op});
+ if (packageOps == null || packageOps.isEmpty()) {
+ return false;
+ }
+ final List<OpEntry> opEntries = packageOps.get(0).getOps();
+ if (opEntries.isEmpty()) {
+ return false;
+ }
+ final OpEntry opEntry = opEntries.get(0);
+ if (!opEntry.isRunning()) {
+ return false;
+ }
+ final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
+ AppOpsManager.OP_FLAG_TRUSTED_PROXY
+ | AppOpsManager.OP_FLAG_UNTRUSTED_PROXY);
+ return proxyInfo != null && Binder.getCallingUid() == proxyInfo.getUid()
+ && proxyPackageName.equals(proxyInfo.getPackageName())
+ && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
+ });
+ return false;
+ }
+
+ @Override
+ public void resetPackageOpsNoHistory(@NonNull String packageName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "resetPackageOpsNoHistory");
+ synchronized (AppOpsService.this) {
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
+ UserHandle.getCallingUserId());
+ if (uid == Process.INVALID_UID) {
+ return;
+ }
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+ Ops removedOps = uidState.pkgOps.remove(packageName);
+ if (removedOps != null) {
+ scheduleFastWriteLocked();
+ }
+ }
+ }
+
+ @Override
public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
long baseSnapshotInterval, int compressionStep) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
@@ -6455,6 +6675,7 @@
return Process.ROOT_UID;
case "shell":
case "dumpstate":
+ case "com.android.shell":
return Process.SHELL_UID;
case "media":
return Process.MEDIA_UID;
@@ -6831,5 +7052,29 @@
shouldCollectAsyncNotedOp, message, shouldCollectMessage,
AppOpsService.this::noteOperationImpl);
}
+
+ public int noteProxyOperationImpl(int code, @NonNull AttributionSource attributionSource,
+ boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage, boolean skipProxyOperation) {
+ return mCheckOpsDelegate.noteProxyOperation(code, attributionSource,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
+ AppOpsService.this::noteProxyOperationImpl);
+ }
+
+ public int startProxyOperationImpl(IBinder token, int code,
+ @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
+ boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+ boolean skipProxyOperation) {
+ return mCheckOpsDelegate.startProxyOperation(token, code, attributionSource,
+ startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ skipProxyOperation, AppOpsService.this::startProxyOperationImpl);
+ }
+
+ public Void finishProxyOperationImpl(IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource) {
+ mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
+ AppOpsService.this::finishProxyOperationImpl);
+ return null;
+ }
}
}
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 920d044..142b36e 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -85,11 +85,13 @@
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.metrics.LogMaker;
import android.os.AsyncTask;
+import android.content.AttributionSource;
import android.os.Binder;
import android.os.Build;
import android.os.Debug;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
@@ -159,6 +161,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@@ -254,6 +257,10 @@
@NonNull
private final PermissionRegistry mRegistry = new PermissionRegistry();
+ @NonNull
+ private final AttributionSourceRegistry mAttributionSourceRegistry =
+ new AttributionSourceRegistry();
+
@GuardedBy("mLock")
@Nullable
private ArraySet<String> mPrivappPermissionsViolations;
@@ -3231,6 +3238,16 @@
}
@Override
+ public @NonNull AttributionSource registerAttributionSource(@NonNull AttributionSource source) {
+ return mAttributionSourceRegistry.registerAttributionSource(source);
+ }
+
+ @Override
+ public boolean isRegisteredAttributionSource(@NonNull AttributionSource source) {
+ return mAttributionSourceRegistry.isRegisteredAttributionSource(source);
+ }
+
+ @Override
public List<String> getAutoRevokeExemptionRequestedPackages(int userId) {
return getPackagesWithAutoRevokePolicy(AUTO_REVOKE_DISCOURAGED, userId);
}
@@ -5265,4 +5282,73 @@
|| mDelegatedPermissionNames.contains(permissionName);
}
}
+
+ private static final class AttributionSourceRegistry {
+ private final Object mLock = new Object();
+
+ private final WeakHashMap<IBinder, AttributionSource> mAttributions = new WeakHashMap<>();
+
+ public @NonNull AttributionSource 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
+ // to itself inclusive if and only if it is adding a node for itself which
+ // optionally points to an attribution chain that was created by each
+ // preceding app recursively up to the beginning of the chain.
+ // The only special case is when the first app in the attribution chain
+ // creates a source that points to another app (not a chain of apps). We
+ // allow this even if the source the app points to is not registered since
+ // in app ops we allow every app to blame every other app (untrusted if not
+ // holding a special permission).
+ // This technique ensures that a bad actor in the middle of the attribution
+ // chain can neither prepend nor append an invalid attribution sequence, i.e.
+ // a sequence that is not constructed by trusted sources up to the that bad
+ // actor's app.
+ // Note that passing your attribution source to another app means you allow
+ // it to blame private data access on your app. This can be mediated by the OS
+ // in, which case security is already enforced; by other app's code running in
+ // your process calling into the other app, in which case it can already access
+ // the private data in your process; or by you explicitly calling to another
+ // app passing the source, in which case you must trust the other side;
+
+ final int callingUid = Binder.getCallingUid();
+ if (source.getUid() != callingUid) {
+ throw new SecurityException("Cannot register attribution source for uid:"
+ + source.getUid() + " from uid:" + callingUid);
+ }
+
+ final PackageManagerInternal packageManagerInternal = LocalServices.getService(
+ PackageManagerInternal.class);
+ if (packageManagerInternal.getPackageUid(source.getPackageName(), 0,
+ UserHandle.getUserId(callingUid)) != source.getUid()) {
+ throw new SecurityException("Cannot register attribution source for package:"
+ + source.getPackageName() + " from uid:" + callingUid);
+ }
+
+ final AttributionSource next = source.getNext();
+ if (next != null && next.getNext() != null
+ && !isRegisteredAttributionSource(next)) {
+ throw new SecurityException("Cannot register forged attribution source:"
+ + source);
+ }
+
+ synchronized (mLock) {
+ final IBinder token = new Binder();
+ final AttributionSource result = source.withToken(token);
+ mAttributions.put(token, result);
+ return result;
+ }
+ }
+
+ public boolean isRegisteredAttributionSource(@NonNull AttributionSource source) {
+ synchronized (mLock) {
+ final AttributionSource cachedSource = mAttributions.get(source.getToken());
+ if (cachedSource != null) {
+ return cachedSource.equals(source);
+ }
+ return false;
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index c965390..5db63c6 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -20,13 +20,18 @@
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal;
+import android.content.AttributionSource;
import android.location.LocationManagerInternal;
+import android.os.IBinder;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.HeptFunction;
+import com.android.internal.util.function.HexFunction;
+import com.android.internal.util.function.OctFunction;
import com.android.internal.util.function.QuadFunction;
+import com.android.internal.util.function.TriFunction;
import com.android.server.LocalServices;
import java.util.Set;
@@ -52,7 +57,7 @@
@GuardedBy("mLock - writes only - see above")
@NonNull
private final ConcurrentHashMap<Integer, ArrayMap<String, ArraySet<String>>> mLocationTags =
- new ConcurrentHashMap();
+ new ConcurrentHashMap<>();
public AppOpsPolicy() {
final LocationManagerInternal locationManagerInternal = LocalServices.getService(
@@ -112,22 +117,59 @@
@Override
public int noteOperation(int code, int uid, @Nullable String packageName,
- @Nullable String featureId, boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer, String, String,
- Boolean, String, Boolean, Integer> superImpl) {
- if (isHandledOp(code)) {
+ @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable
+ String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer,
+ String, String, Boolean, String, Boolean, Integer> superImpl) {
+ return superImpl.apply(resolveOpCode(code, uid, packageName, attributionTag), uid,
+ packageName, attributionTag, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage);
+ }
+
+ @Override
+ public int noteProxyOperation(int code, @NonNull AttributionSource attributionSource,
+ boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage, boolean skipProxyOperation, @NonNull HexFunction<Integer,
+ AttributionSource, Boolean, String, Boolean, Boolean, Integer> superImpl) {
+ return superImpl.apply(resolveOpCode(code, attributionSource.getUid(),
+ attributionSource.getPackageName(), attributionSource.getAttributionTag()),
+ attributionSource, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ skipProxyOperation);
+ }
+
+ @Override
+ public int 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, Integer> superImpl) {
+ return superImpl.apply(token, resolveOpCode(code, attributionSource.getUid(),
+ attributionSource.getPackageName(), attributionSource.getAttributionTag()),
+ attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skipProxyOperation);
+ }
+
+ @Override
+ public void finishProxyOperation(IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource,
+ @NonNull TriFunction<IBinder, Integer, AttributionSource, Void> superImpl) {
+ superImpl.apply(clientId, resolveOpCode(code, attributionSource.getUid(),
+ attributionSource.getPackageName(), attributionSource.getAttributionTag()),
+ attributionSource);
+ }
+
+ private int resolveOpCode(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag) {
+ if (isHandledOp(code) && attributionTag != null) {
// Only a single lookup from the underlying concurrent data structure
final ArrayMap<String, ArraySet<String>> uidTags = mLocationTags.get(uid);
if (uidTags != null) {
final ArraySet<String> packageTags = uidTags.get(packageName);
- if (packageTags != null && packageTags.contains(featureId)) {
- return superImpl.apply(resolveLocationOp(code), uid, packageName, featureId,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ if (packageTags != null && packageTags.contains(attributionTag)) {
+ return resolveHandledOp(code);
}
}
}
- return superImpl.apply(code, uid, packageName, featureId, shouldCollectAsyncNotedOp,
- message, shouldCollectMessage);
+ return code;
}
private static boolean isHandledOp(int code) {
@@ -139,7 +181,7 @@
return false;
}
- private static int resolveLocationOp(int code) {
+ private static int resolveHandledOp(int code) {
switch (code) {
case AppOpsManager.OP_FINE_LOCATION:
return AppOpsManager.OP_FINE_LOCATION_SOURCE;
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 9077433..89d5415 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -578,7 +578,7 @@
private final @NonNull AppOpsManager mAppOpsManager;
private final @NonNull AppOpsManagerInternal mAppOpsManagerInternal;
- private final @NonNull ArrayMap<String, PermissionInfo> mRuntimePermissionInfos;
+ private final @NonNull ArrayMap<String, PermissionInfo> mRuntimeAndTheirBgPermissionInfos;
/**
* All ops that need to be flipped to allow.
@@ -618,7 +618,7 @@
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mAppOpsManagerInternal = LocalServices.getService(AppOpsManagerInternal.class);
- mRuntimePermissionInfos = new ArrayMap<>();
+ mRuntimeAndTheirBgPermissionInfos = new ArrayMap<>();
PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService(
PermissionManagerServiceInternal.class);
List<PermissionInfo> permissionInfos =
@@ -627,7 +627,30 @@
int permissionInfosSize = permissionInfos.size();
for (int i = 0; i < permissionInfosSize; i++) {
PermissionInfo permissionInfo = permissionInfos.get(i);
- mRuntimePermissionInfos.put(permissionInfo.name, permissionInfo);
+ mRuntimeAndTheirBgPermissionInfos.put(permissionInfo.name, permissionInfo);
+ // Make sure we scoop up all background permissions as they may not be runtime
+ if (permissionInfo.backgroundPermission != null) {
+ String backgroundNonRuntimePermission = permissionInfo.backgroundPermission;
+ for (int j = 0; j < permissionInfosSize; j++) {
+ PermissionInfo bgPermissionCandidate = permissionInfos.get(j);
+ if (permissionInfo.backgroundPermission.equals(
+ bgPermissionCandidate.name)) {
+ backgroundNonRuntimePermission = null;
+ break;
+ }
+ }
+ if (backgroundNonRuntimePermission != null) {
+ try {
+ PermissionInfo backgroundPermissionInfo = mPackageManager
+ .getPermissionInfo(backgroundNonRuntimePermission, 0);
+ mRuntimeAndTheirBgPermissionInfos.put(backgroundPermissionInfo.name,
+ backgroundPermissionInfo);
+ } catch (NameNotFoundException e) {
+ Slog.w(LOG_TAG, "Unknown background permission: "
+ + backgroundNonRuntimePermission);
+ }
+ }
+ }
}
}
@@ -691,7 +714,7 @@
*/
private void addAppOps(@NonNull PackageInfo packageInfo, @NonNull AndroidPackage pkg,
@NonNull String permissionName) {
- PermissionInfo permissionInfo = mRuntimePermissionInfos.get(permissionName);
+ PermissionInfo permissionInfo = mRuntimeAndTheirBgPermissionInfos.get(permissionName);
if (permissionInfo == null) {
return;
}
@@ -726,7 +749,7 @@
boolean shouldGrantAppOp = shouldGrantAppOp(packageInfo, pkg, permissionInfo);
if (shouldGrantAppOp) {
if (permissionInfo.backgroundPermission != null) {
- PermissionInfo backgroundPermissionInfo = mRuntimePermissionInfos.get(
+ PermissionInfo backgroundPermissionInfo = mRuntimeAndTheirBgPermissionInfos.get(
permissionInfo.backgroundPermission);
boolean shouldGrantBackgroundAppOp = backgroundPermissionInfo != null
&& shouldGrantAppOp(packageInfo, pkg, backgroundPermissionInfo);
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index 3f20302..9c8ff68 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -18,12 +18,12 @@
import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
+import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.AppOpsManager;
+import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
import android.speech.IRecognitionListener;
@@ -40,10 +40,6 @@
private static final String TAG = RemoteSpeechRecognitionService.class.getSimpleName();
private static final boolean DEBUG = false;
- private static final String APP_OP_MESSAGE = "Recording audio for speech recognition";
- private static final String RECORD_AUDIO_APP_OP =
- AppOpsManager.permissionToOp(android.Manifest.permission.RECORD_AUDIO);
-
private final Object mLock = new Object();
private boolean mConnected = false;
@@ -53,14 +49,6 @@
@Nullable
@GuardedBy("mLock")
- private String mPackageName;
-
- @Nullable
- @GuardedBy("mLock")
- private String mFeatureId;
-
- @Nullable
- @GuardedBy("mLock")
private DelegatingListener mDelegatingListener;
// Makes sure we can block startListening() if session is still in progress.
@@ -72,7 +60,6 @@
private boolean mRecordingInProgress = false;
private final int mCallingUid;
- private final AppOpsManager mAppOpsManager;
private final ComponentName mComponentName;
RemoteSpeechRecognitionService(
@@ -87,7 +74,6 @@
IRecognitionService.Stub::asInterface);
mCallingUid = callingUid;
- mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mComponentName = serviceName;
if (DEBUG) {
@@ -99,11 +85,12 @@
return mComponentName;
}
- void startListening(Intent recognizerIntent, IRecognitionListener listener, String packageName,
- String featureId) {
+ void startListening(Intent recognizerIntent, IRecognitionListener listener,
+ @NonNull AttributionSource attributionSource) {
if (DEBUG) {
Slog.i(TAG, String.format("#startListening for package: %s, feature=%s, callingUid=%d",
- packageName, featureId, mCallingUid));
+ attributionSource.getPackageName(), attributionSource.getAttributionTag(),
+ mCallingUid));
}
if (listener == null) {
@@ -123,10 +110,6 @@
return;
}
- if (startProxyOp(packageName, featureId) != AppOpsManager.MODE_ALLOWED) {
- tryRespondWithError(listener, SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
- return;
- }
mSessionInProgress = true;
mRecordingInProgress = true;
@@ -141,23 +124,18 @@
resetStateLocked();
}
});
- mPackageName = packageName;
- mFeatureId = featureId;
run(service ->
service.startListening(
recognizerIntent,
mDelegatingListener,
- packageName,
- featureId,
- mCallingUid));
+ attributionSource));
}
}
- void stopListening(
- IRecognitionListener listener, String packageName, String featureId) {
+ void stopListening(IRecognitionListener listener) {
if (DEBUG) {
- Slog.i(TAG, "#stopListening for package: " + packageName + ", feature=" + featureId);
+ Slog.i(TAG, "#stopListening");
}
if (!mConnected) {
@@ -184,19 +162,13 @@
}
mRecordingInProgress = false;
- finishProxyOp(packageName, featureId);
-
- run(service -> service.stopListening(mDelegatingListener, packageName, featureId));
+ run(service -> service.stopListening(mDelegatingListener));
}
}
- void cancel(
- IRecognitionListener listener,
- String packageName,
- String featureId,
- boolean isShutdown) {
+ void cancel(IRecognitionListener listener, boolean isShutdown) {
if (DEBUG) {
- Slog.i(TAG, "#cancel for package: " + packageName + ", feature=" + featureId);
+ Slog.i(TAG, "#cancel");
}
if (!mConnected) {
@@ -220,11 +192,8 @@
// Temporary reference to allow for resetting the hard link mDelegatingListener to null.
IRecognitionListener delegatingListener = mDelegatingListener;
- run(service -> service.cancel(delegatingListener, packageName, featureId, isShutdown));
+ run(service -> service.cancel(delegatingListener, isShutdown));
- if (mRecordingInProgress) {
- finishProxyOp(packageName, featureId);
- }
mRecordingInProgress = false;
mSessionInProgress = false;
@@ -249,7 +218,7 @@
}
}
- cancel(mListener, mPackageName, mFeatureId, true /* isShutdown */);
+ cancel(mListener, true /* isShutdown */);
}
@Override // from ServiceConnector.Impl
@@ -286,40 +255,12 @@
}
private void resetStateLocked() {
- if (mRecordingInProgress && mPackageName != null) {
- finishProxyOp(mPackageName, mFeatureId);
- }
-
mListener = null;
mDelegatingListener = null;
mSessionInProgress = false;
mRecordingInProgress = false;
}
- private int startProxyOp(String packageName, String featureId) {
- final long identity = Binder.clearCallingIdentity();
- try {
- return mAppOpsManager.startProxyOp(
- RECORD_AUDIO_APP_OP,
- mCallingUid,
- packageName,
- featureId,
- APP_OP_MESSAGE);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- private void finishProxyOp(String packageName, String featureId) {
- final long identity = Binder.clearCallingIdentity();
- try {
- mAppOpsManager.finishProxyOp(
- RECORD_AUDIO_APP_OP, mCallingUid, packageName, featureId);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
private static void tryRespondWithError(IRecognitionListener listener, int errorCode) {
if (DEBUG) {
Slog.i(TAG, "Responding with error " + errorCode);
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java
index 52c1467b..22eeb34 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java
@@ -75,7 +75,7 @@
@Override
protected SpeechRecognitionManagerServiceImpl newServiceLocked(
@UserIdInt int resolvedUserId, boolean disabled) {
- return new SpeechRecognitionManagerServiceImpl(this, mLock, resolvedUserId, disabled);
+ return new SpeechRecognitionManagerServiceImpl(this, mLock, resolvedUserId);
}
final class SpeechRecognitionManagerServiceStub extends IRecognitionServiceManager.Stub {
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index 769e049..eee08c2 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
+import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -36,8 +37,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.server.infra.AbstractPerUserSystemService;
-import com.google.android.collect.Sets;
-
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -60,7 +59,7 @@
SpeechRecognitionManagerServiceImpl(
@NonNull SpeechRecognitionManagerService master,
- @NonNull Object lock, @UserIdInt int userId, boolean disabled) {
+ @NonNull Object lock, @UserIdInt int userId) {
super(master, lock, userId);
}
@@ -108,10 +107,6 @@
}
final int creatorCallingUid = Binder.getCallingUid();
- Set<String> creatorPackageNames =
- Sets.newArraySet(
- getContext().getPackageManager().getPackagesForUid(creatorCallingUid));
-
RemoteSpeechRecognitionService service = createService(creatorCallingUid, serviceComponent);
if (service == null) {
@@ -127,6 +122,7 @@
} catch (RemoteException e) {
// RemoteException == binder already died, schedule disconnect anyway.
handleClientDeath(creatorCallingUid, service, true /* invoke #cancel */);
+ return;
}
service.connect().thenAccept(binderService -> {
@@ -137,41 +133,24 @@
public void startListening(
Intent recognizerIntent,
IRecognitionListener listener,
- String packageName,
- String featureId,
- int callingUid) throws RemoteException {
- verifyCallerIdentity(
- creatorCallingUid, packageName, creatorPackageNames, listener);
- if (callingUid != creatorCallingUid) {
- listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
- return;
- }
-
- service.startListening(
- recognizerIntent, listener, packageName, featureId);
+ @NonNull AttributionSource attributionSource)
+ throws RemoteException {
+ attributionSource.enforceCallingUid();
+ service.startListening(recognizerIntent, listener, attributionSource);
}
@Override
public void stopListening(
- IRecognitionListener listener,
- String packageName,
- String featureId) throws RemoteException {
- verifyCallerIdentity(
- creatorCallingUid, packageName, creatorPackageNames, listener);
-
- service.stopListening(listener, packageName, featureId);
+ IRecognitionListener listener) throws RemoteException {
+ service.stopListening(listener);
}
@Override
public void cancel(
IRecognitionListener listener,
- String packageName,
- String featureId,
boolean isShutdown) throws RemoteException {
- verifyCallerIdentity(
- creatorCallingUid, packageName, creatorPackageNames, listener);
- service.cancel(listener, packageName, featureId, isShutdown);
+ service.cancel(listener, isShutdown);
if (isShutdown) {
handleClientDeath(
@@ -192,17 +171,6 @@
});
}
- private void verifyCallerIdentity(
- int creatorCallingUid,
- String packageName,
- Set<String> creatorPackageNames,
- IRecognitionListener listener) throws RemoteException {
- if (creatorCallingUid != Binder.getCallingUid()
- || !creatorPackageNames.contains(packageName)) {
- listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
- }
- }
-
private void handleClientDeath(
int callingUid,
RemoteSpeechRecognitionService service, boolean invokeCancel) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index acda4d5..04c144a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -71,6 +71,7 @@
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.content.AttributionSource;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
@@ -86,6 +87,7 @@
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -207,37 +209,35 @@
throw new UnsupportedOperationException("unimplemented mock method");
});
doAnswer(invocation -> {
- String callingPkg = invocation.getArgument(0);
- String featureId = invocation.getArgument(1);
- Uri uri = invocation.getArgument(2);
- RemoteCallback cb = invocation.getArgument(3);
+ AttributionSource attributionSource = invocation.getArgument(0);
+ Uri uri = invocation.getArgument(1);
+ RemoteCallback cb = invocation.getArgument(2);
IContentProvider mock = (IContentProvider) (invocation.getMock());
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
final Bundle bundle = new Bundle();
try {
bundle.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
- mock.canonicalize(callingPkg, featureId, uri));
+ mock.canonicalize(attributionSource, uri));
} catch (RemoteException e) { /* consume */ }
cb.sendResult(bundle);
});
return null;
- }).when(mTestIContentProvider).canonicalizeAsync(any(), any(), any(), any());
+ }).when(mTestIContentProvider).canonicalizeAsync(any(), any(), any());
doAnswer(invocation -> {
- String callingPkg = invocation.getArgument(0);
- String featureId = invocation.getArgument(1);
- Uri uri = invocation.getArgument(2);
- RemoteCallback cb = invocation.getArgument(3);
+ AttributionSource attributionSource = invocation.getArgument(0);
+ Uri uri = invocation.getArgument(1);
+ RemoteCallback cb = invocation.getArgument(2);
IContentProvider mock = (IContentProvider) (invocation.getMock());
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
final Bundle bundle = new Bundle();
try {
bundle.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
- mock.uncanonicalize(callingPkg, featureId, uri));
+ mock.uncanonicalize(attributionSource, uri));
} catch (RemoteException e) { /* consume */ }
cb.sendResult(bundle);
});
return null;
- }).when(mTestIContentProvider).uncanonicalizeAsync(any(), any(), any(), any());
+ }).when(mTestIContentProvider).uncanonicalizeAsync(any(), any(), any());
doAnswer(invocation -> {
Uri uri = invocation.getArgument(0);
RemoteCallback cb = invocation.getArgument(1);
@@ -256,11 +256,11 @@
contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);
doReturn(CANONICAL_SOUND_URI)
- .when(mTestIContentProvider).canonicalize(any(), any(), eq(SOUND_URI));
+ .when(mTestIContentProvider).canonicalize(any(), eq(SOUND_URI));
doReturn(CANONICAL_SOUND_URI)
- .when(mTestIContentProvider).canonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
+ .when(mTestIContentProvider).canonicalize(any(), eq(CANONICAL_SOUND_URI));
doReturn(SOUND_URI)
- .when(mTestIContentProvider).uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
+ .when(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
@@ -594,7 +594,7 @@
// Testing that in restore we are given the canonical version
loadStreamXml(baos, true, UserHandle.USER_SYSTEM);
- verify(mTestIContentProvider).uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
+ verify(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
}
@Test
@@ -605,12 +605,11 @@
.appendQueryParameter("canonical", "1")
.build();
doReturn(canonicalBasedOnLocal)
- .when(mTestIContentProvider).canonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
+ .when(mTestIContentProvider).canonicalize(any(), eq(CANONICAL_SOUND_URI));
doReturn(localUri)
- .when(mTestIContentProvider).uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
+ .when(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
doReturn(localUri)
- .when(mTestIContentProvider).uncanonicalize(any(), any(),
- eq(canonicalBasedOnLocal));
+ .when(mTestIContentProvider).uncanonicalize(any(), eq(canonicalBasedOnLocal));
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -630,9 +629,9 @@
public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
Thread.sleep(3000);
doReturn(null)
- .when(mTestIContentProvider).canonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
+ .when(mTestIContentProvider).canonicalize(any(), eq(CANONICAL_SOUND_URI));
doReturn(null)
- .when(mTestIContentProvider).uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
+ .when(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -657,7 +656,7 @@
public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
// Not a local uncanonicalized uri, simulating that it fails to exist locally
doReturn(null)
- .when(mTestIContentProvider).canonicalize(any(), any(), eq(SOUND_URI));
+ .when(mTestIContentProvider).canonicalize(any(), eq(SOUND_URI));
String id = "id";
String backupWithUncanonicalizedSoundUri = "<ranking version=\"1\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 5018166..b83f9f2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -132,11 +132,11 @@
when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);
- when(mTestIContentProvider.canonicalize(any(), any(), eq(SOUND_URI)))
+ when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI)))
.thenReturn(CANONICAL_SOUND_URI);
- when(mTestIContentProvider.canonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
+ when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
.thenReturn(CANONICAL_SOUND_URI);
- when(mTestIContentProvider.uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
+ when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
.thenReturn(SOUND_URI);
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java b/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java
index aceed86..baae25c 100644
--- a/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java
@@ -110,7 +110,7 @@
mPinnedSliceManager.pin("pkg", FIRST_SPECS, mToken);
TestableLooper.get(this).processAllMessages();
- verify(mIContentProvider).call(anyString(), nullable(String.class), anyString(),
+ verify(mIContentProvider).call(any(), anyString(),
eq(SliceProvider.METHOD_PIN), eq(null), argThat(b -> {
assertEquals(TEST_URI, b.getParcelable(SliceProvider.EXTRA_BIND_URI));
return true;
@@ -168,8 +168,8 @@
// Throw exception when trying to pin
doAnswer(invocation -> {
throw new Exception("Pin failed");
- }).when(mIContentProvider).call(anyString(), nullable(String.class), anyString(),
- anyString(), eq(null), any());
+ }).when(mIContentProvider).call(any(), anyString(), anyString(),
+ nullable(String.class), any());
TestableLooper.get(this).processAllMessages();
@@ -177,7 +177,7 @@
mPinnedSliceManager.pin("pkg", FIRST_SPECS, mToken);
TestableLooper.get(this).processAllMessages();
- verify(mIContentProvider).call(anyString(), nullable(String.class), anyString(),
+ verify(mIContentProvider).call(any(), anyString(),
eq(SliceProvider.METHOD_PIN), eq(null), argThat(b -> {
assertEquals(TEST_URI, b.getParcelable(SliceProvider.EXTRA_BIND_URI));
return true;
diff --git a/test-mock/src/android/test/mock/MockContentProvider.java b/test-mock/src/android/test/mock/MockContentProvider.java
index 5b9f67e..7be42f4 100644
--- a/test-mock/src/android/test/mock/MockContentProvider.java
+++ b/test-mock/src/android/test/mock/MockContentProvider.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.AttributionSource;
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
@@ -60,21 +61,20 @@
*/
private class InversionIContentProvider implements IContentProvider {
@Override
- public ContentProviderResult[] applyBatch(String callingPackage,
- @Nullable String featureId, String authority,
- ArrayList<ContentProviderOperation> operations)
+ public ContentProviderResult[] applyBatch(@NonNull AttributionSource attributionSource,
+ String authority, ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException {
return MockContentProvider.this.applyBatch(authority, operations);
}
@Override
- public int bulkInsert(String callingPackage, @Nullable String featureId, Uri url,
+ public int bulkInsert(@NonNull AttributionSource attributionSource, Uri url,
ContentValues[] initialValues) throws RemoteException {
return MockContentProvider.this.bulkInsert(url, initialValues);
}
@Override
- public int delete(String callingPackage, @Nullable String featureId, Uri url,
+ public int delete(@NonNull AttributionSource attributionSource, Uri url,
Bundle extras) throws RemoteException {
return MockContentProvider.this.delete(url, extras);
}
@@ -90,40 +90,40 @@
}
@Override
- public Uri insert(String callingPackage, @Nullable String featureId, Uri url,
+ public Uri insert(@NonNull AttributionSource attributionSource, Uri url,
ContentValues initialValues, Bundle extras) throws RemoteException {
return MockContentProvider.this.insert(url, initialValues, extras);
}
@Override
- public AssetFileDescriptor openAssetFile(String callingPackage,
- @Nullable String featureId, Uri url, String mode, ICancellationSignal signal)
+ public AssetFileDescriptor openAssetFile(@NonNull AttributionSource attributionSource,
+ Uri url, String mode, ICancellationSignal signal)
throws RemoteException, FileNotFoundException {
return MockContentProvider.this.openAssetFile(url, mode);
}
@Override
- public ParcelFileDescriptor openFile(String callingPackage, @Nullable String featureId,
- Uri url, String mode, ICancellationSignal signal, IBinder callerToken)
+ public ParcelFileDescriptor openFile(@NonNull AttributionSource attributionSource,
+ Uri url, String mode, ICancellationSignal signal)
throws RemoteException, FileNotFoundException {
return MockContentProvider.this.openFile(url, mode);
}
@Override
- public Cursor query(String callingPackage, @Nullable String featureId, Uri url,
+ public Cursor query(@NonNull AttributionSource attributionSource, Uri url,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable ICancellationSignal cancellationSignal) throws RemoteException {
return MockContentProvider.this.query(url, projection, queryArgs, null);
}
@Override
- public int update(String callingPackage, @Nullable String featureId, Uri url,
+ public int update(@NonNull AttributionSource attributionSource, Uri url,
ContentValues values, Bundle extras) throws RemoteException {
return MockContentProvider.this.update(url, values, extras);
}
@Override
- public Bundle call(String callingPackage, @Nullable String featureId, String authority,
+ public Bundle call(@NonNull AttributionSource attributionSource, String authority,
String method, String request, Bundle args) throws RemoteException {
return MockContentProvider.this.call(authority, method, request, args);
}
@@ -139,9 +139,10 @@
}
@Override
- public AssetFileDescriptor openTypedAssetFile(String callingPackage,
- @Nullable String featureId, Uri url, String mimeType, Bundle opts,
- ICancellationSignal signal) throws RemoteException, FileNotFoundException {
+ public AssetFileDescriptor openTypedAssetFile(
+ @NonNull AttributionSource attributionSource, Uri url, String mimeType,
+ Bundle opts, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
return MockContentProvider.this.openTypedAssetFile(url, mimeType, opts);
}
@@ -151,37 +152,37 @@
}
@Override
- public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri uri)
+ public Uri canonicalize(@NonNull AttributionSource attributionSource, Uri uri)
throws RemoteException {
return MockContentProvider.this.canonicalize(uri);
}
@Override
- public void canonicalizeAsync(String callingPkg, String featureId, Uri uri,
+ public void canonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri,
RemoteCallback callback) {
MockContentProvider.this.canonicalizeAsync(uri, callback);
}
@Override
- public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri uri)
+ public Uri uncanonicalize(@NonNull AttributionSource attributionSource, Uri uri)
throws RemoteException {
return MockContentProvider.this.uncanonicalize(uri);
}
@Override
- public void uncanonicalizeAsync(String callingPkg, String featureId, Uri uri,
+ public void uncanonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri,
RemoteCallback callback) {
MockContentProvider.this.uncanonicalizeAsync(uri, callback);
}
@Override
- public boolean refresh(String callingPkg, @Nullable String featureId, Uri url,
+ public boolean refresh(@NonNull AttributionSource attributionSource, Uri url,
Bundle args, ICancellationSignal cancellationSignal) throws RemoteException {
return MockContentProvider.this.refresh(url, args);
}
@Override
- public int checkUriPermission(String callingPkg, @Nullable String featureId, Uri uri,
+ public int checkUriPermission(@NonNull AttributionSource attributionSource, Uri uri,
int uid, int modeFlags) {
return MockContentProvider.this.checkUriPermission(uri, uid, modeFlags);
}
diff --git a/test-mock/src/android/test/mock/MockIContentProvider.java b/test-mock/src/android/test/mock/MockIContentProvider.java
index 82a1cf7..b81c707 100644
--- a/test-mock/src/android/test/mock/MockIContentProvider.java
+++ b/test-mock/src/android/test/mock/MockIContentProvider.java
@@ -16,7 +16,9 @@
package android.test.mock;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.AttributionSource;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
@@ -46,14 +48,14 @@
*/
public class MockIContentProvider implements IContentProvider {
@Override
- public int bulkInsert(String callingPackage, @Nullable String featureId, Uri url,
+ public int bulkInsert(@NonNull AttributionSource attributionSource, Uri url,
ContentValues[] initialValues) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
@SuppressWarnings("unused")
- public int delete(String callingPackage, @Nullable String featureId, Uri url,
+ public int delete(@NonNull AttributionSource attributionSource, Uri url,
Bundle extras) throws RemoteException {
throw new UnsupportedOperationException("unimplemented mock method");
}
@@ -75,31 +77,31 @@
@Override
@SuppressWarnings("unused")
- public Uri insert(String callingPackage, @Nullable String featureId, Uri url,
+ public Uri insert(@NonNull AttributionSource attributionSource, Uri url,
ContentValues initialValues, Bundle extras) throws RemoteException {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
- public ParcelFileDescriptor openFile(String callingPackage, @Nullable String featureId,
- Uri url, String mode, ICancellationSignal signal, IBinder callerToken) {
+ public ParcelFileDescriptor openFile(@NonNull AttributionSource attributionSource,
+ Uri url, String mode, ICancellationSignal signal) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
- public AssetFileDescriptor openAssetFile(String callingPackage, @Nullable String featureId,
+ public AssetFileDescriptor openAssetFile(@NonNull AttributionSource attributionSource,
Uri uri, String mode, ICancellationSignal signal) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
- public ContentProviderResult[] applyBatch(String callingPackage, @Nullable String featureId,
+ public ContentProviderResult[] applyBatch(@NonNull AttributionSource attributionSource,
String authority, ArrayList<ContentProviderOperation> operations) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
- public Cursor query(String callingPackage, @Nullable String featureId, Uri url,
+ public Cursor query(@NonNull AttributionSource attributionSource, Uri url,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable ICancellationSignal cancellationSignal) {
throw new UnsupportedOperationException("unimplemented mock method");
@@ -111,13 +113,13 @@
}
@Override
- public int update(String callingPackage, @Nullable String featureId, Uri url,
+ public int update(@NonNull AttributionSource attributionSource, Uri url,
ContentValues values, Bundle extras) throws RemoteException {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
- public Bundle call(String callingPackage, @Nullable String featureId, String authority,
+ public Bundle call(@NonNull AttributionSource attributionSource, String authority,
String method, String request, Bundle args) throws RemoteException {
throw new UnsupportedOperationException("unimplemented mock method");
}
@@ -133,9 +135,9 @@
}
@Override
- public AssetFileDescriptor openTypedAssetFile(String callingPackage,
- @Nullable String featureId, Uri url, String mimeType, Bundle opts,
- ICancellationSignal signal) throws RemoteException, FileNotFoundException {
+ public AssetFileDescriptor openTypedAssetFile(@NonNull AttributionSource attributionSource,
+ Uri url, String mimeType, Bundle opts, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
throw new UnsupportedOperationException("unimplemented mock method");
}
@@ -145,48 +147,48 @@
}
@Override
- public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri uri) {
+ public Uri canonicalize(@NonNull AttributionSource attributionSource, Uri uri) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
@SuppressWarnings("deprecation")
- public void canonicalizeAsync(String callingPkg, String featureId, Uri uri,
+ public void canonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri,
RemoteCallback remoteCallback) {
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
final Bundle bundle = new Bundle();
bundle.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
- canonicalize(callingPkg, featureId, uri));
+ canonicalize(attributionSource, uri));
remoteCallback.sendResult(bundle);
});
}
@Override
- public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri uri) {
+ public Uri uncanonicalize(@NonNull AttributionSource attributionSource, Uri uri) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
@SuppressWarnings("deprecation")
- public void uncanonicalizeAsync(String callingPkg, String featureId, Uri uri,
+ public void uncanonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri,
RemoteCallback remoteCallback) {
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
final Bundle bundle = new Bundle();
bundle.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
- uncanonicalize(callingPkg, featureId, uri));
+ uncanonicalize(attributionSource, uri));
remoteCallback.sendResult(bundle);
});
}
@Override
- public boolean refresh(String callingPkg, @Nullable String featureId, Uri url, Bundle args,
+ public boolean refresh(@NonNull AttributionSource attributionSource, Uri url, Bundle args,
ICancellationSignal cancellationSignal) throws RemoteException {
throw new UnsupportedOperationException("unimplemented mock method");
}
/** {@hide} */
@Override
- public int checkUriPermission(String callingPkg, @Nullable String featureId, Uri uri, int uid,
+ public int checkUriPermission(@NonNull AttributionSource attributionSource, Uri uri, int uid,
int modeFlags) {
throw new UnsupportedOperationException("unimplemented mock method call");
}