Make DocumentsContract methods more general.

Accepting only ContentResolver arguments was quite limiting, so use
the newly created super-interface ContentInterface, which lets
callers use a ContentResolver, and ContentProviderClient, or even a
specific ContentProvider.

This is a safe API change, since we're accepting a more-general
argument, and existing API users can continue passing ContentResolver
to these methods.

Bug: 117635768
Test: atest DocumentsUITests
Test: atest android.appsecurity.cts.DocumentsTest
Change-Id: I8f0cd1335c9b763dd81eeb237fb0517e9073b625
diff --git a/api/current.txt b/api/current.txt
index bf7dedf..8919257 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -37397,26 +37397,26 @@
     method public static android.net.Uri buildRootsUri(java.lang.String);
     method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String);
     method public static android.net.Uri buildTreeDocumentUri(java.lang.String, java.lang.String);
-    method public static android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
-    method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
-    method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException;
-    method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
-    method public static void ejectRoot(android.content.ContentResolver, android.net.Uri);
-    method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static android.net.Uri copyDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static android.net.Uri createDocument(android.content.ContentInterface, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
+    method public static android.content.IntentSender createWebLinkIntent(android.content.ContentInterface, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException;
+    method public static boolean deleteDocument(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static void ejectRoot(android.content.ContentInterface, android.net.Uri);
+    method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
     method public static java.lang.String getDocumentId(android.net.Uri);
-    method public static android.os.Bundle getDocumentMetadata(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
-    method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
+    method public static android.os.Bundle getDocumentMetadata(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentInterface, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public static java.lang.String getRootId(android.net.Uri);
     method public static java.lang.String getSearchDocumentsQuery(android.net.Uri);
     method public static java.lang.String getTreeDocumentId(android.net.Uri);
-    method public static boolean isChildDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static boolean isChildDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
     method public static boolean isDocumentUri(android.content.Context, android.net.Uri);
     method public static boolean isRootUri(android.content.Context, android.net.Uri);
     method public static boolean isRootsUri(android.content.Context, android.net.Uri);
     method public static boolean isTreeUri(android.net.Uri);
-    method public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
-    method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
-    method public static android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
+    method public static android.net.Uri moveDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static boolean removeDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static android.net.Uri renameDocument(android.content.ContentInterface, android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
     field public static final java.lang.String ACTION_DOCUMENT_SETTINGS = "android.provider.action.DOCUMENT_SETTINGS";
     field public static final java.lang.String EXTRA_ERROR = "error";
     field public static final java.lang.String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF";
@@ -53285,7 +53285,7 @@
     method public abstract boolean getDomStorageEnabled();
     method public abstract java.lang.String getFantasyFontFamily();
     method public abstract java.lang.String getFixedFontFamily();
-    method public abstract int getForceDarkMode();
+    method public int getForceDarkMode();
     method public abstract boolean getJavaScriptCanOpenWindowsAutomatically();
     method public abstract boolean getJavaScriptEnabled();
     method public abstract android.webkit.WebSettings.LayoutAlgorithm getLayoutAlgorithm();
@@ -53332,7 +53332,7 @@
     method public abstract deprecated void setEnableSmoothTransition(boolean);
     method public abstract void setFantasyFontFamily(java.lang.String);
     method public abstract void setFixedFontFamily(java.lang.String);
-    method public abstract void setForceDarkMode(int);
+    method public void setForceDarkMode(int);
     method public abstract deprecated void setGeolocationDatabasePath(java.lang.String);
     method public abstract void setGeolocationEnabled(boolean);
     method public abstract void setJavaScriptCanOpenWindowsAutomatically(boolean);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 866050a..da65941 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -3283,10 +3283,10 @@
     }
 
     /** {@hide} */
-    public static Bitmap loadThumbnail(@NonNull ContentProviderClient client, @NonNull Uri uri,
+    public static Bitmap loadThumbnail(@NonNull ContentInterface content, @NonNull Uri uri,
             @NonNull Size size, @Nullable CancellationSignal signal, int allocator)
             throws IOException {
-        Objects.requireNonNull(client);
+        Objects.requireNonNull(content);
         Objects.requireNonNull(uri);
         Objects.requireNonNull(size);
 
@@ -3295,7 +3295,7 @@
         opts.putParcelable(EXTRA_SIZE, Point.convert(size));
 
         return ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> {
-            return client.openTypedAssetFileDescriptor(uri, "image/*", opts, signal);
+            return content.openTypedAssetFile(uri, "image/*", opts, signal);
         }), (ImageDecoder decoder, ImageInfo info, Source source) -> {
             decoder.setAllocator(allocator);
 
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 4737577..ff77228 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -24,7 +24,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
-import android.content.ContentProviderClient;
+import android.content.ContentInterface;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -48,10 +48,10 @@
 import android.os.Parcelable;
 import android.os.ParcelableException;
 import android.os.RemoteException;
-import android.os.storage.StorageVolume;
-import android.util.DataUnit;
 import android.util.Log;
 
+import dalvik.system.VMRuntime;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -68,8 +68,7 @@
  * All client apps must hold a valid URI permission grant to access documents,
  * typically issued when a user makes a selection through
  * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
- * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or
- * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}.
+ * or {@link Intent#ACTION_OPEN_DOCUMENT_TREE}.
  *
  * @see DocumentsProvider
  */
@@ -234,11 +233,6 @@
     public static final String
             ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
 
-    /**
-     * Buffer is large enough to rewind past any EXIF headers.
-     */
-    private static final int THUMBNAIL_BUFFER_SIZE = (int) DataUnit.KIBIBYTES.toBytes(128);
-
     /** {@hide} */
     public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
             "com.android.externalstorage.documents";
@@ -381,7 +375,7 @@
          * Flag indicating that a document can be represented as a thumbnail.
          *
          * @see #COLUMN_FLAGS
-         * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
+         * @see DocumentsContract#getDocumentThumbnail(ContentInterface, Uri,
          *      Point, CancellationSignal)
          * @see DocumentsProvider#openDocumentThumbnail(String, Point,
          *      android.os.CancellationSignal)
@@ -407,7 +401,7 @@
          * Flag indicating that a document is deletable.
          *
          * @see #COLUMN_FLAGS
-         * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
+         * @see DocumentsContract#deleteDocument(ContentInterface, Uri)
          * @see DocumentsProvider#deleteDocument(String)
          */
         public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
@@ -445,8 +439,7 @@
          * Flag indicating that a document can be renamed.
          *
          * @see #COLUMN_FLAGS
-         * @see DocumentsContract#renameDocument(ContentResolver, Uri,
-         *      String)
+         * @see DocumentsContract#renameDocument(ContentInterface, Uri, String)
          * @see DocumentsProvider#renameDocument(String, String)
          */
         public static final int FLAG_SUPPORTS_RENAME = 1 << 6;
@@ -456,7 +449,7 @@
          * within the same document provider.
          *
          * @see #COLUMN_FLAGS
-         * @see DocumentsContract#copyDocument(ContentResolver, Uri, Uri)
+         * @see DocumentsContract#copyDocument(ContentInterface, Uri, Uri)
          * @see DocumentsProvider#copyDocument(String, String)
          */
         public static final int FLAG_SUPPORTS_COPY = 1 << 7;
@@ -466,7 +459,7 @@
          * within the same document provider.
          *
          * @see #COLUMN_FLAGS
-         * @see DocumentsContract#moveDocument(ContentResolver, Uri, Uri, Uri)
+         * @see DocumentsContract#moveDocument(ContentInterface, Uri, Uri, Uri)
          * @see DocumentsProvider#moveDocument(String, String, String)
          */
         public static final int FLAG_SUPPORTS_MOVE = 1 << 8;
@@ -490,7 +483,7 @@
          * Flag indicating that a document can be removed from a parent.
          *
          * @see #COLUMN_FLAGS
-         * @see DocumentsContract#removeDocument(ContentResolver, Uri, Uri)
+         * @see DocumentsContract#removeDocument(ContentInterface, Uri, Uri)
          * @see DocumentsProvider#removeDocument(String, String)
          */
         public static final int FLAG_SUPPORTS_REMOVE = 1 << 10;
@@ -684,7 +677,7 @@
          * Flag indicating that this root can be ejected.
          *
          * @see #COLUMN_FLAGS
-         * @see DocumentsContract#ejectRoot(ContentResolver, Uri)
+         * @see DocumentsContract#ejectRoot(ContentInterface, Uri)
          * @see DocumentsProvider#ejectRoot(String)
          */
         public static final int FLAG_SUPPORTS_EJECT = 1 << 5;
@@ -807,10 +800,7 @@
     /** {@hide} */
     public static final String EXTRA_URI_PERMISSIONS = "uriPermissions";
 
-    /**
-     * @see #createWebLinkIntent(ContentResolver, Uri, Bundle)
-     * {@hide}
-     */
+    /** {@hide} */
     public static final String EXTRA_OPTIONS = "options";
 
     private static final String PATH_ROOT = "root";
@@ -1255,32 +1245,20 @@
      * @see DocumentsProvider#openDocumentThumbnail(String, Point,
      *      android.os.CancellationSignal)
      */
-    public static Bitmap getDocumentThumbnail(
-            ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal)
-            throws FileNotFoundException {
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                documentUri.getAuthority());
+    public static Bitmap getDocumentThumbnail(ContentInterface content, Uri documentUri, Point size,
+            CancellationSignal signal) throws FileNotFoundException {
         try {
-            return getDocumentThumbnail(client, documentUri, size, signal);
+            return ContentResolver.loadThumbnail(content, documentUri, Point.convert(size), signal,
+                    ImageDecoder.ALLOCATOR_SOFTWARE);
         } catch (Exception e) {
             if (!(e instanceof OperationCanceledException)) {
                 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
             }
-            rethrowIfNecessary(resolver, e);
+            rethrowIfNecessary(e);
             return null;
-        } finally {
-            ContentProviderClient.releaseQuietly(client);
         }
     }
 
-    /** {@hide} */
-    @UnsupportedAppUsage
-    public static Bitmap getDocumentThumbnail(ContentProviderClient client, Uri documentUri,
-            Point size, CancellationSignal signal) throws IOException {
-        return ContentResolver.loadThumbnail(client, documentUri, Point.convert(size), signal,
-                ImageDecoder.ALLOCATOR_SOFTWARE);
-    }
-
     /**
      * Create a new document with given MIME type and display name.
      *
@@ -1289,33 +1267,24 @@
      * @param displayName name of new document
      * @return newly created document, or {@code null} if failed
      */
-    public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
+    public static Uri createDocument(ContentInterface content, Uri parentDocumentUri,
             String mimeType, String displayName) throws FileNotFoundException {
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                parentDocumentUri.getAuthority());
         try {
-            return createDocument(client, parentDocumentUri, mimeType, displayName);
+            final Bundle in = new Bundle();
+            in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
+            in.putString(Document.COLUMN_MIME_TYPE, mimeType);
+            in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
+
+            final Bundle out = content.call(parentDocumentUri.getAuthority(),
+                    METHOD_CREATE_DOCUMENT, null, in);
+            return out.getParcelable(DocumentsContract.EXTRA_URI);
         } catch (Exception e) {
             Log.w(TAG, "Failed to create document", e);
-            rethrowIfNecessary(resolver, e);
+            rethrowIfNecessary(e);
             return null;
-        } finally {
-            ContentProviderClient.releaseQuietly(client);
         }
     }
 
-    /** {@hide} */
-    public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
-            String mimeType, String displayName) throws RemoteException {
-        final Bundle in = new Bundle();
-        in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
-        in.putString(Document.COLUMN_MIME_TYPE, mimeType);
-        in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
-
-        final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
-        return out.getParcelable(DocumentsContract.EXTRA_URI);
-    }
-
 
     /**
      * Test if a document is descendant (child, grandchild, etc) from the given
@@ -1326,39 +1295,29 @@
      * @return if given document is a descendant of the given parent.
      * @see Root#FLAG_SUPPORTS_IS_CHILD
      */
-    public static boolean isChildDocument(ContentResolver resolver, Uri parentDocumentUri,
+    public static boolean isChildDocument(ContentInterface content, Uri parentDocumentUri,
             Uri childDocumentUri) throws FileNotFoundException {
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                parentDocumentUri.getAuthority());
         try {
-            return isChildDocument(client, parentDocumentUri, childDocumentUri);
+            final Bundle in = new Bundle();
+            in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
+            in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri);
+
+            final Bundle out = content.call(parentDocumentUri.getAuthority(),
+                    METHOD_IS_CHILD_DOCUMENT, null, in);
+            if (out == null) {
+                throw new RemoteException("Failed to get a reponse from isChildDocument query.");
+            }
+            if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
+                throw new RemoteException("Response did not include result field..");
+            }
+            return out.getBoolean(DocumentsContract.EXTRA_RESULT);
         } catch (Exception e) {
-            Log.w(TAG, "Failed to query isChildDocument", e);
-            rethrowIfNecessary(resolver, e);
+            Log.w(TAG, "Failed to create document", e);
+            rethrowIfNecessary(e);
             return false;
-        } finally {
-            ContentProviderClient.releaseQuietly(client);
         }
     }
 
