diff options
54 files changed, 3131 insertions, 1107 deletions
diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java index ca1d598ee7d7..f0ac53014bd3 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 @@ package com.android.commands.content; 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 @@ public class Content { @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 @@ public class Content { @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 @@ public class Content { @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 @@ public class Content { @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 @@ public class Content { @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 @@ public class Content { @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 @@ public class Content { @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 b23bf5da5c8d..bc95986c083f 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.ActivityManager; 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.hardware.display.DisplayManagerGlobal; 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 @@ public class ShellUiAutomatorBridge extends UiAutomatorBridge { 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 @@ public class ShellUiAutomatorBridge extends UiAutomatorBridge { } 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 9b8ea6b52382..eb5f648c7190 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9907,6 +9907,27 @@ package android.content { 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 @@ package android.content { 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 @@ package android.content { 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 @@ package android.content { 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 @@ package android.content { 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 @@ package android.speech { 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 92c21f3ef3fc..3f26e140c8e6 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -596,7 +596,7 @@ package android.app { 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 @@ package android.content { 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 @@ package android.content { } 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 21771df3c6a3..7cfe7826377a 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -220,6 +220,7 @@ package android.app { 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.bluetooth { 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 47843cc113a2..0f38b5fdb5c3 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5246,13 +5246,40 @@ public class Activity extends ContextThemeWrapper 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 7e4af1ad7952..f76e1c0e50fb 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -50,6 +50,7 @@ import android.os.IBinder; 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 @@ public class AppOpsManager { * * @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 @@ public class AppOpsManager { * 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 @@ public class AppOpsManager { * * @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 @@ public class AppOpsManager { 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 class AppOpsManager { */ 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 @@ public class AppOpsManager { } /** + * 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 class AppOpsManager { */ 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 @@ public class AppOpsManager { } } - 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 @@ public class AppOpsManager { 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 @@ public class AppOpsManager { 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 class AppOpsManager { */ 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 class AppOpsManager { 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 @@ public class AppOpsManager { } } - 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 @@ public class AppOpsManager { } /** - * 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 @@ public class AppOpsManager { } /** + * 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 @@ public class AppOpsManager { 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 5e032f00a3a0..a3d0cf2e972f 100644 --- a/core/java/android/app/AppOpsManagerInternal.java +++ b/core/java/android/app/AppOpsManagerInternal.java @@ -18,12 +18,17 @@ package android.app; 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 @@ public abstract class AppOpsManagerInternal { @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 03e95fc3b6b9..f8165e9a7cf6 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -60,6 +60,7 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory; 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { /** @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 @@ class ContextImpl extends Context { } @Override + public @NonNull AttributionSource getAttributionSource() { + return mAttributionSource; + } + + @Override public ApplicationInfo getApplicationInfo() { if (mPackageInfo != null) { return mPackageInfo.getApplicationInfo(); @@ -2074,13 +2080,7 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { } 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 @@ class ContextImpl extends Context { } 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 @@ class ContextImpl extends Context { @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 @@ class ContextImpl extends Context { @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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { } 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 4c2433c04771..81e5e1d96294 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -475,8 +475,6 @@ interface IActivityManager { @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 a3d19ca6425c..0be7b732b4bd 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -745,7 +745,6 @@ public final class BluetoothAdapter { * 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 000000000000..10d5c274ae91 --- /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 000000000000..053bfc1a3253 --- /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 73b4f62a23e4..82842039c310 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -18,11 +18,6 @@ package android.content; 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.Binder; 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.UserHandle; 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall // 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); } if (cursor == null) { return null; @@ -272,8 +265,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @@ -314,60 +307,59 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall // 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } /** {@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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall // 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * @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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * @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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall /** {@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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall */ @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 abstract class ContentProvider implements ContentInterface, ComponentCall */ public final void restoreCallingIdentity(@NonNull CallingIdentity identity) { Binder.restoreCallingIdentity(identity.binderToken); - mCallingPackage.set(identity.callingPackage); + mCallingAttributionSource.set(identity.callingAttributionSource); } /** @@ -2374,7 +2390,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall 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 5af7861e1a20..518e7534d512 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -79,7 +79,8 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { mContentResolver = contentResolver; mContentProvider = contentProvider; mPackageName = contentResolver.mPackageName; - mAttributionTag = contentResolver.mAttributionTag; + mAttributionSource = contentResolver.getAttributionSource(); mAuthority = authority; mStable = stable; @@ -193,7 +194,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { beforeRemote(); try { - return mContentProvider.applyBatch(mPackageName, mAttributionTag, authority, + return mContentProvider.applyBatch(mAttributionSource, authority, operations); } catch (DeadObjectException e) { if (!mStable) { @@ -598,7 +598,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { 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 7d121d56c86d..47c966990861 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr { 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr { 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider 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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider 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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider 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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider 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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider } @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 @@ final class ContentProviderProxy implements IContentProvider } @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 8ea417f900db..14b2a65c4da6 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -816,7 +816,6 @@ public abstract class ContentResolver implements ContentInterface { 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 @@ public abstract class ContentResolver implements ContentInterface { 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 @@ public abstract class ContentResolver implements ContentInterface { 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 @@ public abstract class ContentResolver implements ContentInterface { 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 @@ public abstract class ContentResolver implements ContentInterface { 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 @@ public abstract class ContentResolver implements ContentInterface { 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 @@ public abstract class ContentResolver implements ContentInterface { 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 @@ public abstract class ContentResolver implements ContentInterface { 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 @@ public abstract class ContentResolver implements ContentInterface { 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 @@ public abstract class ContentResolver implements ContentInterface { 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 @@ public abstract class ContentResolver implements ContentInterface { } 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 @@ public abstract class ContentResolver implements ContentInterface { } 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 @@ public abstract class ContentResolver implements ContentInterface { } 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 @@ public abstract class ContentResolver implements ContentInterface { } 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 @@ public abstract class ContentResolver implements ContentInterface { 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 @@ public abstract class ContentResolver implements ContentInterface { /** @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 @@ public abstract class ContentResolver implements ContentInterface { @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 6ff296c9799f..8531d341ee9b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -889,6 +889,15 @@ public abstract class Context { 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 @@ public abstract class Context { * @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 fad905bfac13..2b2db8fca2ca 100644 --- a/core/java/android/content/ContextParams.java +++ b/core/java/android/content/ContextParams.java @@ -19,7 +19,6 @@ package android.content; 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 @@ import java.util.Set; * 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 @@ public final class ContextParams { } /** - * @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 final class ContextParams { 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 @@ public final class ContextParams { } /** - * 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 @@ public final class ContextParams { * 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 @@ public final class ContextParams { */ @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 609f417a8008..de0d65fec1fb 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -1060,6 +1060,12 @@ public class ContextWrapper extends Context { 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 9210b132c75a..e0315a3e171b 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 @@ import java.util.ArrayList; * @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 @@ public interface IContentProvider extends IInterface { @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) + ParcelFileDescriptor openFile(@NonNull AttributionSource attributionSource, + Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException; - public AssetFileDescriptor openAssetFile(String callingPkg, @Nullable String attributionTag, + AssetFileDescriptor openAssetFile(@NonNull AttributionSource attributionSource, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException; - public ContentProviderResult[] applyBatch(String callingPkg, @Nullable String attributionTag, + ContentProviderResult[] applyBatch(@NonNull AttributionSource attributionSource, String authority, ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException; @@ -112,18 +116,19 @@ public interface IContentProvider extends IInterface { + "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 @@ public interface IContentProvider extends IInterface { * 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 @@ public interface IContentProvider extends IInterface { * 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 159db92c79c9..08eac5aff655 100644 --- a/core/java/android/content/PermissionChecker.java +++ b/core/java/android/content/PermissionChecker.java @@ -27,6 +27,8 @@ import android.os.Process; 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 @@ import java.lang.annotation.RetentionPolicy; * @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 @@ public final class PermissionChecker { * </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 @@ public final class PermissionChecker { * @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 final class PermissionChecker { 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 @@ public final class PermissionChecker { * 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 @@ public final class PermissionChecker { * @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 final class PermissionChecker { 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 @@ public final class PermissionChecker { 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 @@ public final class PermissionChecker { * 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 @@ public final class PermissionChecker { * @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 @@ public final class PermissionChecker { 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 @@ public final class PermissionChecker { 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); + } + + if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(), + attributionSource.getRenouncedPermissions())) { + return PERMISSION_HARD_DENIED; } - return context.checkPermission(permission, pid, uid); + + 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: { + 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; } - case AppOpsManager.MODE_DEFAULT: { - return context.checkPermission(permission, pid, uid) - == PackageManager.PERMISSION_GRANTED - ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED; + + current = next; + } + } + + private static int checkRuntimePermission(@NonNull Context context, @NonNull String permission, + @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; } - default: { + + // 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 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) { - return PERMISSION_HARD_DENIED; + 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; + } - final String op = AppOpsManager.permissionToOp(permission); - if (op == null || packageName == null) { - return PERMISSION_GRANTED; + 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 AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); - final int opMode = (forDataDelivery) - ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, attributionTag, message) - : appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName); + AttributionSource current = attributionSource; + AttributionSource next = null; + + while (true) { + final boolean skipCurrentChecks = (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 (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; + } + } - switch (opMode) { - case AppOpsManager.MODE_ALLOWED: - case AppOpsManager.MODE_FOREGROUND: + 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 8c105be9fbb7..ef075e1efbff 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 @@ interface IPermissionManager { 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 baa25f07f514..936cbfc70708 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -44,6 +44,7 @@ import android.content.pm.PermissionInfo; 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 @@ public final class PermissionManager { 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 b704d66d8e41..a5a24c0f1013 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -1102,8 +1102,7 @@ public abstract class DocumentsProvider extends ContentProvider { // 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 @@ public abstract class DocumentsProvider extends ContentProvider { } 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 @@ public abstract class DocumentsProvider extends ContentProvider { && 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 @@ public abstract class DocumentsProvider extends ContentProvider { 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 @@ public abstract class DocumentsProvider extends ContentProvider { 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 @@ public abstract class DocumentsProvider extends ContentProvider { } } 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 @@ public abstract class DocumentsProvider extends ContentProvider { 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 @@ public abstract class DocumentsProvider extends ContentProvider { 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 @@ public abstract class DocumentsProvider extends ContentProvider { 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 @@ public abstract class DocumentsProvider extends ContentProvider { 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 5d924cc88706..12ff6405b8f2 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2754,7 +2754,7 @@ public final class Settings { 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 @@ public final class Settings { 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 @@ public final class Settings { 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 @@ public final class Settings { 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 @@ public final class Settings { } // 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 @@ public final class Settings { } 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 @@ public final class Settings { } 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 @@ public final class Settings { 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 @@ public final class Settings { 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 cc1cdedd0f96..9a5e534a68cf 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 @@ oneway interface IRecognitionService { * 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 @@ oneway interface IRecognitionService { * 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 fd584f191743..4afa94735d62 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.Bundle; 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 @@ public abstract class RecognitionService extends Service { 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 @@ public abstract class RecognitionService extends Service { }; 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 @@ public abstract class RecognitionService extends Service { } } - 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 abstract class RecognitionService extends Service { 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 abstract class RecognitionService extends Service { */ 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 @@ public abstract class RecognitionService extends Service { * 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 @@ public abstract class RecognitionService extends Service { * {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter */ public void partialResults(Bundle partialResults) throws RemoteException { + startRecordAudioOpAttributionToCallerIfNeeded(); mListener.onPartialResults(partialResults); } @@ -323,6 +336,7 @@ public abstract class RecognitionService extends Service { * {@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 @@ public abstract class RecognitionService extends Service { * 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 @@ public abstract class RecognitionService extends Service { @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 9b93a64e48a3..7aa5ee51b606 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -386,8 +386,7 @@ public class SpeechRecognizer { 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 @@ public class SpeechRecognizer { 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 @@ public class SpeechRecognizer { 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 class SpeechRecognizer { 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 eecd0cfe66a4..01bb199a1a6a 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.AppOpsManager; 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 @@ interface IAppOpsService { // 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 @@ interface IAppOpsService { 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 @@ interface IAppOpsService { 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 be306e07a7a5..ada670459c13 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 b517428f5b59..01e240a7ff8a 100644 --- a/core/tests/coretests/src/android/content/ContentResolverTest.java +++ b/core/tests/coretests/src/android/content/ContentResolverTest.java @@ -86,7 +86,7 @@ public class ContentResolverTest { 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 4212ef21e0af..97e66c481dda 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.test.mock.MockContentResolver; 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 @@ public class NameValueCacheTest { 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 @@ public class NameValueCacheTest { // 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 @@ public class NameValueCacheTest { // 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 @@ public class NameValueCacheTest { 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 @@ public class NameValueCacheTest { // 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 @@ public class NameValueCacheTest { 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 @@ public class NameValueCacheTest { 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 @@ public class NameValueCacheTest { 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 @@ public class NameValueCacheTest { 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 5f640bee7ce9..449698301e75 100644 --- a/core/tests/coretests/src/android/provider/TestDocumentsProvider.java +++ b/core/tests/coretests/src/android/provider/TestDocumentsProvider.java @@ -18,13 +18,13 @@ package android.provider; 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 @@ public class TestDocumentsProvider extends DocumentsProvider { } @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 de353bf58aec..a0d449239b2a 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.times; 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 @@ public class MediaInserterTest extends InstrumentationTestCase { 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 @@ public class MediaInserterTest extends InstrumentationTestCase { 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 @@ public class MediaInserterTest extends InstrumentationTestCase { 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 @@ public class MediaInserterTest extends InstrumentationTestCase { 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 @@ public class MediaInserterTest extends InstrumentationTestCase { 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 @@ public class MediaInserterTest extends InstrumentationTestCase { @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 @@ public class MediaInserterTest extends InstrumentationTestCase { 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 @@ public class MediaInserterTest extends InstrumentationTestCase { 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 @@ public class MediaInserterTest extends InstrumentationTestCase { 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 087275e73ee8..801b490406cc 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -19,6 +19,7 @@ package com.android.externalstorage; 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.net.Uri; 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 @@ public class ExternalStorageProvider extends FileSystemProvider { } @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 49f6bd8c3334..a2bec334f206 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 @@ public class TileUtils { 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 fdbbc391082f..df6ff73cc046 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 @@ package com.android.providers.settings; 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 @@ public final class DeviceConfigService extends Binder { 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 @@ public final class DeviceConfigService extends Binder { 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 3b3ca5b02417..17ebf6fc3235 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 @@ final public class SettingsService extends Binder { 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 @@ final public class SettingsService extends Binder { 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 @@ final public class SettingsService extends Binder { 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 @@ final public class SettingsService extends Binder { 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 @@ final public class SettingsService extends Binder { } 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 cff8ad1ed964..4c56db4282f6 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 036b88e6fc1c..fa88d6603840 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.UsageEvents.Event; 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.FrameworkStatsLog; 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.graphics.fonts.FontManagerInternal; 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 ee4526b99d1c..b44699bab0bb 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.ApplicationExitInfo; 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.Intent; 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 android.util.SparseArray; 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 @@ public class ContentProviderHelper { 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 7bc71051c627..07ee5a2f84ec 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.AsyncNotedAppOp; 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.ArrayMap; 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.ArrayUtils; 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 @@ public class AppOpsService extends IAppOpsService.Stub { @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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { @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 @@ public class AppOpsService extends IAppOpsService.Stub { } } + 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 @@ public class AppOpsService extends IAppOpsService.Stub { } @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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { } @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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { } @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 @@ public class AppOpsService extends IAppOpsService.Stub { String resolvedProxyPackageName = resolvePackageName(proxyUid, proxyPackageName); if (resolvedProxyPackageName == null) { - return; + return null; } finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName, @@ -3741,11 +3879,13 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { || (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 @@ public class AppOpsService extends IAppOpsService.Stub { } } + 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 @@ public class AppOpsService extends IAppOpsService.Stub { /** * 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { } @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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 920d044989a0..142b36e8eaf3 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.parsing.component.ParsedPermissionGroup; 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.List; 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { @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 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @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 @@ public class PermissionManagerService extends IPermissionManager.Stub { || 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 c9653909adb6..5db63c6db2b4 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.NonNull; 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 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat @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 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat @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 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat 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 907743314c7c..89d54158b006 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 @@ public final class PermissionPolicyService extends SystemService { 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 @@ public final class PermissionPolicyService extends SystemService { 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 @@ public final class PermissionPolicyService extends SystemService { 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 @@ public final class PermissionPolicyService extends SystemService { */ 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 @@ public final class PermissionPolicyService extends SystemService { 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 3f203026a219..9c8ff685d14d 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 @@ package com.android.server.speech; 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 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn 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 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn @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 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn private boolean mRecordingInProgress = false; private final int mCallingUid; - private final AppOpsManager mAppOpsManager; private final ComponentName mComponentName; RemoteSpeechRecognitionService( @@ -87,7 +74,6 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn IRecognitionService.Stub::asInterface); mCallingUid = callingUid; - mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mComponentName = serviceName; if (DEBUG) { @@ -99,11 +85,12 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn 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 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn return; } - if (startProxyOp(packageName, featureId) != AppOpsManager.MODE_ALLOWED) { - tryRespondWithError(listener, SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS); - return; - } mSessionInProgress = true; mRecordingInProgress = true; @@ -141,23 +124,18 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn 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 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn } 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 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn // 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 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn } } - cancel(mListener, mPackageName, mFeatureId, true /* isShutdown */); + cancel(mListener, true /* isShutdown */); } @Override // from ServiceConnector.Impl @@ -286,40 +255,12 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn } 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 52c1467bd5d0..22eeb346a292 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 @@ public final class SpeechRecognitionManagerService extends @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 769e049c8d0e..eee08c221bfa 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.NonNull; 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 android.util.Slog; 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 @@ final class SpeechRecognitionManagerServiceImpl extends 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 class SpeechRecognitionManagerServiceImpl extends } 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 @@ final class SpeechRecognitionManagerServiceImpl extends } 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 @@ final class SpeechRecognitionManagerServiceImpl extends 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 @@ final class SpeechRecognitionManagerServiceImpl extends }); } - 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 acda4d5661e4..04c144a4ce7e 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.Notification; 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.net.Uri; 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { // 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { .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 class PreferencesHelperTest extends UiServiceTestCase { 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 class PreferencesHelperTest extends UiServiceTestCase { 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 5018166d9ef1..b83f9f266ad7 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 @@ public class RankingHelperTest extends UiServiceTestCase { 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 aceed86220c6..baae25cad32e 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 @@ public class PinnedSliceStateTest extends UiServiceTestCase { 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 @@ public class PinnedSliceStateTest extends UiServiceTestCase { // 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 @@ public class PinnedSliceStateTest extends UiServiceTestCase { 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 5b9f67efd95d..7be42f4f36f5 100644 --- a/test-mock/src/android/test/mock/MockContentProvider.java +++ b/test-mock/src/android/test/mock/MockContentProvider.java @@ -18,6 +18,7 @@ package android.test.mock; 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 @@ public class MockContentProvider extends ContentProvider { */ 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 @@ public class MockContentProvider extends ContentProvider { } @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 @@ public class MockContentProvider extends ContentProvider { } @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 @@ public class MockContentProvider extends ContentProvider { } @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 82a1cf7d1796..b81c70704d79 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 @@ import java.util.ArrayList; */ 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 @@ public class MockIContentProvider implements IContentProvider { @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 @@ public class MockIContentProvider implements IContentProvider { } @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 @@ public class MockIContentProvider implements IContentProvider { } @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 @@ public class MockIContentProvider implements IContentProvider { } @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"); } |