Extend insert/update/delete to provide extras.

A few releases ago we added ContentResolver.QUERY_ARG_* constants
to query() as a new best-practice that will help wean us off raw
SQL arguments.  (For example, a provider could add their own
custom arguments like QUERY_ARG_INCLUDE_PENDING to cause the query
to reveal pending items that would otherwise be hidden.)  This
change expands update() and delete() to accept those arguments.

This change also expand insert() to accept extras too, as part of
preparing to support an upcoming MediaProvider feature that will let
apps place new media "adjacent" to an existing media item.  (Sending
that adjacent item through extras is cleaner than trying to send it
through escaped query parameters.)

Bug: 131643582
Test: atest CtsContentTestCases
Change-Id: I436296155b9b5f371b4cbe661feaf42070285fcc
This commit is contained in:
Jeff Sharkey
2019-11-15 12:45:15 -07:00
parent b500cb85ff
commit e9fe152f31
11 changed files with 320 additions and 152 deletions

View File

@@ -9475,6 +9475,7 @@ package android.content {
method @Nullable public android.net.Uri canonicalize(@NonNull android.net.Uri);
method @NonNull public final android.content.ContentProvider.CallingIdentity clearCallingIdentity();
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 String getCallingFeatureId();
method @Nullable public final String getCallingPackage();
@@ -9485,6 +9486,7 @@ package android.content {
method @Nullable public abstract String getType(@NonNull android.net.Uri);
method @Nullable public final String getWritePermission();
method @Nullable public abstract android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues);
method @Nullable public android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues, @Nullable android.os.Bundle);
method protected boolean isTemporary();
method public void onConfigurationChanged(android.content.res.Configuration);
method public abstract boolean onCreate();
@@ -9510,6 +9512,7 @@ package android.content {
method public void shutdown();
method @Nullable public android.net.Uri uncanonicalize(@NonNull android.net.Uri);
method public abstract int update(@NonNull android.net.Uri, @Nullable android.content.ContentValues, @Nullable String, @Nullable String[]);
method public int update(@NonNull android.net.Uri, @Nullable android.content.ContentValues, @Nullable android.os.Bundle);
}
public final class ContentProvider.CallingIdentity {
@@ -9528,10 +9531,12 @@ package android.content {
method @Nullable public final android.net.Uri canonicalize(@NonNull android.net.Uri) throws android.os.RemoteException;
method public void close();
method public int delete(@NonNull android.net.Uri, @Nullable String, @Nullable String[]) throws android.os.RemoteException;
method public int delete(@NonNull android.net.Uri, @Nullable android.os.Bundle) throws android.os.RemoteException;
method @Nullable public android.content.ContentProvider getLocalContentProvider();
method @Nullable public String[] getStreamTypes(@NonNull android.net.Uri, @NonNull String) throws android.os.RemoteException;
method @Nullable public String getType(@NonNull android.net.Uri) throws android.os.RemoteException;
method @Nullable public android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues) throws android.os.RemoteException;
method @Nullable public android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues, @Nullable android.os.Bundle) throws android.os.RemoteException;
method @Nullable public android.content.res.AssetFileDescriptor openAssetFile(@NonNull android.net.Uri, @NonNull String) throws java.io.FileNotFoundException, android.os.RemoteException;
method @Nullable public android.content.res.AssetFileDescriptor openAssetFile(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException;
method @Nullable public android.os.ParcelFileDescriptor openFile(@NonNull android.net.Uri, @NonNull String) throws java.io.FileNotFoundException, android.os.RemoteException;
@@ -9546,6 +9551,7 @@ package android.content {
method @Deprecated public boolean release();
method @Nullable public final android.net.Uri uncanonicalize(@NonNull android.net.Uri) throws android.os.RemoteException;
method public int update(@NonNull android.net.Uri, @Nullable android.content.ContentValues, @Nullable String, @Nullable String[]) throws android.os.RemoteException;
method public int update(@NonNull android.net.Uri, @Nullable android.content.ContentValues, @Nullable android.os.Bundle) throws android.os.RemoteException;
}
public class ContentProviderOperation implements android.os.Parcelable {
@@ -9633,6 +9639,7 @@ package android.content {
method public static void cancelSync(android.content.SyncRequest);
method @Nullable public final android.net.Uri canonicalize(@NonNull android.net.Uri);
method public final int delete(@NonNull @RequiresPermission.Write android.net.Uri, @Nullable String, @Nullable String[]);
method public final int delete(@NonNull @RequiresPermission.Write android.net.Uri, @Nullable android.os.Bundle);
method @Deprecated public static android.content.SyncInfo getCurrentSync();
method public static java.util.List<android.content.SyncInfo> getCurrentSyncs();
method public static int getIsSyncable(android.accounts.Account, String);
@@ -9646,6 +9653,7 @@ package android.content {
method @Nullable public final String getType(@NonNull android.net.Uri);
method @NonNull public final android.content.ContentResolver.MimeTypeInfo getTypeInfo(@NonNull String);
method @Nullable public final android.net.Uri insert(@NonNull @RequiresPermission.Write android.net.Uri, @Nullable android.content.ContentValues);
method @Nullable public final android.net.Uri insert(@NonNull @RequiresPermission.Write android.net.Uri, @Nullable android.content.ContentValues, @Nullable android.os.Bundle);
method public static boolean isSyncActive(android.accounts.Account, String);
method public static boolean isSyncPending(android.accounts.Account, String);
method @NonNull public android.graphics.Bitmap loadThumbnail(@NonNull android.net.Uri, @NonNull android.util.Size, @Nullable android.os.CancellationSignal) throws java.io.IOException;
@@ -9683,6 +9691,7 @@ package android.content {
method @Nullable public final android.net.Uri uncanonicalize(@NonNull android.net.Uri);
method public final void unregisterContentObserver(@NonNull android.database.ContentObserver);
method public final int update(@NonNull @RequiresPermission.Write android.net.Uri, @Nullable android.content.ContentValues, @Nullable String, @Nullable String[]);
method public final int update(@NonNull @RequiresPermission.Write android.net.Uri, @Nullable android.content.ContentValues, @Nullable android.os.Bundle);
method public static void validateSyncExtrasBundle(android.os.Bundle);
method @NonNull public static android.content.ContentResolver wrap(@NonNull android.content.ContentProvider);
method @NonNull public static android.content.ContentResolver wrap(@NonNull android.content.ContentProviderClient);

View File

@@ -508,7 +508,7 @@ public class Content {
@Override
public void onExecute(IContentProvider provider) throws Exception {
provider.insert(resolveCallingPackage(), null, mUri, mContentValues);
provider.insert(resolveCallingPackage(), null, mUri, mContentValues, null);
}
}
@@ -522,7 +522,8 @@ public class Content {
@Override
public void onExecute(IContentProvider provider) throws Exception {
provider.delete(resolveCallingPackage(), null, mUri, mWhere, null);
provider.delete(resolveCallingPackage(), null, mUri,
ContentResolver.createSqlQueryBundle(mWhere, null));
}
}
@@ -679,7 +680,8 @@ public class Content {
@Override
public void onExecute(IContentProvider provider) throws Exception {
provider.update(resolveCallingPackage(), null, mUri, mContentValues, mWhere, null);
provider.update(resolveCallingPackage(), null, mUri, mContentValues,
ContentResolver.createSqlQueryBundle(mWhere, null));
}
}

View File

@@ -53,23 +53,22 @@ public interface ContentInterface {
public @Nullable Uri uncanonicalize(@NonNull Uri uri) throws RemoteException;
public boolean refresh(@NonNull Uri uri, @Nullable Bundle args,
public boolean refresh(@NonNull Uri uri, @Nullable Bundle extras,
@Nullable CancellationSignal cancellationSignal) throws RemoteException;
public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags)
throws RemoteException;
public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues initialValues)
throws RemoteException;
public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues initialValues,
@Nullable Bundle extras) throws RemoteException;
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] initialValues)
throws RemoteException;
public int delete(@NonNull Uri uri, @Nullable String selection,
@Nullable String[] selectionArgs) throws RemoteException;
public int delete(@NonNull Uri uri, @Nullable Bundle extras) throws RemoteException;
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
@Nullable String[] selectionArgs) throws RemoteException;
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable Bundle extras)
throws RemoteException;
public @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode,
@Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException;