-    /** {@hide} */
-    public static boolean isChildDocument(ContentProviderClient client, Uri parentDocumentUri,
-            Uri childDocumentUri) throws RemoteException {
-
-        final Bundle in = new Bundle();
-        in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
-        in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri);
-
-        final Bundle out = client.call(METHOD_IS_CHILD_DOCUMENT, null, in);
-        if (out == null) {
-            throw new RemoteException("Failed to get a response from isChildDocument query.");
-        }
-        if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
-            throw new RemoteException("Response did not include result field..");
-        }
-        return out.getBoolean(DocumentsContract.EXTRA_RESULT);
-    }
-
     /**
      * Change the display name of an existing document.
      * <p>
@@ -1372,64 +1331,46 @@
      * @return the existing or new document after the rename, or {@code null} if
      *         failed.
      */
-    public static Uri renameDocument(ContentResolver resolver, Uri documentUri,
+    public static Uri renameDocument(ContentInterface content, Uri documentUri,
             String displayName) throws FileNotFoundException {
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                documentUri.getAuthority());
         try {
-            return renameDocument(client, documentUri, displayName);
+            final Bundle in = new Bundle();
+            in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
+            in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
+
+            final Bundle out = content.call(documentUri.getAuthority(),
+                    METHOD_RENAME_DOCUMENT, null, in);
+            final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI);
+            return (outUri != null) ? outUri : documentUri;
         } catch (Exception e) {
             Log.w(TAG, "Failed to rename document", e);
-            rethrowIfNecessary(resolver, e);
+            rethrowIfNecessary(e);
             return null;
-        } finally {
-            ContentProviderClient.releaseQuietly(client);
         }
     }
 
