diff options
| -rw-r--r-- | api/current.txt | 9 | ||||
| -rw-r--r-- | cmds/content/src/com/android/commands/content/Content.java | 8 | ||||
| -rw-r--r-- | core/java/android/content/ContentInterface.java | 13 | ||||
| -rw-r--r-- | core/java/android/content/ContentProvider.java | 193 | ||||
| -rw-r--r-- | core/java/android/content/ContentProviderClient.java | 35 | ||||
| -rw-r--r-- | core/java/android/content/ContentProviderNative.java | 45 | ||||
| -rw-r--r-- | core/java/android/content/ContentResolver.java | 109 | ||||
| -rw-r--r-- | core/java/android/content/IContentProvider.java | 20 | ||||
| -rw-r--r-- | core/java/android/content/LoggingContentInterface.java | 23 | ||||
| -rw-r--r-- | test-mock/src/android/test/mock/MockContentProvider.java | 13 | ||||
| -rw-r--r-- | test-mock/src/android/test/mock/MockIContentProvider.java | 6 |
11 files changed, 321 insertions, 153 deletions
diff --git a/api/current.txt b/api/current.txt index 667f5d2f1d60..b0e2acbf6219 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java index 7e278e964ab5..59544a971e5f 100644 --- a/cmds/content/src/com/android/commands/content/Content.java +++ b/cmds/content/src/com/android/commands/content/Content.java @@ -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)); } } diff --git a/core/java/android/content/ContentInterface.java b/core/java/android/content/ContentInterface.java index 197de9711296..5988dd3914f1 100644 --- a/core/java/android/content/ContentInterface.java +++ b/core/java/android/content/ContentInterface.java @@ -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; diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 17f1a07d6e01..2240823ebe92 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -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,20 +1549,42 @@ 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 * {@link #insert} on each of them. @@ -1583,50 +1609,111 @@ 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 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}. * This method can be called from multiple threads, as described in diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index d2632e78c00c..bb65aa013f72 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -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); diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index f082690e2ceb..dfa71f88fa0a 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -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); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index a93c6d25c38f..d4280f8992c2 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -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 @@ -3643,6 +3693,15 @@ public abstract class ContentResolver implements ContentInterface { */ public static @Nullable Bundle createSqlQueryBundle( @Nullable String selection, + @Nullable String[] selectionArgs) { + return createSqlQueryBundle(selection, selectionArgs, null); + } + + /** + * @hide + */ + public static @Nullable Bundle createSqlQueryBundle( + @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index d2c97c4ebdd3..1fb29586e15b 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -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; diff --git a/core/java/android/content/LoggingContentInterface.java b/core/java/android/content/LoggingContentInterface.java index 1df1c4faf2fe..3bd083268d45 100644 --- a/core/java/android/content/LoggingContentInterface.java +++ b/core/java/android/content/LoggingContentInterface.java @@ -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; diff --git a/test-mock/src/android/test/mock/MockContentProvider.java b/test-mock/src/android/test/mock/MockContentProvider.java index 9d3e12050193..85e5916a63df 100644 --- a/test-mock/src/android/test/mock/MockContentProvider.java +++ b/test-mock/src/android/test/mock/MockContentProvider.java @@ -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 diff --git a/test-mock/src/android/test/mock/MockIContentProvider.java b/test-mock/src/android/test/mock/MockIContentProvider.java index e512b52643f3..464abfb1a514 100644 --- a/test-mock/src/android/test/mock/MockIContentProvider.java +++ b/test-mock/src/android/test/mock/MockIContentProvider.java @@ -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"); } |