Merge "Iteration on findPath API."

This commit is contained in:
TreeHugger Robot
2016-10-18 16:57:01 +00:00
committed by Android (Google) Code Review
5 changed files with 428 additions and 57 deletions

View File

@@ -19,6 +19,10 @@ package android.provider;
import static android.net.TrafficStats.KB_IN_BYTES;
import static android.system.OsConstants.SEEK_SET;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
import android.annotation.Nullable;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
@@ -55,6 +59,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
/**
* Defines the contract between a documents provider and the platform.
@@ -1311,21 +1316,26 @@ public final class DocumentsContract {
}
/**
* Finds the canonical path to the root. Document id should be unique across
* roots.
* Finds the canonical path to the top of the tree. The return value starts
* from the top of the tree or the root document to the requested document,
* both inclusive.
*
* @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)
* Document id should be unique across roots.
*
* @param treeUri treeUri of the document which path is requested.
* @return a list of documents ID starting from the top of the tree to the
* requested document, or {@code null} if failed.
* @see DocumentsProvider#findPath(String, String)
*
* {@hide}
*/
public static Path findPath(ContentResolver resolver, Uri documentUri)
throws RemoteException {
public static List<String> findPath(ContentResolver resolver, Uri treeUri) {
checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri.");
final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
documentUri.getAuthority());
treeUri.getAuthority());
try {
return findPath(client, documentUri);
return findPath(client, treeUri).getPath();
} catch (Exception e) {
Log.w(TAG, "Failed to find path", e);
return null;
@@ -1334,11 +1344,24 @@ public final class DocumentsContract {
}
}
/** {@hide} */
public static Path findPath(ContentProviderClient client, Uri documentUri)
throws RemoteException {
/**
* Finds the canonical path. If uri is a document uri returns path to a root and
* its associated root id. If uri is a tree uri returns the path to the top of
* the tree. The {@link Path#getPath()} in the return value starts from the top of
* the tree or the root document to the requested document, both inclusive.
*
* Document id should be unique across roots.
*
* @param uri uri of the document which path is requested. It can be either a
* plain document uri or a tree uri.
* @return the path of the document.
* @see DocumentsProvider#findPath(String, String)
*
* {@hide}
*/
public static Path findPath(ContentProviderClient client, Uri uri) throws RemoteException {
final Bundle in = new Bundle();
in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
in.putParcelable(DocumentsContract.EXTRA_URI, uri);
final Bundle out = client.call(METHOD_FIND_PATH, null, in);
@@ -1392,20 +1415,71 @@ public final class DocumentsContract {
*/
public static final class Path implements Parcelable {
public final String mRootId;
public final List<String> mPath;
private final @Nullable String mRootId;
private 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
*
* @param rootId the ID of the root. May be null.
* @param path the list of document ids from the parent document at
* position 0 to the child document.
*/
public Path(String rootId, List<String> path) {
checkCollectionNotEmpty(path, "path");
checkCollectionElementsNotNull(path, "path");
mRootId = rootId;
mPath = path;
}
/**
* Returns the root id or null if the calling package doesn't have
* permission to access root information.
*/
public @Nullable String getRootId() {
return mRootId;
}
/**
* Returns the path. The path is trimmed to the top of tree if
* calling package doesn't have permission to access those
* documents.
*/
public List<String> getPath() {
return mPath;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || !(o instanceof Path)) {
return false;
}
Path path = (Path) o;
return Objects.equals(mRootId, path.mRootId) &&
Objects.equals(mPath, path.mPath);
}
@Override
public int hashCode() {
return Objects.hash(mRootId, mPath);
}
@Override
public String toString() {
return new StringBuilder()
.append("DocumentsContract.Path{")
.append("rootId=")
.append(mRootId)
.append(", path=")
.append(mPath)
.append("}")
.toString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mRootId);

View File

@@ -36,6 +36,7 @@ import static android.provider.DocumentsContract.isTreeUri;
import android.Manifest;
import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.content.ClipDescription;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -54,8 +55,8 @@ import android.os.CancellationSignal;
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.provider.DocumentsContract.Root;
import android.util.Log;
import libcore.io.IoUtils;
@@ -154,17 +155,7 @@ public abstract class DocumentsProvider extends ContentProvider {
*/
@Override
public void attachInfo(Context context, ProviderInfo info) {
mAuthority = info.authority;
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(mAuthority, "root", MATCH_ROOTS);
mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT);
mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE);
mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE);
registerAuthority(info.authority);
// Sanity check our setup
if (!info.exported) {
@@ -181,6 +172,28 @@ public abstract class DocumentsProvider extends ContentProvider {
super.attachInfo(context, info);
}
/** {@hide} */
@Override
public void attachInfoForTesting(Context context, ProviderInfo info) {
registerAuthority(info.authority);
super.attachInfoForTesting(context, info);
}
private void registerAuthority(String authority) {
mAuthority = authority;
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(mAuthority, "root", MATCH_ROOTS);
mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT);
mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE);
mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE);
}
/**
* Test if a document is descendant (child, grandchild, etc) from the given
* parent. For example, providers must implement this to support
@@ -326,23 +339,28 @@ public abstract class DocumentsProvider extends ContentProvider {
}
/**
* 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.
* Finds the canonical path for the requested document. The path must start
* from the parent document if parentDocumentId is not null or the root document
* if parentDocumentId is null. If there are more than one path to this document,
* return the most typical one. Include both the parent document or root document
* and the requested document in the returned path.
*
* <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
* <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.
* @param childDocumentId the document which path is requested.
* @param parentDocumentId the document with which path starts if not null, or
* null to indicate path to root is requested.
* @return the path of the requested document. If parentDocumentId is null
* returned root ID must not be null. If parentDocumentId is not null
* returned root ID must be null.
*
* @hide
*/
public Path findPath(String documentId)
public Path findPath(String childDocumentId, @Nullable String parentDocumentId)
throws FileNotFoundException {
Log.w(TAG, "findPath is called on an unsupported provider.");
return null;
throw new UnsupportedOperationException("findPath not supported.");
}
/**
@@ -897,9 +915,27 @@ 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 boolean isTreeUri = isTreeUri(documentUri);
final Path path = findPath(documentId);
if (isTreeUri) {
enforceReadPermissionInner(documentUri, getCallingPackage(), null);
} else {
getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null);
}
final String parentDocumentId = isTreeUri
? DocumentsContract.getTreeDocumentId(documentUri)
: null;
final Path path = findPath(documentId, parentDocumentId);
// Ensure provider doesn't leak information to unprivileged callers.
if (isTreeUri
&& (path.getRootId() != null
|| !Objects.equals(path.getPath().get(0), parentDocumentId))) {
throw new IllegalStateException(
"Provider returns an invalid result for findPath.");
}
out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
} else {