-    /** {@hide} */
-    public static Uri renameDocument(ContentProviderClient client, Uri documentUri,
-            String displayName) throws RemoteException {
-        final Bundle in = new Bundle();
-        in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
-        in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
-
-        final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
-        final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI);
-        return (outUri != null) ? outUri : documentUri;
-    }
-
     /**
      * Delete the given document.
      *
      * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
      * @return if the document was deleted successfully.
      */
-    public static boolean deleteDocument(ContentResolver resolver, Uri documentUri)
+    public static boolean deleteDocument(ContentInterface content, Uri documentUri)
             throws FileNotFoundException {
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                documentUri.getAuthority());
         try {
-            deleteDocument(client, documentUri);
+            final Bundle in = new Bundle();
+            in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
+
+            content.call(documentUri.getAuthority(),
+                    METHOD_DELETE_DOCUMENT, null, in);
             return true;
         } catch (Exception e) {
             Log.w(TAG, "Failed to delete document", e);
-            rethrowIfNecessary(resolver, e);
+            rethrowIfNecessary(e);
             return false;
-        } finally {
-            ContentProviderClient.releaseQuietly(client);
         }
     }
 
-    /** {@hide} */
-    public static void deleteDocument(ContentProviderClient client, Uri documentUri)
-            throws RemoteException {
-        final Bundle in = new Bundle();
-        in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
-
-        client.call(METHOD_DELETE_DOCUMENT, null, in);
-    }
-
     /**
      * Copies the given document.
      *
@@ -1438,32 +1379,23 @@
      *         document's copy.
      * @return the copied document, or {@code null} if failed.
      */
