Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e9fe152f authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

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
parent b500cb85
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -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);
+5 −3
Original line number Diff line number Diff line
@@ -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));
        }
    }

+6 −7
Original line number Diff line number Diff line
@@ -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;
+140 −53
Original line number Diff line number Diff line
@@ -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.
     *
     * @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}.
+25 −10

File changed.

Preview size limit exceeded, changes collapsed.

Loading