summaryrefslogtreecommitdiff
path: root/core/java/android/content/ContentProvider.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/content/ContentProvider.java')
-rw-r--r--core/java/android/content/ContentProvider.java582
1 files changed, 444 insertions, 138 deletions
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 56ccbb6bfb0d..33be50d34777 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -17,6 +17,7 @@
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;
@@ -26,8 +27,11 @@ import static android.os.Trace.TRACE_TAG_DATABASE;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.AppOpsManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.PackageManager;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
import android.content.res.AssetFileDescriptor;
@@ -44,13 +48,16 @@ import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.Trace;
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;
@@ -134,7 +141,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
private boolean mNoPerms;
private boolean mSingleUser;
- private ThreadLocal<String> mCallingPackage;
+ private ThreadLocal<Pair<String, String>> mCallingPackage;
private Transport mTransport = new Transport();
@@ -224,11 +231,13 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
- public Cursor query(String callingPkg, Uri uri, @Nullable String[] projection,
- @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) {
+ public Cursor query(String callingPkg, @Nullable String attributionTag, Uri uri,
+ @Nullable String[] projection, @Nullable Bundle queryArgs,
+ @Nullable ICancellationSignal cancellationSignal) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
// 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.
@@ -244,7 +253,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 String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
cursor = mInterface.query(
uri, projection, queryArgs,
@@ -262,7 +272,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
return new MatrixCursor(cursor.getColumnNames(), 0);
}
Trace.traceBegin(TRACE_TAG_DATABASE, "query");
- final String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
return mInterface.query(
uri, projection, queryArgs,
@@ -291,12 +302,27 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
- public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
+ public void getTypeAsync(Uri uri, RemoteCallback callback) {
+ final Bundle result = new Bundle();
+ try {
+ result.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getType(uri));
+ } catch (Exception e) {
+ result.putParcelable(ContentResolver.REMOTE_CALLBACK_ERROR,
+ new ParcelableException(e));
+ }
+ callback.sendResult(result);
+ }
+
+ @Override
+ public Uri insert(String callingPkg, @Nullable String attributionTag, Uri uri,
+ ContentValues initialValues, Bundle extras) {
uri = validateIncomingUri(uri);
int userId = getUserIdFromUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
- final String original = setCallingPackage(callingPkg);
+ if (enforceWritePermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
return rejectInsert(uri, initialValues);
} finally {
@@ -304,9 +330,10 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
}
Trace.traceBegin(TRACE_TAG_DATABASE, "insert");
- final String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
- return maybeAddUserId(mInterface.insert(uri, initialValues), userId);
+ return maybeAddUserId(mInterface.insert(uri, initialValues, extras), userId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
@@ -316,14 +343,17 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
- public int bulkInsert(String callingPkg, Uri uri, ContentValues[] initialValues) {
+ public int bulkInsert(String callingPkg, @Nullable String attributionTag, Uri uri,
+ ContentValues[] initialValues) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceWritePermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
return 0;
}
Trace.traceBegin(TRACE_TAG_DATABASE, "bulkInsert");
- final String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
return mInterface.bulkInsert(uri, initialValues);
} catch (RemoteException e) {
@@ -335,7 +365,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
- public ContentProviderResult[] applyBatch(String callingPkg, String authority,
+ public ContentProviderResult[] applyBatch(String callingPkg,
+ @Nullable String attributionTag, String authority,
ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
validateIncomingAuthority(authority);
@@ -353,20 +384,21 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
operations.set(i, operation);
}
if (operation.isReadOperation()) {
- if (enforceReadPermission(callingPkg, uri, null)
+ if (enforceReadPermission(callingPkg, attributionTag, uri, null)
!= AppOpsManager.MODE_ALLOWED) {
throw new OperationApplicationException("App op not allowed", 0);
}
}
if (operation.isWriteOperation()) {
- if (enforceWritePermission(callingPkg, uri, null)
+ if (enforceWritePermission(callingPkg, attributionTag, uri, null)
!= AppOpsManager.MODE_ALLOWED) {
throw new OperationApplicationException("App op not allowed", 0);
}
}
}
Trace.traceBegin(TRACE_TAG_DATABASE, "applyBatch");
- final String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
ContentProviderResult[] results = mInterface.applyBatch(authority,
operations);
@@ -388,16 +420,19 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
- public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) {
+ public int delete(String callingPkg, @Nullable String attributionTag, Uri uri,
+ Bundle extras) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceWritePermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
return 0;
}
Trace.traceBegin(TRACE_TAG_DATABASE, "delete");
- final String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
- return mInterface.delete(uri, selection, selectionArgs);
+ return mInterface.delete(uri, extras);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
@@ -407,17 +442,19 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
- public int update(String callingPkg, Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
+ public int update(String callingPkg, @Nullable String attributionTag, Uri uri,
+ ContentValues values, Bundle extras) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceWritePermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
return 0;
}
Trace.traceBegin(TRACE_TAG_DATABASE, "update");
- final String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
- return mInterface.update(uri, values, selection, selectionArgs);
+ return mInterface.update(uri, values, extras);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
@@ -427,14 +464,15 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
- public ParcelFileDescriptor openFile(
- String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal,
- IBinder callerToken) throws FileNotFoundException {
+ public ParcelFileDescriptor openFile(String callingPkg, @Nullable String attributionTag,
+ Uri uri, String mode, ICancellationSignal cancellationSignal, IBinder callerToken)
+ throws FileNotFoundException {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- enforceFilePermission(callingPkg, uri, mode, callerToken);
+ enforceFilePermission(callingPkg, attributionTag, uri, mode, callerToken);
Trace.traceBegin(TRACE_TAG_DATABASE, "openFile");
- final String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
return mInterface.openFile(
uri, mode, CancellationSignal.fromTransport(cancellationSignal));
@@ -447,14 +485,15 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
- public AssetFileDescriptor openAssetFile(
- String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
+ public AssetFileDescriptor openAssetFile(String callingPkg, @Nullable String attributionTag,
+ Uri uri, String mode, ICancellationSignal cancellationSignal)
throws FileNotFoundException {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- enforceFilePermission(callingPkg, uri, mode, null);
+ enforceFilePermission(callingPkg, attributionTag, uri, mode, null);
Trace.traceBegin(TRACE_TAG_DATABASE, "openAssetFile");
- final String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
return mInterface.openAssetFile(
uri, mode, CancellationSignal.fromTransport(cancellationSignal));
@@ -467,12 +506,13 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
- public Bundle call(String callingPkg, String authority, String method, @Nullable String arg,
- @Nullable Bundle extras) {
+ public Bundle call(String callingPkg, @Nullable String attributionTag, String authority,
+ String method, @Nullable String arg, @Nullable Bundle extras) {
validateIncomingAuthority(authority);
Bundle.setDefusable(extras, true);
Trace.traceBegin(TRACE_TAG_DATABASE, "call");
- final String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
return mInterface.call(authority, method, arg, extras);
} catch (RemoteException e) {
@@ -499,14 +539,16 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
- public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType,
- Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException {
+ public AssetFileDescriptor openTypedAssetFile(String callingPkg,
+ @Nullable String attributionTag, Uri uri, String mimeType, Bundle opts,
+ ICancellationSignal cancellationSignal) throws FileNotFoundException {
Bundle.setDefusable(opts, true);
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- enforceFilePermission(callingPkg, uri, "r", null);
+ enforceFilePermission(callingPkg, attributionTag, uri, "r", null);
Trace.traceBegin(TRACE_TAG_DATABASE, "openTypedAssetFile");
- final String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
return mInterface.openTypedAssetFile(
uri, mimeType, opts, CancellationSignal.fromTransport(cancellationSignal));
@@ -524,15 +566,17 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
- public Uri canonicalize(String callingPkg, Uri uri) {
+ public Uri canonicalize(String callingPkg, @Nullable String attributionTag, Uri uri) {
uri = validateIncomingUri(uri);
int userId = getUserIdFromUri(uri);
uri = getUriWithoutUserId(uri);
- if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
return null;
}
Trace.traceBegin(TRACE_TAG_DATABASE, "canonicalize");
- final String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
return maybeAddUserId(mInterface.canonicalize(uri), userId);
} catch (RemoteException e) {
@@ -544,15 +588,31 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
- public Uri uncanonicalize(String callingPkg, Uri uri) {
+ public void canonicalizeAsync(String callingPkg, @Nullable String attributionTag, Uri uri,
+ RemoteCallback callback) {
+ final Bundle result = new Bundle();
+ try {
+ result.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
+ canonicalize(callingPkg, attributionTag, uri));
+ } catch (Exception e) {
+ result.putParcelable(ContentResolver.REMOTE_CALLBACK_ERROR,
+ new ParcelableException(e));
+ }
+ callback.sendResult(result);
+ }
+
+ @Override
+ public Uri uncanonicalize(String callingPkg, String attributionTag, Uri uri) {
uri = validateIncomingUri(uri);
int userId = getUserIdFromUri(uri);
uri = getUriWithoutUserId(uri);
- if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
return null;
}
Trace.traceBegin(TRACE_TAG_DATABASE, "uncanonicalize");
- final String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
return maybeAddUserId(mInterface.uncanonicalize(uri), userId);
} catch (RemoteException e) {
@@ -564,17 +624,19 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
- public boolean refresh(String callingPkg, Uri uri, Bundle args,
+ public boolean refresh(String callingPkg, String attributionTag, Uri uri, Bundle extras,
ICancellationSignal cancellationSignal) throws RemoteException {
uri = validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
- if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
return false;
}
Trace.traceBegin(TRACE_TAG_DATABASE, "refresh");
- final String original = setCallingPackage(callingPkg);
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
try {
- return mInterface.refresh(uri, args,
+ return mInterface.refresh(uri, extras,
CancellationSignal.fromTransport(cancellationSignal));
} finally {
setCallingPackage(original);
@@ -582,44 +644,68 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
}
- private void enforceFilePermission(String callingPkg, Uri uri, String mode,
- IBinder callerToken) throws FileNotFoundException, SecurityException {
+ @Override
+ public int checkUriPermission(String callingPkg, @Nullable String attributionTag, 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));
+ try {
+ return mInterface.checkUriPermission(uri, uid, modeFlags);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ private void enforceFilePermission(String callingPkg, @Nullable String attributionTag,
+ Uri uri, String mode, IBinder callerToken)
+ throws FileNotFoundException, SecurityException {
if (mode != null && mode.indexOf('w') != -1) {
- if (enforceWritePermission(callingPkg, uri, callerToken)
+ if (enforceWritePermission(callingPkg, attributionTag, uri, callerToken)
!= AppOpsManager.MODE_ALLOWED) {
throw new FileNotFoundException("App op not allowed");
}
} else {
- if (enforceReadPermission(callingPkg, uri, callerToken)
+ if (enforceReadPermission(callingPkg, attributionTag, uri, callerToken)
!= AppOpsManager.MODE_ALLOWED) {
throw new FileNotFoundException("App op not allowed");
}
}
}
- private int enforceReadPermission(String callingPkg, Uri uri, IBinder callerToken)
+ private int enforceReadPermission(String callingPkg, @Nullable String attributionTag,
+ Uri uri, IBinder callerToken)
throws SecurityException {
- final int mode = enforceReadPermissionInner(uri, callingPkg, callerToken);
+ final int mode = enforceReadPermissionInner(uri, callingPkg, attributionTag,
+ callerToken);
if (mode != MODE_ALLOWED) {
return mode;
}
- return noteProxyOp(callingPkg, mReadOp);
+ return noteProxyOp(callingPkg, attributionTag, mReadOp);
}
- private int enforceWritePermission(String callingPkg, Uri uri, IBinder callerToken)
+ private int enforceWritePermission(String callingPkg, String attributionTag, Uri uri,
+ IBinder callerToken)
throws SecurityException {
- final int mode = enforceWritePermissionInner(uri, callingPkg, callerToken);
+ final int mode = enforceWritePermissionInner(uri, callingPkg, attributionTag,
+ callerToken);
if (mode != MODE_ALLOWED) {
return mode;
}
- return noteProxyOp(callingPkg, mWriteOp);
+ return noteProxyOp(callingPkg, attributionTag, mWriteOp);
}
- private int noteProxyOp(String callingPkg, int op) {
+ private int noteProxyOp(String callingPkg, String attributionTag, int op) {
if (op != AppOpsManager.OP_NONE) {
- int mode = mAppOpsManager.noteProxyOp(op, callingPkg);
+ int mode = mAppOpsManager.noteProxyOp(op, callingPkg, Binder.getCallingUid(),
+ attributionTag, null);
return mode == MODE_DEFAULT ? MODE_IGNORED : mode;
}
@@ -628,9 +714,11 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
boolean checkUser(int pid, int uid, Context context) {
- return UserHandle.getUserId(uid) == context.getUserId()
- || mSingleUser
- || context.checkPermission(INTERACT_ACROSS_USERS, pid, uid)
+ if (UserHandle.getUserId(uid) == context.getUserId() || mSingleUser) {
+ return true;
+ }
+ return context.checkPermission(INTERACT_ACROSS_USERS, pid, uid) == PERMISSION_GRANTED
+ || context.checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid)
== PERMISSION_GRANTED;
}
@@ -639,18 +727,19 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
* associated with that permission.
*/
private int checkPermissionAndAppOp(String permission, String callingPkg,
- IBinder callerToken) {
+ @Nullable String attributionTag, IBinder callerToken) {
if (getContext().checkPermission(permission, Binder.getCallingPid(), Binder.getCallingUid(),
callerToken) != PERMISSION_GRANTED) {
return MODE_ERRORED;
}
- return mTransport.noteProxyOp(callingPkg, AppOpsManager.permissionToOpCode(permission));
+ return mTransport.noteProxyOp(callingPkg, attributionTag,
+ AppOpsManager.permissionToOpCode(permission));
}
/** {@hide} */
- protected int enforceReadPermissionInner(Uri uri, String callingPkg, IBinder callerToken)
- throws SecurityException {
+ protected int enforceReadPermissionInner(Uri uri, String callingPkg,
+ @Nullable String attributionTag, IBinder callerToken) throws SecurityException {
final Context context = getContext();
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -664,7 +753,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
if (mExported && checkUser(pid, uid, context)) {
final String componentPerm = getReadPermission();
if (componentPerm != null) {
- final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, callerToken);
+ final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, attributionTag,
+ callerToken);
if (mode == MODE_ALLOWED) {
return MODE_ALLOWED;
} else {
@@ -683,7 +773,8 @@ 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, callerToken);
+ final int mode = checkPermissionAndAppOp(pathPerm, callingPkg,
+ attributionTag, callerToken);
if (mode == MODE_ALLOWED) {
return MODE_ALLOWED;
} else {
@@ -731,8 +822,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
/** {@hide} */
- protected int enforceWritePermissionInner(Uri uri, String callingPkg, IBinder callerToken)
- throws SecurityException {
+ protected int enforceWritePermissionInner(Uri uri, String callingPkg,
+ @Nullable String attributionTag, IBinder callerToken) throws SecurityException {
final Context context = getContext();
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -746,7 +837,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
if (mExported && checkUser(pid, uid, context)) {
final String componentPerm = getWritePermission();
if (componentPerm != null) {
- final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, callerToken);
+ final int mode = checkPermissionAndAppOp(componentPerm, callingPkg,
+ attributionTag, callerToken);
if (mode == MODE_ALLOWED) {
return MODE_ALLOWED;
} else {
@@ -765,7 +857,8 @@ 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, callerToken);
+ final int mode = checkPermissionAndAppOp(pathPerm, callingPkg,
+ attributionTag, callerToken);
if (mode == MODE_ALLOWED) {
return MODE_ALLOWED;
} else {
@@ -814,11 +907,28 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
/**
- * Set the calling package, returning the current value (or {@code null})
+ * Retrieves a Non-Nullable Context this provider is running in, this is intended to be called
+ * after {@link #onCreate}. When called before context was created, an IllegalStateException
+ * will be thrown.
+ * <p>
+ * Note A provider must be declared in the manifest and created automatically by the system,
+ * and context is only available after {@link #onCreate} is called.
+ */
+ @NonNull
+ public final Context requireContext() {
+ final Context ctx = getContext();
+ if (ctx == null) {
+ throw new IllegalStateException("Cannot find context from the provider.");
+ }
+ return ctx;
+ }
+
+ /**
+ * Set the calling package/feature, returning the current value (or {@code null})
* which can be used later to restore the previous state.
*/
- private String setCallingPackage(String callingPackage) {
- final String original = mCallingPackage.get();
+ private Pair<String, String> setCallingPackage(Pair<String, String> callingPackage) {
+ final Pair<String, String> original = mCallingPackage.get();
mCallingPackage.set(callingPackage);
onCallingPackageChanged();
return original;
@@ -839,19 +949,71 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
* calling UID.
*/
public final @Nullable String getCallingPackage() {
- final String pkg = mCallingPackage.get();
+ final Pair<String, String> pkg = mCallingPackage.get();
if (pkg != null) {
- mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg);
+ mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg.first);
+ return pkg.first;
}
- return pkg;
+
+ return null;
}
- /** {@hide} */
+ /**
+ * Return the attribution tag of the caller that initiated the request being
+ * processed on the current thread. Returns {@code null} if not currently processing
+ * a request of the request is for the default attribution.
+ * <p>
+ * This will always return {@code null} when processing
+ * {@link #getType(Uri)} or {@link #getStreamTypes(Uri, String)} requests.
+ *
+ * @see #getCallingPackage
+ */
+ public final @Nullable String getCallingAttributionTag() {
+ final Pair<String, String> pkg = mCallingPackage.get();
+ if (pkg != null) {
+ return pkg.second;
+ }
+
+ return null;
+ }
+
+ /**
+ * @removed
+ */
+ @Deprecated
+ public final @Nullable String getCallingFeatureId() {
+ return getCallingAttributionTag();
+ }
+
+ /**
+ * Return the package name of the caller that initiated the request being
+ * processed on the current thread. The returned package will have
+ * <em>not</em> been verified to belong to the calling UID. Returns
+ * {@code null} if not currently processing a request.
+ * <p>
+ * This will always return {@code null} when processing
+ * {@link #getType(Uri)} or {@link #getStreamTypes(Uri, String)} requests.
+ *
+ * @see Binder#getCallingUid()
+ * @see Context#grantUriPermission(String, Uri, int)
+ */
public final @Nullable String getCallingPackageUnchecked() {
- return mCallingPackage.get();
+ final Pair<String, String> pkg = mCallingPackage.get();
+ if (pkg != null) {
+ return pkg.first;
+ }
+
+ return null;
}
- /** {@hide} */
+ /**
+ * Called whenever the value of {@link #getCallingPackage()} changes, giving
+ * the provider an opportunity to invalidate any security related caching it
+ * may be performing.
+ * <p>
+ * This typically happens when a {@link ContentProvider} makes a nested call
+ * back into itself when already processing a call from a remote process.
+ */
public void onCallingPackageChanged() {
}
@@ -862,10 +1024,10 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
/** {@hide} */
public final long binderToken;
/** {@hide} */
- public final String callingPackage;
+ public final Pair<String, String> callingPackage;
/** {@hide} */
- public CallingIdentity(long binderToken, String callingPackage) {
+ public CallingIdentity(long binderToken, Pair<String, String> callingPackage) {
this.binderToken = binderToken;
this.callingPackage = callingPackage;
}
@@ -1013,6 +1175,9 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
/** @hide */
public final void setTransportLoggingEnabled(boolean enabled) {
+ if (mTransport == null) {
+ return;
+ }
if (enabled) {
mTransport.mInterface = new LoggingContentInterface(getClass().getSimpleName(), this);
} else {
@@ -1286,8 +1451,11 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
* @param uri The URI to query. This will be the full URI sent by the client.
* @param projection The list of columns to put into the cursor.
* If {@code null} provide a default set of columns.
- * @param queryArgs A Bundle containing all additional information necessary for the query.
- * Values in the Bundle may include SQL style arguments.
+ * @param queryArgs A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
* @param cancellationSignal A signal to cancel the operation in progress,
* or {@code null}.
* @return a Cursor or {@code null}.
@@ -1389,34 +1557,65 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
/**
- * Implement this to support refresh of content identified by {@code uri}. By default, this
- * method returns false; providers who wish to implement this should return true to signal the
- * client that the provider has tried refreshing with its own implementation.
+ * Implement this to support refresh of content identified by {@code uri}.
+ * By default, this method returns false; providers who wish to implement
+ * this should return true to signal the client that the provider has tried
+ * refreshing with its own implementation.
* <p>
- * This allows clients to request an explicit refresh of content identified by {@code uri}.
+ * This allows clients to request an explicit refresh of content identified
+ * by {@code uri}.
* <p>
- * Client code should only invoke this method when there is a strong indication (such as a user
- * initiated pull to refresh gesture) that the content is stale.
+ * Client code should only invoke this method when there is a strong
+ * indication (such as a user initiated pull to refresh gesture) that the
+ * content is stale.
* <p>
- * Remember to send {@link ContentResolver#notifyChange(Uri, android.database.ContentObserver)}
+ * Remember to send
+ * {@link ContentResolver#notifyChange(Uri, android.database.ContentObserver)}
* notifications when content changes.
*
* @param uri The Uri identifying the data to refresh.
- * @param args Additional options from the client. The definitions of these are specific to the
- * content provider being called.
- * @param cancellationSignal A signal to cancel the operation in progress, or {@code null} if
- * none. For example, if you called refresh on a particular uri, you should call
- * {@link CancellationSignal#throwIfCanceled()} to check whether the client has
- * canceled the refresh request.
+ * @param extras Additional options from the client. The definitions of
+ * these are specific to the content provider being called.
+ * @param cancellationSignal A signal to cancel the operation in progress,
+ * or {@code null} if none. For example, if you called refresh on
+ * a particular uri, you should call
+ * {@link CancellationSignal#throwIfCanceled()} to check whether
+ * the client has canceled the refresh request.
* @return true if the provider actually tried refreshing.
*/
@Override
- public boolean refresh(Uri uri, @Nullable Bundle args,
+ public boolean refresh(Uri uri, @Nullable Bundle extras,
@Nullable CancellationSignal cancellationSignal) {
return false;
}
/**
+ * Perform a detailed internal check on a {@link Uri} to determine if a UID
+ * is able to access it with specific mode flags.
+ * <p>
+ * This method is typically used when the provider implements more dynamic
+ * access controls that cannot be expressed with {@code <path-permission>}
+ * style static rules.
+ * <p>
+ * Because validation of these dynamic access controls has significant
+ * system health impact, this feature is only available to providers that
+ * are built into the system.
+ *
+ * @param uri the {@link Uri} to perform an access check on.
+ * @param uid the UID to check the permission for.
+ * @param modeFlags the access flags to use for the access check, such as
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}.
+ * @return {@link PackageManager#PERMISSION_GRANTED} if access is allowed,
+ * otherwise {@link PackageManager#PERMISSION_DENIED}.
+ * @hide
+ */
+ @Override
+ @SystemApi
+ public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ /**
* @hide
* Implementation when a caller has performed an insert on the content
* provider, but that call has been rejected for the operation given
@@ -1433,21 +1632,47 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
/**
- * Implement this to handle requests to insert a new row.
- * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
- * after inserting.
- * This method can be called from multiple threads, as described in
- * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * Implement this to handle requests to insert a new row. As a courtesy,
+ * call
+ * {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver)
+ * notifyChange()} after inserting. This method can be called from multiple
+ * threads, as described in <a href="
+ * {@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
* and Threads</a>.
- * @param uri The content:// URI of the insertion request. This must not be {@code null}.
+ *
+ * @param uri The content:// URI of the insertion request.
* @param values A set of column_name/value pairs to add to the database.
- * This must not be {@code null}.
* @return The URI for the newly inserted item.
*/
- @Override
public abstract @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues values);
/**
+ * Implement this to handle requests to insert a new row. As a courtesy,
+ * call
+ * {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver)
+ * notifyChange()} after inserting. This method can be called from multiple
+ * threads, as described in <a href="
+ * {@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * @param uri The content:// URI of the insertion request.
+ * @param values A set of column_name/value pairs to add to the database.
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
+ * @return The URI for the newly inserted item.
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
+ */
+ @Override
+ public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues values,
+ @Nullable Bundle extras) {
+ return insert(uri, values);
+ }
+
+ /**
* Override this to handle requests to insert a set of new rows, or the
* default implementation will iterate over the values and call
* {@link #insert} on each of them.
@@ -1472,51 +1697,118 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
/**
- * Implement this to handle requests to delete one or more rows.
- * The implementation should apply the selection clause when performing
+ * Implement this to handle requests to delete one or more rows. The
+ * implementation should apply the selection clause when performing
* deletion, allowing the operation to affect multiple rows in a directory.
- * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
- * after deleting.
- * This method can be called from multiple threads, as described in
- * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * As a courtesy, call
+ * {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver)
+ * notifyChange()} after deleting. This method can be called from multiple
+ * threads, as described in <a href="
+ * {@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
* and Threads</a>.
- *
- * <p>The implementation is responsible for parsing out a row ID at the end
- * of the URI, if a specific row is being deleted. That is, the client would
- * pass in <code>content://contacts/people/22</code> and the implementation is
- * responsible for parsing the record number (22) when creating a SQL statement.
- *
- * @param uri The full URI to query, including a row ID (if a specific record is requested).
+ * <p>
+ * The implementation is responsible for parsing out a row ID at the end of
+ * the URI, if a specific row is being deleted. That is, the client would
+ * pass in <code>content://contacts/people/22</code> and the implementation
+ * is responsible for parsing the record number (22) when creating a SQL
+ * statement.
+ *
+ * @param uri The full URI to query, including a row ID (if a specific
+ * record is requested).
* @param selection An optional restriction to apply to rows when deleting.
* @return The number of rows affected.
* @throws SQLException
*/
- @Override
public abstract int delete(@NonNull Uri uri, @Nullable String selection,
@Nullable String[] selectionArgs);
/**
- * Implement this to handle requests to update one or more rows.
- * The implementation should update all rows matching the selection
- * to set the columns according to the provided values map.
- * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
- * after updating.
- * This method can be called from multiple threads, as described in
- * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * Implement this to handle requests to delete one or more rows. The
+ * implementation should apply the selection clause when performing
+ * deletion, allowing the operation to affect multiple rows in a directory.
+ * As a courtesy, call
+ * {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver)
+ * notifyChange()} after deleting. This method can be called from multiple
+ * threads, as described in <a href="
+ * {@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ * <p>
+ * The implementation is responsible for parsing out a row ID at the end of
+ * the URI, if a specific row is being deleted. That is, the client would
+ * pass in <code>content://contacts/people/22</code> and the implementation
+ * is responsible for parsing the record number (22) when creating a SQL
+ * statement.
+ *
+ * @param uri The full URI to query, including a row ID (if a specific
+ * record is requested).
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
+ * @throws SQLException
+ */
+ @Override
+ public int delete(@NonNull Uri uri, @Nullable Bundle extras) {
+ extras = (extras != null) ? extras : Bundle.EMPTY;
+ return delete(uri,
+ extras.getString(ContentResolver.QUERY_ARG_SQL_SELECTION),
+ extras.getStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS));
+ }
+
+ /**
+ * Implement this to handle requests to update one or more rows. The
+ * implementation should update all rows matching the selection to set the
+ * columns according to the provided values map. As a courtesy, call
+ * {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver)
+ * notifyChange()} after updating. This method can be called from multiple
+ * threads, as described in <a href="
+ * {@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
* and Threads</a>.
*
- * @param uri The URI to query. This can potentially have a record ID if this
- * is an update request for a specific record.
+ * @param uri The URI to query. This can potentially have a record ID if
+ * this is an update request for a specific record.
* @param values A set of column_name/value pairs to update in the database.
- * This must not be {@code null}.
* @param selection An optional filter to match rows to update.
* @return the number of rows affected.
*/
- @Override
public abstract int update(@NonNull Uri uri, @Nullable ContentValues values,
@Nullable String selection, @Nullable String[] selectionArgs);
/**
+ * Implement this to handle requests to update one or more rows. The
+ * implementation should update all rows matching the selection to set the
+ * columns according to the provided values map. As a courtesy, call
+ * {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver)
+ * notifyChange()} after updating. This method can be called from multiple
+ * threads, as described in <a href="
+ * {@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * @param uri The URI to query. This can potentially have a record ID if
+ * this is an update request for a specific record.
+ * @param values A set of column_name/value pairs to update in the database.
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
+ * @return the number of rows affected.
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
+ */
+ @Override
+ public int update(@NonNull Uri uri, @Nullable ContentValues values,
+ @Nullable Bundle extras) {
+ extras = (extras != null) ? extras : Bundle.EMPTY;
+ return update(uri, values,
+ extras.getString(ContentResolver.QUERY_ARG_SQL_SELECTION),
+ extras.getStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS));
+ }
+
+ /**
* Override this to handle requests to open a file blob.
* The default implementation always throws {@link FileNotFoundException}.
* This method can be called from multiple threads, as described in
@@ -2088,6 +2380,10 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
setAuthorities(info.authority);
}
+ if (Build.IS_DEBUGGABLE) {
+ setTransportLoggingEnabled(Log.isLoggable(getClass().getSimpleName(),
+ Log.VERBOSE));
+ }
ContentProvider.this.onCreate();
}
}
@@ -2275,6 +2571,16 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
/**
+ * Returns the user associated with the given URI.
+ *
+ * @hide
+ */
+ @TestApi
+ public @NonNull static UserHandle getUserHandleFromUri(@NonNull Uri uri) {
+ return UserHandle.of(getUserIdFromUri(uri, Process.myUserHandle().getIdentifier()));
+ }
+
+ /**
* Removes userId part from authority string. Expects format:
* userId@some.authority
* If there is no userId in the authority, it symply returns the argument