-    public static Uri copyDocument(ContentResolver resolver, Uri sourceDocumentUri,
+    public static Uri copyDocument(ContentInterface content, Uri sourceDocumentUri,
             Uri targetParentDocumentUri) throws FileNotFoundException {
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                sourceDocumentUri.getAuthority());
         try {
-            return copyDocument(client, sourceDocumentUri, targetParentDocumentUri);
+            final Bundle in = new Bundle();
+            in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
+            in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
+
+            final Bundle out = content.call(sourceDocumentUri.getAuthority(),
+                    METHOD_COPY_DOCUMENT, null, in);
+            return out.getParcelable(DocumentsContract.EXTRA_URI);
         } catch (Exception e) {
             Log.w(TAG, "Failed to copy document", e);
-            rethrowIfNecessary(resolver, e);
+            rethrowIfNecessary(e);
             return null;
-        } finally {
-            ContentProviderClient.releaseQuietly(client);
         }
     }
 
-    /** {@hide} */
-    public static Uri copyDocument(ContentProviderClient client, Uri sourceDocumentUri,
-            Uri targetParentDocumentUri) throws RemoteException {
-        final Bundle in = new Bundle();
-        in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
-        in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
-
-        final Bundle out = client.call(METHOD_COPY_DOCUMENT, null, in);
-        return out.getParcelable(DocumentsContract.EXTRA_URI);
-    }
-
     /**
      * Moves the given document under a new parent.
      *
@@ -1473,35 +1405,24 @@
      *         document.
      * @return the moved document, or {@code null} if failed.
      */
