DO NOT MERGE ANYWHERE: Add findPath API to SAF.

Implement it in ExternalStorageProvider.

Bug: 30948740
Change-Id: I03241cdfa561ef2fc0a0b829c9a59ad845e8f844
(cherry picked from commit 51efc73f3f341393cf93f71604be791205021b69)
This commit is contained in:
Garfield Tan
2016-09-23 13:27:59 -07:00
parent 12e319b735
commit 2f6d0d6db3
3 changed files with 147 additions and 3 deletions

View File

@@ -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;
@@ -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";
@@ -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
@@ -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];
}
};
}
}

View File

@@ -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;
@@ -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;
@@ -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;
@@ -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
@@ -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);
}