Loading core/java/android/provider/DocumentsContract.java +86 −0 Original line number Diff line number Diff line Loading @@ -36,8 +36,10 @@ import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; import android.os.OperationCanceledException; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.OnCloseListener; import android.os.Parcelable; import android.os.RemoteException; import android.os.storage.StorageVolume; import android.system.ErrnoException; Loading Loading @@ -644,6 +646,8 @@ public final class DocumentsContract { public static final String METHOD_REMOVE_DOCUMENT = "android:removeDocument"; /** {@hide} */ public static final String METHOD_EJECT_ROOT = "android:ejectRoot"; /** {@hide} */ public static final String METHOD_FIND_PATH = "android:findPath"; /** {@hide} */ public static final String EXTRA_PARENT_URI = "parentUri"; Loading Loading @@ -1306,6 +1310,41 @@ public final class DocumentsContract { return out.getBoolean(DocumentsContract.EXTRA_RESULT); } /** * Finds the canonical path to the root. Document id should be unique across * roots. * * @param documentUri uri of the document which path is requested. * @return the path to the root of the document, or {@code null} if failed. * @see DocumentsProvider#findPath(String) * * {@hide} */ public static Path findPath(ContentResolver resolver, Uri documentUri) throws RemoteException { final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( documentUri.getAuthority()); try { return findPath(client, documentUri); } catch (Exception e) { Log.w(TAG, "Failed to find path", e); return null; } finally { ContentProviderClient.releaseQuietly(client); } } /** {@hide} */ public static Path findPath(ContentProviderClient client, Uri documentUri) throws RemoteException { final Bundle in = new Bundle(); in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); final Bundle out = client.call(METHOD_FIND_PATH, 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 Loading Loading @@ -1345,4 +1384,51 @@ public final class DocumentsContract { return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras); } /** * Holds a path from a root to a particular document under it. * * @hide */ public static final class Path implements Parcelable { public final String mRootId; public final List<String> mPath; /** * Creates a Path. * @param rootId the id of the root * @param path the list of document ids from the root document * at position 0 to the target document */ public Path(String rootId, List<String> path) { mRootId = rootId; mPath = path; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mRootId); dest.writeStringList(mPath); } @Override public int describeContents() { return 0; } public static final Creator<Path> CREATOR = new Creator<Path>() { @Override public Path createFromParcel(Parcel in) { final String rootId = in.readString(); final List<String> path = in.createStringArrayList(); return new Path(rootId, path); } @Override public Path[] newArray(int size) { return new Path[size]; } }; } } core/java/android/provider/DocumentsProvider.java +29 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ 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_FIND_PATH; 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 @@ -33,6 +34,7 @@ import static android.provider.DocumentsContract.getSearchDocumentsQuery; import static android.provider.DocumentsContract.getTreeDocumentId; import static android.provider.DocumentsContract.isTreeUri; import android.Manifest; import android.annotation.CallSuper; import android.content.ClipDescription; import android.content.ContentProvider; Loading @@ -53,6 +55,7 @@ import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.OnCloseListener; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract.Path; import android.util.Log; import libcore.io.IoUtils; Loading Loading @@ -322,6 +325,26 @@ public abstract class DocumentsProvider extends ContentProvider { throw new UnsupportedOperationException("Remove not supported"); } /** * Finds the canonical path to the root for the requested document. If there are * more than one path to this document, return the most typical one. * * <p>This API assumes that document id has enough info to infer the root. * Different roots should use different document id to refer to the same * document. * * @param documentId the document which path is requested. * @return the path of the requested document to the root, or null if * such operation is not supported. * * @hide */ public Path findPath(String documentId) throws FileNotFoundException { Log.w(TAG, "findPath is called on an unsupported provider."); return null; } /** * Return all roots currently provided. To display to users, you must define * at least one root. You should avoid making network requests to keep this Loading Loading @@ -873,6 +896,12 @@ 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_FIND_PATH.equals(method)) { getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null); final Path path = findPath(documentId); out.putParcelable(DocumentsContract.EXTRA_RESULT, path); } else { throw new UnsupportedOperationException("Method not supported " + method); } Loading packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +32 −3 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import android.os.storage.VolumeInfo; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract.Path; import android.provider.DocumentsProvider; import android.provider.MediaStore; import android.provider.Settings; Loading @@ -48,6 +49,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.DebugUtils; import android.util.Log; import android.util.Pair; import android.webkit.MimeTypeMap; import com.android.internal.annotations.GuardedBy; Loading Loading @@ -183,7 +185,8 @@ public class ExternalStorageProvider extends DocumentsProvider { root.rootId = rootId; root.volumeId = volume.id; root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD; | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD; final DiskInfo disk = volume.getDisk(); if (DEBUG) Log.d(TAG, "Disk for root " + rootId + " is " + disk); Loading Loading @@ -270,7 +273,6 @@ public class ExternalStorageProvider extends DocumentsProvider { return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; } private String getDocIdForFile(File file) throws FileNotFoundException { return getDocIdForFileMaybeCreate(file, false); } Loading Loading @@ -323,6 +325,11 @@ public class ExternalStorageProvider extends DocumentsProvider { } private File getFileForDocId(String docId, boolean visible) throws FileNotFoundException { return resolveDocId(docId, visible).second; } private Pair<RootInfo, File> resolveDocId(String docId, boolean visible) throws FileNotFoundException { final int splitIndex = docId.indexOf(':', 1); final String tag = docId.substring(0, splitIndex); final String path = docId.substring(splitIndex + 1); Loading @@ -346,7 +353,7 @@ public class ExternalStorageProvider extends DocumentsProvider { if (!target.exists()) { throw new FileNotFoundException("Missing file for " + docId + " at " + target); } return target; return Pair.create(root, target); } private void includeFile(MatrixCursor result, String docId, File file) Loading Loading @@ -422,6 +429,28 @@ public class ExternalStorageProvider extends DocumentsProvider { } } @Override public Path findPath(String documentId) throws FileNotFoundException { LinkedList<String> path = new LinkedList<>(); final Pair<RootInfo, File> resolvedDocId = resolveDocId(documentId, false); RootInfo root = resolvedDocId.first; File file = resolvedDocId.second; if (!file.exists()) { throw new FileNotFoundException(); } while (file != null && file.getAbsolutePath().startsWith(root.path.getAbsolutePath())) { path.addFirst(getDocIdForFile(file)); file = file.getParentFile(); } return new Path(root.rootId, path); } @Override public String createDocument(String docId, String mimeType, String displayName) throws FileNotFoundException { Loading Loading
core/java/android/provider/DocumentsContract.java +86 −0 Original line number Diff line number Diff line Loading @@ -36,8 +36,10 @@ import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; import android.os.OperationCanceledException; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.OnCloseListener; import android.os.Parcelable; import android.os.RemoteException; import android.os.storage.StorageVolume; import android.system.ErrnoException; Loading Loading @@ -644,6 +646,8 @@ public final class DocumentsContract { public static final String METHOD_REMOVE_DOCUMENT = "android:removeDocument"; /** {@hide} */ public static final String METHOD_EJECT_ROOT = "android:ejectRoot"; /** {@hide} */ public static final String METHOD_FIND_PATH = "android:findPath"; /** {@hide} */ public static final String EXTRA_PARENT_URI = "parentUri"; Loading Loading @@ -1306,6 +1310,41 @@ public final class DocumentsContract { return out.getBoolean(DocumentsContract.EXTRA_RESULT); } /** * Finds the canonical path to the root. Document id should be unique across * roots. * * @param documentUri uri of the document which path is requested. * @return the path to the root of the document, or {@code null} if failed. * @see DocumentsProvider#findPath(String) * * {@hide} */ public static Path findPath(ContentResolver resolver, Uri documentUri) throws RemoteException { final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( documentUri.getAuthority()); try { return findPath(client, documentUri); } catch (Exception e) { Log.w(TAG, "Failed to find path", e); return null; } finally { ContentProviderClient.releaseQuietly(client); } } /** {@hide} */ public static Path findPath(ContentProviderClient client, Uri documentUri) throws RemoteException { final Bundle in = new Bundle(); in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); final Bundle out = client.call(METHOD_FIND_PATH, 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 Loading Loading @@ -1345,4 +1384,51 @@ public final class DocumentsContract { return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras); } /** * Holds a path from a root to a particular document under it. * * @hide */ public static final class Path implements Parcelable { public final String mRootId; public final List<String> mPath; /** * Creates a Path. * @param rootId the id of the root * @param path the list of document ids from the root document * at position 0 to the target document */ public Path(String rootId, List<String> path) { mRootId = rootId; mPath = path; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mRootId); dest.writeStringList(mPath); } @Override public int describeContents() { return 0; } public static final Creator<Path> CREATOR = new Creator<Path>() { @Override public Path createFromParcel(Parcel in) { final String rootId = in.readString(); final List<String> path = in.createStringArrayList(); return new Path(rootId, path); } @Override public Path[] newArray(int size) { return new Path[size]; } }; } }
core/java/android/provider/DocumentsProvider.java +29 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ 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_FIND_PATH; 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 @@ -33,6 +34,7 @@ import static android.provider.DocumentsContract.getSearchDocumentsQuery; import static android.provider.DocumentsContract.getTreeDocumentId; import static android.provider.DocumentsContract.isTreeUri; import android.Manifest; import android.annotation.CallSuper; import android.content.ClipDescription; import android.content.ContentProvider; Loading @@ -53,6 +55,7 @@ import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.OnCloseListener; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract.Path; import android.util.Log; import libcore.io.IoUtils; Loading Loading @@ -322,6 +325,26 @@ public abstract class DocumentsProvider extends ContentProvider { throw new UnsupportedOperationException("Remove not supported"); } /** * Finds the canonical path to the root for the requested document. If there are * more than one path to this document, return the most typical one. * * <p>This API assumes that document id has enough info to infer the root. * Different roots should use different document id to refer to the same * document. * * @param documentId the document which path is requested. * @return the path of the requested document to the root, or null if * such operation is not supported. * * @hide */ public Path findPath(String documentId) throws FileNotFoundException { Log.w(TAG, "findPath is called on an unsupported provider."); return null; } /** * Return all roots currently provided. To display to users, you must define * at least one root. You should avoid making network requests to keep this Loading Loading @@ -873,6 +896,12 @@ 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_FIND_PATH.equals(method)) { getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null); final Path path = findPath(documentId); out.putParcelable(DocumentsContract.EXTRA_RESULT, path); } else { throw new UnsupportedOperationException("Method not supported " + method); } Loading
packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +32 −3 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import android.os.storage.VolumeInfo; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract.Path; import android.provider.DocumentsProvider; import android.provider.MediaStore; import android.provider.Settings; Loading @@ -48,6 +49,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.DebugUtils; import android.util.Log; import android.util.Pair; import android.webkit.MimeTypeMap; import com.android.internal.annotations.GuardedBy; Loading Loading @@ -183,7 +185,8 @@ public class ExternalStorageProvider extends DocumentsProvider { root.rootId = rootId; root.volumeId = volume.id; root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD; | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD; final DiskInfo disk = volume.getDisk(); if (DEBUG) Log.d(TAG, "Disk for root " + rootId + " is " + disk); Loading Loading @@ -270,7 +273,6 @@ public class ExternalStorageProvider extends DocumentsProvider { return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; } private String getDocIdForFile(File file) throws FileNotFoundException { return getDocIdForFileMaybeCreate(file, false); } Loading Loading @@ -323,6 +325,11 @@ public class ExternalStorageProvider extends DocumentsProvider { } private File getFileForDocId(String docId, boolean visible) throws FileNotFoundException { return resolveDocId(docId, visible).second; } private Pair<RootInfo, File> resolveDocId(String docId, boolean visible) throws FileNotFoundException { final int splitIndex = docId.indexOf(':', 1); final String tag = docId.substring(0, splitIndex); final String path = docId.substring(splitIndex + 1); Loading @@ -346,7 +353,7 @@ public class ExternalStorageProvider extends DocumentsProvider { if (!target.exists()) { throw new FileNotFoundException("Missing file for " + docId + " at " + target); } return target; return Pair.create(root, target); } private void includeFile(MatrixCursor result, String docId, File file) Loading Loading @@ -422,6 +429,28 @@ public class ExternalStorageProvider extends DocumentsProvider { } } @Override public Path findPath(String documentId) throws FileNotFoundException { LinkedList<String> path = new LinkedList<>(); final Pair<RootInfo, File> resolvedDocId = resolveDocId(documentId, false); RootInfo root = resolvedDocId.first; File file = resolvedDocId.second; if (!file.exists()) { throw new FileNotFoundException(); } while (file != null && file.getAbsolutePath().startsWith(root.path.getAbsolutePath())) { path.addFirst(getDocIdForFile(file)); file = file.getParentFile(); } return new Path(root.rootId, path); } @Override public String createDocument(String docId, String mimeType, String displayName) throws FileNotFoundException { Loading