diff options
author | 2016-06-24 15:21:08 -0700 | |
---|---|---|
committer | 2016-06-29 23:06:51 +0000 | |
commit | e7822fb76791e3951502e759e2b11204869d532a (patch) | |
tree | a6bb1c2224f9def6b86d5a5da34a3f2c64a464b1 | |
parent | aab044f09168f6f7296abcb2283a181f877b8050 (diff) |
Provider-level changes for implementing direct eject of a root in Files app.
Several changes at different levels:
1. Introduction of ejectRoot(String) for DocumentsProvider
2. Introduction of ejectRoot(ContentResolver, Uri, String) for
DocumentsContract
4. Additional permission for MOUNT_UNMOUNT for ExternalStorageProvider
5. Implementation of ejectRoot(String) for External StorageProvider
Bug: 29584653
Change-Id: I28557af63259548784cf24d5b051eb06ad5193ca
(cherry picked from commit 2ccc18357d6741dde56edc4d5a2608f15f4b9078)
4 files changed, 80 insertions, 1 deletions
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 1158776f5fd1..b0ea99c0fa87 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -593,6 +593,9 @@ public final class DocumentsContract { * @hide */ public static final int FLAG_REMOVABLE_USB = 1 << 20; + + /** {@hide} */ + public static final int FLAG_SUPPORTS_EJECT = 1 << 21; } /** @@ -643,6 +646,8 @@ public final class DocumentsContract { public static final String METHOD_IS_CHILD_DOCUMENT = "android:isChildDocument"; /** {@hide} */ public static final String METHOD_REMOVE_DOCUMENT = "android:removeDocument"; + /** {@hide} */ + public static final String METHOD_EJECT_ROOT = "android:ejectRoot"; /** {@hide} */ public static final String EXTRA_PARENT_URI = "parentUri"; @@ -1274,6 +1279,37 @@ public final class DocumentsContract { client.call(METHOD_REMOVE_DOCUMENT, null, in); } + /** {@hide} */ + public static boolean 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; + } finally { + ContentProviderClient.releaseQuietly(client); + } + } + + /** {@hide} */ + public static boolean 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); + } + /** * Open the given image for thumbnail purposes, using any embedded EXIF * thumbnail if available, and providing orientation hints from the parent diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 515f975bb578..ac8f3758827f 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -19,6 +19,7 @@ package android.provider; import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT; import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT; +import static android.provider.DocumentsContract.METHOD_EJECT_ROOT; import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT; import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT; import static android.provider.DocumentsContract.METHOD_REMOVE_DOCUMENT; @@ -458,6 +459,12 @@ public abstract class DocumentsProvider extends ContentProvider { throw new UnsupportedOperationException("Search not supported"); } + /** {@hide} */ + @SuppressWarnings("unused") + public boolean ejectRoot(String rootId) { + throw new UnsupportedOperationException("Eject not supported"); + } + /** * Return concrete MIME type of the requested document. Must match the value * of {@link Document#COLUMN_MIME_TYPE} for this document. The default @@ -851,7 +858,17 @@ public abstract class DocumentsProvider extends ContentProvider { // It's responsibility of the provider to revoke any grants, as the document may be // still attached to another parents. - + } else 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); + final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI); + final String rootId = DocumentsContract.getRootId(rootUri); + final boolean ejected = ejectRoot(rootId); + + out.putBoolean(DocumentsContract.EXTRA_RESULT, ejected); } else { throw new UnsupportedOperationException("Method not supported " + method); } diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml index 3185917c4df0..0b290cebf053 100644 --- a/packages/ExternalStorageProvider/AndroidManifest.xml +++ b/packages/ExternalStorageProvider/AndroidManifest.xml @@ -4,6 +4,7 @@ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> + <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <application android:label="@string/app_label"> <provider diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 62f33bf490a8..0643e9be5d08 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -25,6 +25,7 @@ import android.database.MatrixCursor; import android.database.MatrixCursor.RowBuilder; import android.graphics.Point; import android.net.Uri; +import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Environment; @@ -86,6 +87,7 @@ public class ExternalStorageProvider extends DocumentsProvider { private static class RootInfo { public String rootId; + public String volumeId; public int flags; public String title; public String docId; @@ -182,6 +184,7 @@ public class ExternalStorageProvider extends DocumentsProvider { mRoots.put(rootId, root); root.rootId = rootId; + root.volumeId = volume.id; root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD; @@ -193,6 +196,10 @@ public class ExternalStorageProvider extends DocumentsProvider { root.flags |= Root.FLAG_REMOVABLE_USB; } + if (!VolumeInfo.ID_EMULATED_INTERNAL.equals(volume.getId())) { + root.flags |= Root.FLAG_SUPPORTS_EJECT; + } + if (volume.isPrimary()) { // save off the primary volume for subsequent "Home" dir initialization. primaryVolume = volume; @@ -584,6 +591,24 @@ public class ExternalStorageProvider extends DocumentsProvider { } @Override + public boolean 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"); + } finally { + Binder.restoreCallingIdentity(token); + } + } + return ejected; + } + + @Override public String getDocumentType(String documentId) throws FileNotFoundException { if (mArchiveHelper.isArchivedDocument(documentId)) { return mArchiveHelper.getDocumentType(documentId); |