-    public static Uri moveDocument(ContentResolver resolver, Uri sourceDocumentUri,
+    public static Uri moveDocument(ContentInterface content, Uri sourceDocumentUri,
             Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws FileNotFoundException {
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                sourceDocumentUri.getAuthority());
         try {
-            return moveDocument(client, sourceDocumentUri, sourceParentDocumentUri,
-                    targetParentDocumentUri);
+            final Bundle in = new Bundle();
+            in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
+            in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, sourceParentDocumentUri);
+            in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
+
+            final Bundle out = content.call(sourceDocumentUri.getAuthority(),
+                    METHOD_MOVE_DOCUMENT, null, in);
+            return out.getParcelable(DocumentsContract.EXTRA_URI);
         } catch (Exception e) {
             Log.w(TAG, "Failed to move document", e);
-            rethrowIfNecessary(resolver, e);
+            rethrowIfNecessary(e);
             return null;
-        } finally {
-            ContentProviderClient.releaseQuietly(client);
         }
     }
 
-    /** {@hide} */
-    @UnsupportedAppUsage
-    public static Uri moveDocument(ContentProviderClient client, Uri sourceDocumentUri,
-            Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws RemoteException {
-        final Bundle in = new Bundle();
-        in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
-        in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, sourceParentDocumentUri);
-        in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
-
-        final Bundle out = client.call(METHOD_MOVE_DOCUMENT, null, in);
-        return out.getParcelable(DocumentsContract.EXTRA_URI);
-    }
-
     /**
      * Removes the given document from a parent directory.
      *
@@ -1512,58 +1433,40 @@
      * @param parentDocumentUri parent document of the document to remove.
      * @return true if the document was removed successfully.
      */
-    public static boolean removeDocument(ContentResolver resolver, Uri documentUri,
+    public static boolean removeDocument(ContentInterface content, Uri documentUri,
             Uri parentDocumentUri) throws FileNotFoundException {
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                documentUri.getAuthority());
         try {
-            removeDocument(client, documentUri, parentDocumentUri);
+            final Bundle in = new Bundle();
+            in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
+            in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, parentDocumentUri);
+
+            content.call(documentUri.getAuthority(),
+                    METHOD_REMOVE_DOCUMENT, null, in);
             return true;
         } catch (Exception e) {
             Log.w(TAG, "Failed to remove document", e);
-            rethrowIfNecessary(resolver, e);
+            rethrowIfNecessary(e);
             return false;
-        } finally {
-            ContentProviderClient.releaseQuietly(client);
         }
     }
 
-    /** {@hide} */
-    public static void removeDocument(ContentProviderClient client, Uri documentUri,
-            Uri parentDocumentUri) throws RemoteException {
-        final Bundle in = new Bundle();
-        in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
-        in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, parentDocumentUri);
-
-        client.call(METHOD_REMOVE_DOCUMENT, null, in);
-    }
-
     /**
      * Ejects the given root. It throws {@link IllegalStateException} when ejection failed.
      *
      * @param rootUri root with {@link Root#FLAG_SUPPORTS_EJECT} to be ejected
      */
-    public static void ejectRoot(ContentResolver resolver, Uri rootUri) {
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                rootUri.getAuthority());
+    public static void ejectRoot(ContentInterface content, Uri rootUri) {
         try {
-            ejectRoot(client, rootUri);
+            final Bundle in = new Bundle();
+            in.putParcelable(DocumentsContract.EXTRA_URI, rootUri);
+
+            content.call(rootUri.getAuthority(),
+                    METHOD_EJECT_ROOT, null, in);
         } catch (RemoteException e) {
             e.rethrowAsRuntimeException();
-        } finally {
-            ContentProviderClient.releaseQuietly(client);
         }
     }
 
