Loading core/java/android/provider/DocumentsContract.java +36 −0 Original line number Diff line number Diff line Loading @@ -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; } /** Loading Loading @@ -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"; Loading Loading @@ -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 Loading core/java/android/provider/DocumentsProvider.java +18 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); } Loading packages/ExternalStorageProvider/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +25 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -583,6 +590,24 @@ public class ExternalStorageProvider extends DocumentsProvider { return result; } @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)) { Loading Loading
core/java/android/provider/DocumentsContract.java +36 −0 Original line number Diff line number Diff line Loading @@ -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; } /** Loading Loading @@ -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"; Loading Loading @@ -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 Loading
core/java/android/provider/DocumentsProvider.java +18 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); } Loading
packages/ExternalStorageProvider/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +25 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -583,6 +590,24 @@ public class ExternalStorageProvider extends DocumentsProvider { return result; } @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)) { Loading