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

Commit 633a13e2 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Extract common methods into ContentInterface.

Existing APIs that accept a ContentResolver are too restrictive when
the caller has their own ContentProviderClient already bound and
configured, so we're in the market for a solution to open those
existing APIs to accept a wider range of inputs.

The solution we've come up with is to introduce a super-interface
which contains the common ContentProvider APIs, and then make
ContentProvider, ContentResolver, and ContentProviderClient all
implement that interface for consistency.

After this change lands, we can then safely relax existing APIs to
accept this new ContentInterface, offering a clean path to solving
the problem outlined above.

Bug: 117635768
Test: atest android.content.cts
Test: atest android.provider.cts
Change-Id: Ic5ae08107f7dd3dd23dcaec2df40c16543e0d86e
Exempted-From-Owner-Approval: keep tests working
parent dfeea604
Loading
Loading
Loading
Loading
+30 −3
Original line number Diff line number Diff line
@@ -9227,11 +9227,31 @@ package android.content {
    field public static final android.os.Parcelable.Creator<android.content.ComponentName> CREATOR;
  }
  public abstract class ContentProvider implements android.content.ComponentCallbacks2 {
  public abstract interface ContentInterface {
    method public abstract android.content.ContentProviderResult[] applyBatch(java.lang.String, java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException;
    method public abstract int bulkInsert(android.net.Uri, android.content.ContentValues[]) throws android.os.RemoteException;
    method public abstract android.os.Bundle call(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException;
    method public abstract android.net.Uri canonicalize(android.net.Uri) throws android.os.RemoteException;
    method public abstract int delete(android.net.Uri, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
    method public abstract java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String) throws android.os.RemoteException;
    method public abstract java.lang.String getType(android.net.Uri) throws android.os.RemoteException;
    method public abstract android.net.Uri insert(android.net.Uri, android.content.ContentValues) throws android.os.RemoteException;
    method public abstract android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException;
    method public abstract android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException;
    method public abstract android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException;
    method public abstract android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal) throws android.os.RemoteException;
    method public abstract boolean refresh(android.net.Uri, android.os.Bundle, android.os.CancellationSignal) throws android.os.RemoteException;
    method public abstract android.net.Uri uncanonicalize(android.net.Uri) throws android.os.RemoteException;
    method public abstract int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
  }
  public abstract class ContentProvider implements android.content.ComponentCallbacks2 android.content.ContentInterface {
    ctor public ContentProvider();
    method public android.content.ContentProviderResult[] applyBatch(java.lang.String, java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException;
    method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException;
    method public void attachInfo(android.content.Context, android.content.pm.ProviderInfo);
    method public int bulkInsert(android.net.Uri, android.content.ContentValues[]);
    method public android.os.Bundle call(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle);
    method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle);
    method public android.net.Uri canonicalize(android.net.Uri);
    method public final android.content.ContentProvider.CallingIdentity clearCallingIdentity();
@@ -9278,10 +9298,12 @@ package android.content {
    method public abstract void writeDataToPipe(android.os.ParcelFileDescriptor, android.net.Uri, java.lang.String, android.os.Bundle, T);
  }
  public class ContentProviderClient implements java.lang.AutoCloseable {
  public class ContentProviderClient implements java.lang.AutoCloseable android.content.ContentInterface {
    method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException;
    method public android.content.ContentProviderResult[] applyBatch(java.lang.String, java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException;
    method public int bulkInsert(android.net.Uri, android.content.ContentValues[]) throws android.os.RemoteException;
    method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException;
    method public android.os.Bundle call(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException;
    method public final android.net.Uri canonicalize(android.net.Uri) throws android.os.RemoteException;
    method public void close();
    method public static void closeQuietly(android.content.ContentProviderClient);
@@ -9294,6 +9316,7 @@ package android.content {
    method public android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException;
    method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException, android.os.RemoteException;
    method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException;
    method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException;
    method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException, android.os.RemoteException;
    method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException;
    method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) throws android.os.RemoteException;
@@ -9358,7 +9381,7 @@ package android.content {
    method public void setKeepUpdated(boolean);
  }
  public abstract class ContentResolver {
  public abstract class ContentResolver implements android.content.ContentInterface {
    ctor public ContentResolver(android.content.Context);
    method public final android.content.ContentProviderClient acquireContentProviderClient(android.net.Uri);
    method public final android.content.ContentProviderClient acquireContentProviderClient(java.lang.String);
@@ -9369,6 +9392,7 @@ package android.content {
    method public android.content.ContentProviderResult[] applyBatch(java.lang.String, java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException;
    method public final int bulkInsert(android.net.Uri, android.content.ContentValues[]);
    method public final android.os.Bundle call(android.net.Uri, java.lang.String, java.lang.String, android.os.Bundle);
    method public final android.os.Bundle call(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle);
    method public deprecated void cancelSync(android.net.Uri);
    method public static void cancelSync(android.accounts.Account, java.lang.String);
    method public static void cancelSync(android.content.SyncRequest);
@@ -9392,13 +9416,16 @@ package android.content {
    method public void notifyChange(android.net.Uri, android.database.ContentObserver);
    method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean);
    method public void notifyChange(android.net.Uri, android.database.ContentObserver, int);
    method public final android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
    method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
    method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
    method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
    method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
    method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
    method public final java.io.InputStream openInputStream(android.net.Uri) throws java.io.FileNotFoundException;
    method public final java.io.OutputStream openOutputStream(android.net.Uri) throws java.io.FileNotFoundException;
    method public final java.io.OutputStream openOutputStream(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
    method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
    method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
    method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
+1 −1
Original line number Diff line number Diff line
@@ -299,7 +299,7 @@ package android.bluetooth {

package android.content {

  public abstract class ContentResolver {
  public abstract class ContentResolver implements android.content.ContentInterface {
    method public static java.lang.String[] getSyncAdapterPackagesForAuthorityAsUser(java.lang.String, int);
  }

+1 −1
Original line number Diff line number Diff line
@@ -557,7 +557,7 @@ public class Content {

        @Override
        public void onExecute(IContentProvider provider) throws Exception {
            Bundle result = provider.call(null, mMethod, mArg, mExtras);
            Bundle result = provider.call(null, mUri.getAuthority(), mMethod, mArg, mExtras);
            if (result != null) {
                result.size(); // unpack
            }
+85 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.content;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;

import java.io.FileNotFoundException;
import java.util.ArrayList;

/**
 * Interface representing calls that can be made to {@link ContentProvider}
 * instances.
 * <p>
 * These methods have been extracted into a general interface so that APIs can
 * be flexible in accepting either a {@link ContentProvider}, a
 * {@link ContentResolver}, or a {@link ContentProviderClient}.
 */
public interface ContentInterface {
    public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
            @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal)
            throws RemoteException;

    public @Nullable String getType(@NonNull Uri uri) throws RemoteException;

    public @Nullable String[] getStreamTypes(@NonNull Uri uri, @NonNull String mimeTypeFilter)
            throws RemoteException;

    public @Nullable Uri canonicalize(@NonNull Uri uri) throws RemoteException;

    public @Nullable Uri uncanonicalize(@NonNull Uri uri) throws RemoteException;

    public boolean refresh(@NonNull Uri uri, @Nullable Bundle args,
            @Nullable CancellationSignal cancellationSignal) throws RemoteException;

    public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues initialValues)
            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 update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
            @Nullable String[] selectionArgs) throws RemoteException;

    public @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode,
            @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException;

    public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode,
            @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException;

    public @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri,
            @NonNull String mimeTypeFilter, @Nullable Bundle opts,
            @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException;

    public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority,
            @NonNull ArrayList<ContentProviderOperation> operations)
            throws RemoteException, OperationApplicationException;

    public @Nullable Bundle call(@NonNull String authority, @NonNull String method,
            @Nullable String arg, @Nullable Bundle extras) throws RemoteException;
}
+47 −16
Original line number Diff line number Diff line
@@ -105,7 +105,7 @@ import java.util.Objects;
 * developer guide.</p>
 * </div>
 */
public abstract class ContentProvider implements ComponentCallbacks2 {
public abstract class ContentProvider implements ContentInterface, ComponentCallbacks2 {

    private static final String TAG = "ContentProvider";

@@ -324,7 +324,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
        }

        @Override
        public ContentProviderResult[] applyBatch(String callingPkg,
        public ContentProviderResult[] applyBatch(String callingPkg, String authority,
                ArrayList<ContentProviderOperation> operations)
                throws OperationApplicationException {
            int numOperations = operations.size();
@@ -356,7 +356,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
            Trace.traceBegin(TRACE_TAG_DATABASE, "applyBatch");
            final String original = setCallingPackage(callingPkg);
            try {
                ContentProviderResult[] results = ContentProvider.this.applyBatch(operations);
                ContentProviderResult[] results = ContentProvider.this.applyBatch(authority,
                        operations);
                if (results != null) {
                    for (int i = 0; i < results.length ; i++) {
                        if (userIds[i] != UserHandle.USER_CURRENT) {
@@ -444,13 +445,13 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
        }

        @Override
        public Bundle call(
                String callingPkg, String method, @Nullable String arg, @Nullable Bundle extras) {
        public Bundle call(String callingPkg, String authority, String method, @Nullable String arg,
                @Nullable Bundle extras) {
            Bundle.setDefusable(extras, true);
            Trace.traceBegin(TRACE_TAG_DATABASE, "call");
            final String original = setCallingPackage(callingPkg);
            try {
                return ContentProvider.this.call(method, arg, extras);
                return ContentProvider.this.call(authority, method, arg, extras);
            } finally {
                setCallingPackage(original);
                Trace.traceEnd(TRACE_TAG_DATABASE);
@@ -1255,6 +1256,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     *            or {@code null}.
     * @return a Cursor or {@code null}.
     */
    @Override
    public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
            @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) {
        queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;
@@ -1293,6 +1295,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     * @param uri the URI to query.
     * @return a MIME type string, or {@code null} if there is no type.
     */
    @Override
    public abstract @Nullable String getType(@NonNull Uri uri);

    /**
@@ -1325,6 +1328,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     * @return Return the canonical representation of <var>url</var>, or null if
     * canonicalization of that Uri is not supported.
     */
    @Override
    public @Nullable Uri canonicalize(@NonNull Uri url) {
        return null;
    }
@@ -1343,6 +1347,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     * the data identified by the canonical representation can not be found in
     * the current environment.
     */
    @Override
    public @Nullable Uri uncanonicalize(@NonNull Uri url) {
        return url;
    }
@@ -1369,6 +1374,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     *            canceled the refresh request.
     * @return true if the provider actually tried refreshing.
     */
    @Override
    public boolean refresh(Uri uri, @Nullable Bundle args,
            @Nullable CancellationSignal cancellationSignal) {
        return false;
@@ -1403,6 +1409,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     *     This must not be {@code null}.
     * @return The URI for the newly inserted item.
     */
    @Override
    public abstract @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues values);

    /**
@@ -1420,6 +1427,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     *    This must not be {@code null}.
     * @return The number of values that were inserted.
     */
    @Override
    public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
        int numValues = values.length;
        for (int i = 0; i < numValues; i++) {
@@ -1448,6 +1456,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     * @return The number of rows affected.
     * @throws SQLException
     */
    @Override
    public abstract int delete(@NonNull Uri uri, @Nullable String selection,
            @Nullable String[] selectionArgs);

@@ -1468,6 +1477,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     * @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);

@@ -1604,6 +1614,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     * @see #getType(android.net.Uri)
     * @see ParcelFileDescriptor#parseMode(String)
     */
    @Override
    public @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode,
            @Nullable CancellationSignal signal) throws FileNotFoundException {
        return openFile(uri, mode);
@@ -1723,6 +1734,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     * @see #openFileHelper(Uri, String)
     * @see #getType(android.net.Uri)
     */
    @Override
    public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode,
            @Nullable CancellationSignal signal) throws FileNotFoundException {
        return openAssetFile(uri, mode);
@@ -1789,6 +1801,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     * @see #openTypedAssetFile(Uri, String, Bundle)
     * @see ClipDescription#compareMimeTypes(String, String)
     */
    @Override
    public @Nullable String[] getStreamTypes(@NonNull Uri uri, @NonNull String mimeTypeFilter) {
        return null;
    }
@@ -1905,6 +1918,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     * @see #openAssetFile(Uri, String)
     * @see ClipDescription#compareMimeTypes(String, String)
     */
    @Override
    public @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri,
            @NonNull String mimeTypeFilter, @Nullable Bundle opts,
            @Nullable CancellationSignal signal) throws FileNotFoundException {
@@ -2059,6 +2073,13 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     * @throws OperationApplicationException thrown if any operation fails.
     * @see ContentProviderOperation#apply
     */
    @Override
    public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority,
            @NonNull ArrayList<ContentProviderOperation> operations)
                    throws OperationApplicationException {
        return applyBatch(operations);
    }

    public @NonNull ContentProviderResult[] applyBatch(
            @NonNull ArrayList<ContentProviderOperation> operations)
                    throws OperationApplicationException {
@@ -2088,6 +2109,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     * @return provider-defined return value.  May be {@code null}, which is also
     *   the default for providers which don't implement any call methods.
     */
    @Override
    public @Nullable Bundle call(@NonNull String authority, @NonNull String method,
            @Nullable String arg, @Nullable Bundle extras) {
        return call(method, arg, extras);
    }

    public @Nullable Bundle call(@NonNull String method, @Nullable String arg,
            @Nullable Bundle extras) {
        return null;
@@ -2133,6 +2160,19 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
        writer.println("nothing to dump");
    }

    private void validateIncomingAuthority(String authority) throws SecurityException {
        if (!matchesOurAuthorities(getAuthorityWithoutUserId(authority))) {
            String message = "The authority " + authority + " does not match the one of the "
                    + "contentProvider: ";
            if (mAuthority != null) {
                message += mAuthority;
            } else {
                message += Arrays.toString(mAuthorities);
            }
            throw new SecurityException(message);
        }
    }

    /** @hide */
    @VisibleForTesting
    public Uri validateIncomingUri(Uri uri) throws SecurityException {
@@ -2144,16 +2184,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
                        + mContext.getUserId() + " with a uri belonging to user " + userId);
            }
        }
        if (!matchesOurAuthorities(getAuthorityWithoutUserId(auth))) {
            String message = "The authority of the uri " + uri + " does not match the one of the "
                    + "contentProvider: ";
            if (mAuthority != null) {
                message += mAuthority;
            } else {
                message += Arrays.toString(mAuthorities);
            }
            throw new SecurityException(message);
        }
        validateIncomingAuthority(auth);

        // Normalize the path by removing any empty path segments, which can be
        // a source of security issues.
Loading