-    /** {@hide} */
-    public static void ejectRoot(ContentProviderClient client, Uri rootUri)
-            throws RemoteException {
-        final Bundle in = new Bundle();
-        in.putParcelable(DocumentsContract.EXTRA_URI, rootUri);
-
-        client.call(METHOD_EJECT_ROOT, null, in);
-    }
-
     /**
      * Returns metadata associated with the document. The type of metadata returned
      * is specific to the document type. For example the data returned for an image
@@ -1594,67 +1497,22 @@
      * @param documentUri a Document URI
      * @return a Bundle of Bundles.
      */
-    public static Bundle getDocumentMetadata(ContentResolver resolver, Uri documentUri)
+    public static Bundle getDocumentMetadata(ContentInterface content, Uri documentUri)
             throws FileNotFoundException {
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                documentUri.getAuthority());
-
         try {
-            return getDocumentMetadata(client, documentUri);
+            final Bundle in = new Bundle();
+            in.putParcelable(EXTRA_URI, documentUri);
+
+            return content.call(documentUri.getAuthority(),
+                    METHOD_GET_DOCUMENT_METADATA, null, in);
         } catch (Exception e) {
             Log.w(TAG, "Failed to get document metadata");
-            rethrowIfNecessary(resolver, e);
+            rethrowIfNecessary(e);
             return null;
-        } finally {
-            ContentProviderClient.releaseQuietly(client);
         }
     }
 
     /**
-     * Returns metadata associated with the document. The type of metadata returned
-     * is specific to the document type. For example the data returned for an image
-     * file will likely consist primarily or soley of EXIF metadata.
-     *
-     * <p>The returned {@link Bundle} will contain zero or more entries depending
-     * on the type of data supported by the document provider.
-     *
-     * <ol>
-     * <li>A {@link DocumentsContract.METADATA_TYPES} containing a {@code String[]} value.
-     *     The string array identifies the type or types of metadata returned. Each
-     *     value in the can be used to access a {@link Bundle} of data
-     *     containing that type of data.
-     * <li>An entry each for each type of returned metadata. Each set of metadata is
-     *     itself represented as a bundle and accessible via a string key naming
-     *     the type of data.
-     * </ol>
-     *
-     * <p>Example:
-     * <p><pre><code>
-     *     Bundle metadata = DocumentsContract.getDocumentMetadata(client, imageDocUri, tags);
-     *     if (metadata.containsKey(DocumentsContract.METADATA_EXIF)) {
-     *         Bundle exif = metadata.getBundle(DocumentsContract.METADATA_EXIF);
-     *         int imageLength = exif.getInt(ExifInterface.TAG_IMAGE_LENGTH);
-     *     }
-     * </code></pre>
-     *
-     * @param documentUri a Document URI
-     * @return a Bundle of Bundles.
-     * {@hide}
-     */
-    public static Bundle getDocumentMetadata(
-            ContentProviderClient client, Uri documentUri) throws RemoteException {
-        final Bundle in = new Bundle();
-        in.putParcelable(EXTRA_URI, documentUri);
-
-        final Bundle out = client.call(METHOD_GET_DOCUMENT_METADATA, null, in);
-
-        if (out == null) {
-            throw new RemoteException("Failed to get a response from getDocumentMetadata");
-        }
-        return out;
-    }
-
-    /**
      * Finds the canonical path from the top of the document tree.
      *
      * The {@link Path#getPath()} of the return value contains the document ID
@@ -1667,52 +1525,25 @@
      * @return the path of the document, or {@code null} if failed.
      * @see DocumentsProvider#findDocumentPath(String, String)
      */
