Unhide FLAG_SUPPORT_EJECT and related methods.

Test: Builds and CTS tests pass. Some manual tests as well.
Bug: 36483910
Change-Id: Idd9b1c9d9573222ee12127044ff11b9ab2487f0a
diff --git a/api/current.txt b/api/current.txt
index 360c4f2..627eedc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -34230,6 +34230,7 @@
     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 java.lang.String getDocumentId(android.net.Uri);
     method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
@@ -34298,6 +34299,7 @@
     field public static final java.lang.String COLUMN_TITLE = "title";
     field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
     field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
+    field public static final int FLAG_SUPPORTS_EJECT = 32; // 0x20
     field public static final int FLAG_SUPPORTS_IS_CHILD = 16; // 0x10
     field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
     field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
@@ -34311,6 +34313,7 @@
     method public android.content.IntentSender createWebLinkIntent(java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException;
+    method public void ejectRoot(java.lang.String);
     method public android.provider.DocumentsContract.Path findDocumentPath(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
     method public java.lang.String[] getDocumentStreamTypes(java.lang.String, java.lang.String);
     method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
diff --git a/api/system-current.txt b/api/system-current.txt
index 1a0a8bb..afcfa04 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -37136,6 +37136,7 @@
     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 java.lang.String getDocumentId(android.net.Uri);
     method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
@@ -37204,6 +37205,7 @@
     field public static final java.lang.String COLUMN_TITLE = "title";
     field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
     field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
+    field public static final int FLAG_SUPPORTS_EJECT = 32; // 0x20
     field public static final int FLAG_SUPPORTS_IS_CHILD = 16; // 0x10
     field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
     field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
@@ -37217,6 +37219,7 @@
     method public android.content.IntentSender createWebLinkIntent(java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException;
+    method public void ejectRoot(java.lang.String);
     method public android.provider.DocumentsContract.Path findDocumentPath(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
     method public java.lang.String[] getDocumentStreamTypes(java.lang.String, java.lang.String);
     method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
diff --git a/api/test-current.txt b/api/test-current.txt
index 674ffa6..6db9a04 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -34358,6 +34358,7 @@
     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 java.lang.String getDocumentId(android.net.Uri);
     method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
@@ -34426,6 +34427,7 @@
     field public static final java.lang.String COLUMN_TITLE = "title";
     field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
     field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
+    field public static final int FLAG_SUPPORTS_EJECT = 32; // 0x20
     field public static final int FLAG_SUPPORTS_IS_CHILD = 16; // 0x10
     field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
     field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
@@ -34439,6 +34441,7 @@
     method public android.content.IntentSender createWebLinkIntent(java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException;
+    method public void ejectRoot(java.lang.String);
     method public android.provider.DocumentsContract.Path findDocumentPath(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
     method public java.lang.String[] getDocumentStreamTypes(java.lang.String, java.lang.String);
     method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 56d4ff7..16eb358 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -594,6 +594,15 @@
         public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4;
 
         /**
+         * Flag indicating that this root can be ejected.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsContract#ejectRoot(ContentResolver, Uri)
+         * @see DocumentsProvider#ejectRoot(String)
+         */
+        public static final int FLAG_SUPPORTS_EJECT = 1 << 5;
+
+        /**
          * Flag indicating that this root is currently empty. This may be used
          * to hide the root when opening documents, but the root will still be
          * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
@@ -641,9 +650,6 @@
          * @hide
          */
         public static final int FLAG_REMOVABLE_USB = 1 << 20;
-
-        /** {@hide} */
-        public static final int FLAG_SUPPORTS_EJECT = 1 << 21;
     }
 
     /**
@@ -1345,35 +1351,30 @@
         client.call(METHOD_REMOVE_DOCUMENT, null, in);
     }
 
-    /** {@hide} */
-    public static boolean ejectRoot(ContentResolver resolver, Uri rootUri) {
+    /**
+     * 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());
         try {
-            return ejectRoot(client, rootUri);
-        } catch (Exception e) {
-            Log.w(TAG, "Failed to eject root", e);
-            return false;
+            ejectRoot(client, rootUri);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
         } finally {
             ContentProviderClient.releaseQuietly(client);
         }
     }
 
     /** {@hide} */
-    public static boolean ejectRoot(ContentProviderClient client, Uri rootUri)
+    public static void ejectRoot(ContentProviderClient client, Uri rootUri)
             throws RemoteException {
         final Bundle in = new Bundle();
         in.putParcelable(DocumentsContract.EXTRA_URI, rootUri);
 
-        final Bundle out = client.call(METHOD_EJECT_ROOT, null, in);
-
-        if (out == null) {
-            throw new RemoteException("Failed to get a reponse from ejectRoot.");
-        }
-        if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
-            throw new RemoteException("Response did not include result field..");
-        }
-        return out.getBoolean(DocumentsContract.EXTRA_RESULT);
+        client.call(METHOD_EJECT_ROOT, null, in);
     }
 
     /**
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 620d33a5..24c9a6a 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -629,9 +629,14 @@
         throw new UnsupportedOperationException("Search not supported");
     }
 
-    /** {@hide} */
+    /**
+     * Ejects the root. Throws {@link IllegalStateException} if ejection failed.
+     *
+     * @param rootId the root to be ejected.
+     * @see Root#FLAG_SUPPORTS_EJECT
+     */
     @SuppressWarnings("unused")
-    public boolean ejectRoot(String rootId) {
+    public void ejectRoot(String rootId) {
         throw new UnsupportedOperationException("Eject not supported");
     }
 
@@ -963,14 +968,12 @@
         if (METHOD_EJECT_ROOT.equals(method)) {
             // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps
             // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
-            // MANAGE_DOCUMENTS here instead
-            getContext().enforceCallingPermission(
-                    android.Manifest.permission.MANAGE_DOCUMENTS, null);
+            // MANAGE_DOCUMENTS or associated URI permission here instead
             final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
-            final String rootId = DocumentsContract.getRootId(rootUri);
-            final boolean ejected = ejectRoot(rootId);
+            enforceWritePermissionInner(rootUri, getCallingPackage(), null);
 
-            out.putBoolean(DocumentsContract.EXTRA_RESULT, ejected);
+            final String rootId = DocumentsContract.getRootId(rootUri);
+            ejectRoot(rootId);
 
             return out;
         }
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 8802010..b60e2fe 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -480,21 +480,18 @@
     }
 
     @Override
-    public boolean ejectRoot(String rootId) {
+    public void ejectRoot(String rootId) {
         final long token = Binder.clearCallingIdentity();
-        boolean ejected = false;
         RootInfo root = mRoots.get(rootId);
         if (root != null) {
             try {
                 mStorageManager.unmount(root.volumeId);
-                ejected = true;
             } catch (RuntimeException e) {
-                Log.w(TAG, "Root '" + root.title + "' could not be ejected");
+                throw new IllegalStateException(e);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
         }
-        return ejected;
     }
 
     @Override