View File

@@ -299,7 +299,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
@Override
public Uri insert(String callingPkg, @Nullable String featureId, Uri uri,
ContentValues initialValues) {
ContentValues initialValues, Bundle extras) {
uri = validateIncomingUri(uri);
int userId = getUserIdFromUri(uri);
uri = maybeGetUriWithoutUserId(uri);
@@ -317,7 +317,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
final Pair<String, String> original = setCallingPackage(
new Pair<>(callingPkg, featureId));
try {
return maybeAddUserId(mInterface.insert(uri, initialValues), userId);
return maybeAddUserId(mInterface.insert(uri, initialValues, extras), userId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
@@ -403,8 +403,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
public int delete(String callingPkg, @Nullable String featureId, Uri uri, String selection,
String[] selectionArgs) {
public int delete(String callingPkg, @Nullable String featureId, Uri uri, Bundle extras) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
if (enforceWritePermission(callingPkg, featureId, uri, null)
@@ -415,7 +414,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
final Pair<String, String> original = setCallingPackage(
new Pair<>(callingPkg, featureId));
try {
return mInterface.delete(uri, selection, selectionArgs);
return mInterface.delete(uri, extras);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
@@ -426,7 +425,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
@Override
public int update(String callingPkg, @Nullable String featureId, Uri uri,
ContentValues values, String selection, String[] selectionArgs) {
ContentValues values, Bundle extras) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
if (enforceWritePermission(callingPkg, featureId, uri, null)
@@ -437,7 +436,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
final Pair<String, String> original = setCallingPackage(
new Pair<>(callingPkg, featureId));
try {
return mInterface.update(uri, values, selection, selectionArgs);
return mInterface.update(uri, values, extras);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
@@ -593,7 +592,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
@Override
public boolean refresh(String callingPkg, String featureId, Uri uri, Bundle args,
public boolean refresh(String callingPkg, String featureId, Uri uri, Bundle extras,
ICancellationSignal cancellationSignal) throws RemoteException {
uri = validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
@@ -605,7 +604,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
final Pair<String, String> original = setCallingPackage(
new Pair<>(callingPkg, featureId));
try {
return mInterface.refresh(uri, args,
return mInterface.refresh(uri, extras,
CancellationSignal.fromTransport(cancellationSignal));
} finally {
setCallingPackage(original);
@@ -1494,29 +1493,34 @@ 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;
}
@@ -1545,19 +1549,41 @@ 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.
* @param values A set of column_name/value pairs to add to the database.
* @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 all additional information necessary
* for the insert.
* @return The URI for the newly inserted item.
*/
@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
@@ -1583,49 +1609,110 @@ 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.
*
* <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 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 all additional information necessary
* for the delete. Values in the Bundle may include SQL style
* arguments.
* @return The number of rows affected.
* @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.
* @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 all additional information necessary
* for the update. Values in the Bundle may include SQL style
* arguments.
* @return the number of rows affected.
*/
@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}.

View File

@@ -286,7 +286,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable {
/** See {@link ContentProvider#refresh} */
@Override
public boolean refresh(Uri url, @Nullable Bundle args,
public boolean refresh(Uri url, @Nullable Bundle extras,
@Nullable CancellationSignal cancellationSignal) throws RemoteException {
Preconditions.checkNotNull(url, "url");
@@ -298,7 +298,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable {
remoteCancellationSignal = mContentProvider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal);
}
return mContentProvider.refresh(mPackageName, mFeatureId, url, args,
return mContentProvider.refresh(mPackageName, mFeatureId, url, extras,
remoteCancellationSignal);
} catch (DeadObjectException e) {
if (!mStable) {
@@ -331,14 +331,20 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable {
}
/** See {@link ContentProvider#insert ContentProvider.insert} */
@Override
public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues)
throws RemoteException {
return insert(url, initialValues, null);
}
/** See {@link ContentProvider#insert ContentProvider.insert} */
@Override
public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues,
@Nullable Bundle extras) throws RemoteException {
Preconditions.checkNotNull(url, "url");
beforeRemote();
try {
return mContentProvider.insert(mPackageName, mFeatureId, url, initialValues);
return mContentProvider.insert(mPackageName, mFeatureId, url, initialValues, extras);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -370,15 +376,19 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable {
}
/** See {@link ContentProvider#delete ContentProvider.delete} */
@Override
public int delete(@NonNull Uri url, @Nullable String selection,
@Nullable String[] selectionArgs) throws RemoteException {
return delete(url, ContentResolver.createSqlQueryBundle(selection, selectionArgs));
}
/** See {@link ContentProvider#delete ContentProvider.delete} */
@Override
public int delete(@NonNull Uri url, @Nullable Bundle extras) throws RemoteException {
Preconditions.checkNotNull(url, "url");
beforeRemote();
try {
return mContentProvider.delete(mPackageName, mFeatureId, url, selection,
selectionArgs);
return mContentProvider.delete(mPackageName, mFeatureId, url, extras);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -390,15 +400,20 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable {
}
/** See {@link ContentProvider#update ContentProvider.update} */
@Override
public int update(@NonNull Uri url, @Nullable ContentValues values, @Nullable String selection,
@Nullable String[] selectionArgs) throws RemoteException {
return update(url, values, ContentResolver.createSqlQueryBundle(selection, selectionArgs));
}
/** See {@link ContentProvider#update ContentProvider.update} */
@Override
public int update(@NonNull Uri url, @Nullable ContentValues values, @Nullable Bundle extras)
throws RemoteException {
Preconditions.checkNotNull(url, "url");
beforeRemote();
try {
return mContentProvider.update(mPackageName, mFeatureId, url, values, selection,
selectionArgs);
return mContentProvider.update(mPackageName, mFeatureId, url, values, extras);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);

View File

@@ -153,8 +153,9 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
String featureId = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
ContentValues values = ContentValues.CREATOR.createFromParcel(data);
Bundle extras = data.readBundle();
Uri out = insert(callingPkg, featureId, url, values);
Uri out = insert(callingPkg, featureId, url, values, extras);
reply.writeNoException();
Uri.writeToParcel(reply, out);
return true;
@@ -199,10 +200,9 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
String callingPkg = data.readString();
String featureId = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
String selection = data.readString();
String[] selectionArgs = data.readStringArray();
Bundle extras = data.readBundle();
int count = delete(callingPkg, featureId, url, selection, selectionArgs);
int count = delete(callingPkg, featureId, url, extras);
reply.writeNoException();
reply.writeInt(count);
@@ -216,11 +216,9 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
String featureId = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
ContentValues values = ContentValues.CREATOR.createFromParcel(data);
String selection = data.readString();
String[] selectionArgs = data.readStringArray();
Bundle extras = data.readBundle();
int count = update(callingPkg, featureId, url, values, selection,
selectionArgs);
int count = update(callingPkg, featureId, url, values, extras);
reply.writeNoException();
reply.writeInt(count);
@@ -283,10 +281,10 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
String authority = data.readString();
String method = data.readString();
String stringArg = data.readString();
Bundle args = data.readBundle();
Bundle extras = data.readBundle();
Bundle responseBundle = call(callingPkg, featureId, authority, method,
stringArg, args);
stringArg, extras);
reply.writeNoException();
reply.writeBundle(responseBundle);
@@ -370,11 +368,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
String callingPkg = data.readString();
String featureId = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
Bundle args = data.readBundle();
Bundle extras = data.readBundle();
ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
data.readStrongBinder());
boolean out = refresh(callingPkg, featureId, url, args, signal);
boolean out = refresh(callingPkg, featureId, url, extras, signal);
reply.writeNoException();
reply.writeInt(out ? 0 : -1);
return true;
@@ -498,7 +496,7 @@ final class ContentProviderProxy implements IContentProvider
@Override
public Uri insert(String callingPkg, @Nullable String featureId, Uri url,
ContentValues values) throws RemoteException
ContentValues values, Bundle extras) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -509,6 +507,7 @@ final class ContentProviderProxy implements IContentProvider
data.writeString(featureId);
url.writeToParcel(data, 0);
values.writeToParcel(data, 0);
data.writeBundle(extras);
mRemote.transact(IContentProvider.INSERT_TRANSACTION, data, reply, 0);
@@ -573,8 +572,8 @@ final class ContentProviderProxy implements IContentProvider
}
@Override
public int delete(String callingPkg, @Nullable String featureId, Uri url, String selection,
String[] selectionArgs) throws RemoteException {
public int delete(String callingPkg, @Nullable String featureId, Uri url, Bundle extras)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
@@ -583,8 +582,7 @@ final class ContentProviderProxy implements IContentProvider
data.writeString(callingPkg);
data.writeString(featureId);
url.writeToParcel(data, 0);
data.writeString(selection);
data.writeStringArray(selectionArgs);
data.writeBundle(extras);
mRemote.transact(IContentProvider.DELETE_TRANSACTION, data, reply, 0);
@@ -599,7 +597,7 @@ final class ContentProviderProxy implements IContentProvider
@Override
public int update(String callingPkg, @Nullable String featureId, Uri url,
ContentValues values, String selection, String[] selectionArgs) throws RemoteException {
ContentValues values, Bundle extras) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
@@ -609,8 +607,7 @@ final class ContentProviderProxy implements IContentProvider
data.writeString(featureId);
url.writeToParcel(data, 0);
values.writeToParcel(data, 0);
data.writeString(selection);
data.writeStringArray(selectionArgs);
data.writeBundle(extras);
mRemote.transact(IContentProvider.UPDATE_TRANSACTION, data, reply, 0);
@@ -682,7 +679,7 @@ final class ContentProviderProxy implements IContentProvider
@Override
public Bundle call(String callingPkg, @Nullable String featureId, String authority,
String method, String request, Bundle args) throws RemoteException {
String method, String request, Bundle extras) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
@@ -693,7 +690,7 @@ final class ContentProviderProxy implements IContentProvider
data.writeString(authority);
data.writeString(method);
data.writeString(request);
data.writeBundle(args);
data.writeBundle(extras);
mRemote.transact(IContentProvider.CALL_TRANSACTION, data, reply, 0);
@@ -824,7 +821,7 @@ final class ContentProviderProxy implements IContentProvider
}
@Override
public boolean refresh(String callingPkg, @Nullable String featureId, Uri url, Bundle args,
public boolean refresh(String callingPkg, @Nullable String featureId, Uri url, Bundle extras,
ICancellationSignal signal) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -834,7 +831,7 @@ final class ContentProviderProxy implements IContentProvider
data.writeString(callingPkg);
data.writeString(featureId);
url.writeToParcel(data, 0);
data.writeBundle(args);
data.writeBundle(extras);
data.writeStrongBinder(signal != null ? signal.asBinder() : null);
mRemote.transact(IContentProvider.REFRESH_TRANSACTION, data, reply, 0);

View File

@@ -1181,28 +1181,31 @@ public abstract class ContentResolver implements ContentInterface {
}
/**
* 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>
*
* @param url 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 final boolean refresh(@NonNull Uri url, @Nullable Bundle args,
public final boolean refresh(@NonNull Uri url, @Nullable Bundle extras,
@Nullable CancellationSignal cancellationSignal) {
Preconditions.checkNotNull(url, "url");
try {
if (mWrapped != null) return mWrapped.refresh(url, args, cancellationSignal);
if (mWrapped != null) return mWrapped.refresh(url, extras, cancellationSignal);
} catch (RemoteException e) {
return false;
}
@@ -1219,7 +1222,7 @@ public abstract class ContentResolver implements ContentInterface {
remoteCancellationSignal = provider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal);
}
return provider.refresh(mPackageName, mFeatureId, url, args,
return provider.refresh(mPackageName, mFeatureId, url, extras,
remoteCancellationSignal);
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
@@ -1910,13 +1913,30 @@ public abstract class ContentResolver implements ContentInterface {
* @return the URL of the newly created row. May return <code>null</code> if the underlying
* content provider returns <code>null</code>, or if it crashes.
*/
@Override
public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
@Nullable ContentValues values) {
return insert(url, values, null);
}
/**
* Inserts a row into a table at the given URL.
*
* If the content provider supports transactions the insertion will be atomic.
*
* @param url The URL of the table to insert into.
* @param values The initial values for the newly inserted row. The key is the column name for
* the field. Passing an empty ContentValues will create an empty row.
* @param extras A Bundle containing all additional information necessary for the insert.
* @return the URL of the newly created row. May return <code>null</code> if the underlying
* content provider returns <code>null</code>, or if it crashes.
*/
@Override
public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
@Nullable ContentValues values, @Nullable Bundle extras) {
Preconditions.checkNotNull(url, "url");
try {
if (mWrapped != null) return mWrapped.insert(url, values);
if (mWrapped != null) return mWrapped.insert(url, values, extras);
} catch (RemoteException e) {
return null;
}
@@ -1927,7 +1947,7 @@ public abstract class ContentResolver implements ContentInterface {
}
try {
long startTime = SystemClock.uptimeMillis();
Uri createdRow = provider.insert(mPackageName, mFeatureId, url, values);
Uri createdRow = provider.insert(mPackageName, mFeatureId, url, values, extras);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);
return createdRow;
@@ -2031,13 +2051,27 @@ public abstract class ContentResolver implements ContentInterface {
(excluding the WHERE itself).
* @return The number of rows deleted.
*/
@Override
public final int delete(@RequiresPermission.Write @NonNull Uri url, @Nullable String where,
@Nullable String[] selectionArgs) {
return delete(url, createSqlQueryBundle(where, selectionArgs));
}
/**
* Deletes row(s) specified by a content URI.
*
* If the content provider supports transactions, the deletion will be atomic.
*
* @param url The URL of the row to delete.
* @param extras A Bundle containing all additional information necessary for the delete.
* Values in the Bundle may include SQL style arguments.
* @return The number of rows deleted.
*/
@Override
public final int delete(@RequiresPermission.Write @NonNull Uri url, @Nullable Bundle extras) {
Preconditions.checkNotNull(url, "url");
try {
if (mWrapped != null) return mWrapped.delete(url, where, selectionArgs);
if (mWrapped != null) return mWrapped.delete(url, extras);
} catch (RemoteException e) {
return 0;
}
@@ -2048,10 +2082,9 @@ public abstract class ContentResolver implements ContentInterface {
}
try {
long startTime = SystemClock.uptimeMillis();
int rowsDeleted = provider.delete(mPackageName, mFeatureId, url, where,
selectionArgs);
int rowsDeleted = provider.delete(mPackageName, mFeatureId, url, extras);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "delete", where);
maybeLogUpdateToEventLog(durationMillis, url, "delete", null);
return rowsDeleted;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
@@ -2075,14 +2108,32 @@ public abstract class ContentResolver implements ContentInterface {
* @return the number of rows updated.
* @throws NullPointerException if uri or values are null
*/
@Override
public final int update(@RequiresPermission.Write @NonNull Uri uri,
@Nullable ContentValues values, @Nullable String where,
@Nullable String[] selectionArgs) {
return update(uri, values, createSqlQueryBundle(where, selectionArgs));
}
/**
* Update row(s) in a content URI.
*
* If the content provider supports transactions the update will be atomic.
*
* @param uri The URI to modify.
* @param values The new field values. The key is the column name for the field.
A null value will remove an existing field value.
* @param extras A Bundle containing all additional information necessary for the update.
* Values in the Bundle may include SQL style arguments.
* @return the number of rows updated.
* @throws NullPointerException if uri or values are null
*/
@Override
public final int update(@RequiresPermission.Write @NonNull Uri uri,
@Nullable ContentValues values, @Nullable Bundle extras) {
Preconditions.checkNotNull(uri, "uri");
try {
if (mWrapped != null) return mWrapped.update(uri, values, where, selectionArgs);
if (mWrapped != null) return mWrapped.update(uri, values, extras);
} catch (RemoteException e) {
return 0;
}
@@ -2093,10 +2144,9 @@ public abstract class ContentResolver implements ContentInterface {
}
try {
long startTime = SystemClock.uptimeMillis();
int rowsUpdated = provider.update(mPackageName, mFeatureId, uri, values, where,
selectionArgs);
int rowsUpdated = provider.update(mPackageName, mFeatureId, uri, values, extras);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, uri, "update", where);
maybeLogUpdateToEventLog(durationMillis, uri, "update", null);
return rowsUpdated;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
@@ -3638,6 +3688,15 @@ public abstract class ContentResolver implements ContentInterface {
}
}
/**
* @hide
*/
public static @Nullable Bundle createSqlQueryBundle(
@Nullable String selection,
@Nullable String[] selectionArgs) {
return createSqlQueryBundle(selection, selectionArgs, null);
}
/**
* @hide
*/

View File

@@ -48,10 +48,10 @@ public interface IContentProvider extends IInterface {
+ "instead")
public default Uri insert(String callingPkg, Uri url, ContentValues initialValues)
throws RemoteException {
return insert(callingPkg, null, url, initialValues);
return insert(callingPkg, null, url, initialValues, null);
}
public Uri insert(String callingPkg, String featureId, Uri url, ContentValues initialValues)
throws RemoteException;
public Uri insert(String callingPkg, String featureId, 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[])"
@@ -68,20 +68,22 @@ public interface IContentProvider extends IInterface {
+ ".String[])} instead")
public default int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
throws RemoteException {
return delete(callingPkg, null, url, selection, selectionArgs);
return delete(callingPkg, null, url,
ContentResolver.createSqlQueryBundle(selection, selectionArgs));
}
public int delete(String callingPkg, String featureId, Uri url, String selection,
String[] selectionArgs) throws RemoteException;
public int delete(String callingPkg, String featureId, 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,
String[] selectionArgs) throws RemoteException {
return update(callingPkg, null, url, values, selection, selectionArgs);
return update(callingPkg, null, url, values,
ContentResolver.createSqlQueryBundle(selection, selectionArgs));
}
public int update(String callingPkg, String featureId, Uri url, ContentValues values,
String selection, String[] selectionArgs) throws RemoteException;
Bundle extras) throws RemoteException;
public ParcelFileDescriptor openFile(String callingPkg, @Nullable String featureId, Uri url,
String mode, ICancellationSignal signal, IBinder callerToken)
@@ -119,7 +121,7 @@ public interface IContentProvider extends IInterface {
throws RemoteException;
public boolean refresh(String callingPkg, @Nullable String featureId, Uri url,
@Nullable Bundle args, ICancellationSignal cancellationSignal) throws RemoteException;
@Nullable Bundle extras, ICancellationSignal cancellationSignal) throws RemoteException;
// Data interchange.
public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;

View File

@@ -178,11 +178,11 @@ public class LoggingContentInterface implements ContentInterface {
}
@Override
public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues initialValues)
throws RemoteException {
try (Logger l = new Logger("insert", uri, initialValues)) {
public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues initialValues,
@Nullable Bundle extras) throws RemoteException {
try (Logger l = new Logger("insert", uri, initialValues, extras)) {
try {
return l.setResult(delegate.insert(uri, initialValues));
return l.setResult(delegate.insert(uri, initialValues, extras));
} catch (Exception res) {
l.setResult(res);
throw res;
@@ -204,11 +204,10 @@ public class LoggingContentInterface implements ContentInterface {
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection,
@Nullable String[] selectionArgs) throws RemoteException {
try (Logger l = new Logger("delete", uri, selection, selectionArgs)) {
public int delete(@NonNull Uri uri, @Nullable Bundle extras) throws RemoteException {
try (Logger l = new Logger("delete", uri, extras)) {
try {
return l.setResult(delegate.delete(uri, selection, selectionArgs));
return l.setResult(delegate.delete(uri, extras));
} catch (Exception res) {
l.setResult(res);
throw res;
@@ -217,11 +216,11 @@ public class LoggingContentInterface implements ContentInterface {
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
@Nullable String[] selectionArgs) throws RemoteException {
try (Logger l = new Logger("update", uri, values, selection, selectionArgs)) {
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable Bundle extras)
throws RemoteException {
try (Logger l = new Logger("update", uri, values, extras)) {
try {
return l.setResult(delegate.update(uri, values, selection, selectionArgs));
return l.setResult(delegate.update(uri, values, extras));
} catch (Exception res) {
l.setResult(res);
throw res;

View File

@@ -71,8 +71,8 @@ public class MockContentProvider extends ContentProvider {
@Override
public int delete(String callingPackage, @Nullable String featureId, Uri url,
String selection, String[] selectionArgs) throws RemoteException {
return MockContentProvider.this.delete(url, selection, selectionArgs);
Bundle extras) throws RemoteException {
return MockContentProvider.this.delete(url, extras);
}
@Override
@@ -82,8 +82,8 @@ public class MockContentProvider extends ContentProvider {
@Override
public Uri insert(String callingPackage, @Nullable String featureId, Uri url,
ContentValues initialValues) throws RemoteException {
return MockContentProvider.this.insert(url, initialValues);
ContentValues initialValues, Bundle extras) throws RemoteException {
return MockContentProvider.this.insert(url, initialValues, extras);
}
@Override
@@ -109,9 +109,8 @@ public class MockContentProvider extends ContentProvider {
@Override
public int update(String callingPackage, @Nullable String featureId, Uri url,
ContentValues values, String selection, String[] selectionArgs)
throws RemoteException {
return MockContentProvider.this.update(url, values, selection, selectionArgs);
ContentValues values, Bundle extras) throws RemoteException {
return MockContentProvider.this.update(url, values, extras);
}
@Override

View File

@@ -51,7 +51,7 @@ public class MockIContentProvider implements IContentProvider {
@Override
@SuppressWarnings("unused")
public int delete(String callingPackage, @Nullable String featureId, Uri url,
String selection, String[] selectionArgs) throws RemoteException {
Bundle extras) throws RemoteException {
throw new UnsupportedOperationException("unimplemented mock method");
}
@@ -63,7 +63,7 @@ public class MockIContentProvider implements IContentProvider {
@Override
@SuppressWarnings("unused")
public Uri insert(String callingPackage, @Nullable String featureId, Uri url,
ContentValues initialValues) throws RemoteException {
ContentValues initialValues, Bundle extras) throws RemoteException {
throw new UnsupportedOperationException("unimplemented mock method");
}
@@ -99,7 +99,7 @@ public class MockIContentProvider implements IContentProvider {
@Override
public int update(String callingPackage, @Nullable String featureId, Uri url,
ContentValues values, String selection, String[] selectionArgs) throws RemoteException {
ContentValues values, Bundle extras) throws RemoteException {
throw new UnsupportedOperationException("unimplemented mock method");
}