-    public static Path findDocumentPath(ContentResolver resolver, Uri treeUri)
+    public static Path findDocumentPath(ContentInterface content, Uri treeUri)
             throws FileNotFoundException {
         checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri.");
 
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                treeUri.getAuthority());
         try {
-            return findDocumentPath(client, treeUri);
+            final Bundle in = new Bundle();
+            in.putParcelable(DocumentsContract.EXTRA_URI, treeUri);
+
+            final Bundle out = content.call(treeUri.getAuthority(),
+                    METHOD_FIND_DOCUMENT_PATH, null, in);
+            return out.getParcelable(DocumentsContract.EXTRA_RESULT);
         } catch (Exception e) {
             Log.w(TAG, "Failed to find path", e);
-            rethrowIfNecessary(resolver, e);
+            rethrowIfNecessary(e);
             return null;
-        } finally {
-            ContentProviderClient.releaseQuietly(client);
         }
     }
 
     /**
-     * Finds the canonical path. If uri is a document uri returns path from a root and
-     * its associated root id. If uri is a tree uri returns the path from the top of
-     * the tree. The {@link Path#getPath()} of the return value contains document ID
-     * starts from the top of the tree or the root document to the requested document,
-     * both inclusive.
-     *
-     * Callers can expect the root ID returned from multiple calls to this method is
-     * consistent.
-     *
-     * @param uri uri of the document which path is requested. It can be either a
-     *          plain document uri or a tree uri.
-     * @return the path of the document.
-     * @see DocumentsProvider#findDocumentPath(String, String)
-     *
-     * {@hide}
-     */
-    public static Path findDocumentPath(ContentProviderClient client, Uri uri)
-            throws RemoteException {
-
-        final Bundle in = new Bundle();
-        in.putParcelable(DocumentsContract.EXTRA_URI, uri);
-
-        final Bundle out = client.call(METHOD_FIND_DOCUMENT_PATH, null, in);
-
-        return out.getParcelable(DocumentsContract.EXTRA_RESULT);
-    }
-
-    /**
      * Creates an intent for obtaining a web link for the specified document.
      *
      * <p>Note, that due to internal limitations, if there is already a web link
@@ -1763,40 +1594,29 @@
      * @see DocumentsProvider#createWebLinkIntent(String, Bundle)
      * @see Intent#EXTRA_EMAIL
      */
-    public static IntentSender createWebLinkIntent(ContentResolver resolver, Uri uri,
+    public static IntentSender createWebLinkIntent(ContentInterface content, Uri uri,
             Bundle options) throws FileNotFoundException {
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                uri.getAuthority());
         try {
-            return createWebLinkIntent(client, uri, options);
+            final Bundle in = new Bundle();
+            in.putParcelable(DocumentsContract.EXTRA_URI, uri);
+
+            // Options may be provider specific, so put them in a separate bundle to
+            // avoid overriding the Uri.
+            if (options != null) {
+                in.putBundle(EXTRA_OPTIONS, options);
+            }
+
+            final Bundle out = content.call(uri.getAuthority(),
+                    METHOD_CREATE_WEB_LINK_INTENT, null, in);
+            return out.getParcelable(DocumentsContract.EXTRA_RESULT);
         } catch (Exception e) {
             Log.w(TAG, "Failed to create a web link intent", e);
-            rethrowIfNecessary(resolver, e);
+            rethrowIfNecessary(e);
             return null;
-        } finally {
-            ContentProviderClient.releaseQuietly(client);
         }
     }
 
     /**
-     * {@hide}
-     */
-    public static IntentSender createWebLinkIntent(ContentProviderClient client, Uri uri,
-            Bundle options) throws RemoteException {
-        final Bundle in = new Bundle();
-        in.putParcelable(DocumentsContract.EXTRA_URI, uri);
-
-        // Options may be provider specific, so put them in a separate bundle to
-        // avoid overriding the Uri.
-        if (options != null) {
-            in.putBundle(EXTRA_OPTIONS, options);
-        }
-
-        final Bundle out = client.call(METHOD_CREATE_WEB_LINK_INTENT, null, in);
-        return out.getParcelable(DocumentsContract.EXTRA_RESULT);
-    }
-
-    /**
      * Open the given image for thumbnail purposes, using any embedded EXIF
      * thumbnail if available, and providing orientation hints from the parent
      * image.
@@ -1836,10 +1656,9 @@
         return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
     }
 
-    private static void rethrowIfNecessary(ContentResolver resolver, Exception e)
-            throws FileNotFoundException {
+    private static void rethrowIfNecessary(Exception e) throws FileNotFoundException {
         // We only want to throw applications targetting O and above
-        if (resolver.getTargetSdkVersion() >= Build.VERSION_CODES.O) {
+        if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.O) {
             if (e instanceof ParcelableException) {
                 ((ParcelableException) e).maybeRethrow(FileNotFoundException.class);
             } else if (e instanceof RemoteException) {