Merge "Refactor directory API to "opening document tree."" into lmp-preview-dev
This commit is contained in:
@@ -7400,6 +7400,7 @@ package android.content {
|
||||
field public static final java.lang.String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED";
|
||||
field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
|
||||
field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
|
||||
field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
|
||||
field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
|
||||
field public static final java.lang.String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
|
||||
field public static final java.lang.String ACTION_PACKAGE_DATA_CLEARED = "android.intent.action.PACKAGE_DATA_CLEARED";
|
||||
@@ -7414,7 +7415,6 @@ package android.content {
|
||||
field public static final java.lang.String ACTION_PASTE = "android.intent.action.PASTE";
|
||||
field public static final java.lang.String ACTION_PICK = "android.intent.action.PICK";
|
||||
field public static final java.lang.String ACTION_PICK_ACTIVITY = "android.intent.action.PICK_ACTIVITY";
|
||||
field public static final java.lang.String ACTION_PICK_DIRECTORY = "android.intent.action.PICK_DIRECTORY";
|
||||
field public static final java.lang.String ACTION_POWER_CONNECTED = "android.intent.action.ACTION_POWER_CONNECTED";
|
||||
field public static final java.lang.String ACTION_POWER_DISCONNECTED = "android.intent.action.ACTION_POWER_DISCONNECTED";
|
||||
field public static final java.lang.String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY";
|
||||
@@ -23824,21 +23824,21 @@ package android.provider {
|
||||
|
||||
public final class DocumentsContract {
|
||||
method public static android.net.Uri buildChildDocumentsUri(java.lang.String, java.lang.String);
|
||||
method public static android.net.Uri buildChildDocumentsViaUri(android.net.Uri, java.lang.String);
|
||||
method public static android.net.Uri buildChildDocumentsUriUsingTree(android.net.Uri, java.lang.String);
|
||||
method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String);
|
||||
method public static android.net.Uri buildDocumentViaUri(android.net.Uri, java.lang.String);
|
||||
method public static android.net.Uri buildDocumentUriUsingTree(android.net.Uri, java.lang.String);
|
||||
method public static android.net.Uri buildRecentDocumentsUri(java.lang.String, java.lang.String);
|
||||
method public static android.net.Uri buildRootUri(java.lang.String, java.lang.String);
|
||||
method public static android.net.Uri buildRootsUri(java.lang.String);
|
||||
method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String);
|
||||
method public static android.net.Uri buildViaUri(java.lang.String, java.lang.String);
|
||||
method public static android.net.Uri buildTreeDocumentUri(java.lang.String, java.lang.String);
|
||||
method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String);
|
||||
method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri);
|
||||
method public static java.lang.String getDocumentId(android.net.Uri);
|
||||
method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal);
|
||||
method public static java.lang.String getRootId(android.net.Uri);
|
||||
method public static java.lang.String getSearchDocumentsQuery(android.net.Uri);
|
||||
method public static java.lang.String getViaDocumentId(android.net.Uri);
|
||||
method public static java.lang.String getTreeDocumentId(android.net.Uri);
|
||||
method public static boolean isDocumentUri(android.content.Context, android.net.Uri);
|
||||
method public static android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String);
|
||||
field public static final java.lang.String EXTRA_ERROR = "error";
|
||||
@@ -23877,7 +23877,7 @@ package android.provider {
|
||||
field public static final java.lang.String COLUMN_TITLE = "title";
|
||||
field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
|
||||
field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
|
||||
field public static final int FLAG_SUPPORTS_DIR_SELECTION = 16; // 0x10
|
||||
field public static final int FLAG_SUPPORTS_IS_CHILD = 16; // 0x10
|
||||
field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
|
||||
field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
|
||||
}
|
||||
|
||||
@@ -2731,6 +2731,7 @@ public class Intent implements Parcelable, Cloneable {
|
||||
* returned in {@link #getClipData()}.
|
||||
*
|
||||
* @see DocumentsContract
|
||||
* @see #ACTION_OPEN_DOCUMENT_TREE
|
||||
* @see #ACTION_CREATE_DOCUMENT
|
||||
* @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
*/
|
||||
@@ -2765,28 +2766,30 @@ public class Intent implements Parcelable, Cloneable {
|
||||
*
|
||||
* @see DocumentsContract
|
||||
* @see #ACTION_OPEN_DOCUMENT
|
||||
* @see #ACTION_OPEN_DOCUMENT_TREE
|
||||
* @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
*/
|
||||
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
|
||||
public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
|
||||
|
||||
/**
|
||||
* Activity Action: Allow the user to pick a directory. When invoked, the
|
||||
* system will display the various {@link DocumentsProvider} instances
|
||||
* installed on the device, letting the user navigate through them. Apps can
|
||||
* fully manage documents within the returned directory.
|
||||
* Activity Action: Allow the user to pick a directory subtree. When
|
||||
* invoked, the system will display the various {@link DocumentsProvider}
|
||||
* instances installed on the device, letting the user navigate through
|
||||
* them. Apps can fully manage documents within the returned directory.
|
||||
* <p>
|
||||
* To gain access to descendant (child, grandchild, etc) documents, use
|
||||
* {@link DocumentsContract#buildDocumentViaUri(Uri, String)} and
|
||||
* {@link DocumentsContract#buildChildDocumentsViaUri(Uri, String)} using
|
||||
* the returned directory URI.
|
||||
* {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)} and
|
||||
* {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)}
|
||||
* with the returned URI.
|
||||
* <p>
|
||||
* Output: The URI representing the selected directory.
|
||||
* Output: The URI representing the selected directory tree.
|
||||
*
|
||||
* @see DocumentsContract
|
||||
*/
|
||||
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
|
||||
public static final String ACTION_PICK_DIRECTORY = "android.intent.action.PICK_DIRECTORY";
|
||||
public static final String
|
||||
ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -3365,8 +3368,8 @@ public class Intent implements Parcelable, Cloneable {
|
||||
*
|
||||
* @see #ACTION_GET_CONTENT
|
||||
* @see #ACTION_OPEN_DOCUMENT
|
||||
* @see #ACTION_OPEN_DOCUMENT_TREE
|
||||
* @see #ACTION_CREATE_DOCUMENT
|
||||
* @see #ACTION_PICK_DIRECTORY
|
||||
*/
|
||||
public static final String EXTRA_LOCAL_ONLY =
|
||||
"android.intent.extra.LOCAL_ONLY";
|
||||
|
||||
@@ -60,7 +60,8 @@ import java.util.List;
|
||||
* <p>
|
||||
* All client apps must hold a valid URI permission grant to access documents,
|
||||
* typically issued when a user makes a selection through
|
||||
* {@link Intent#ACTION_OPEN_DOCUMENT} or {@link Intent#ACTION_CREATE_DOCUMENT}.
|
||||
* {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
|
||||
* or {@link Intent#ACTION_OPEN_DOCUMENT_TREE}.
|
||||
*
|
||||
* @see DocumentsProvider
|
||||
*/
|
||||
@@ -73,8 +74,8 @@ public final class DocumentsContract {
|
||||
// content://com.example/root/sdcard/search/?query=pony
|
||||
// content://com.example/document/12/
|
||||
// content://com.example/document/12/children/
|
||||
// content://com.example/via/12/document/24/
|
||||
// content://com.example/via/12/document/24/children/
|
||||
// content://com.example/tree/12/document/24/
|
||||
// content://com.example/tree/12/document/24/children/
|
||||
|
||||
private DocumentsContract() {
|
||||
}
|
||||
@@ -441,12 +442,13 @@ public final class DocumentsContract {
|
||||
public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
|
||||
|
||||
/**
|
||||
* Flag indicating that this root supports directory selection.
|
||||
* Flag indicating that this root supports testing parent child
|
||||
* relationships.
|
||||
*
|
||||
* @see #COLUMN_FLAGS
|
||||
* @see DocumentsProvider#isChildDocument(String, String)
|
||||
*/
|
||||
public static final int FLAG_SUPPORTS_DIR_SELECTION = 1 << 4;
|
||||
public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4;
|
||||
|
||||
/**
|
||||
* Flag indicating that this root is currently empty. This may be used
|
||||
@@ -518,7 +520,7 @@ public final class DocumentsContract {
|
||||
private static final String PATH_DOCUMENT = "document";
|
||||
private static final String PATH_CHILDREN = "children";
|
||||
private static final String PATH_SEARCH = "search";
|
||||
private static final String PATH_VIA = "via";
|
||||
private static final String PATH_TREE = "tree";
|
||||
|
||||
private static final String PARAM_QUERY = "query";
|
||||
private static final String PARAM_MANAGE = "manage";
|
||||
@@ -564,17 +566,17 @@ public final class DocumentsContract {
|
||||
* Build URI representing access to descendant documents of the given
|
||||
* {@link Document#COLUMN_DOCUMENT_ID}.
|
||||
*
|
||||
* @see #getViaDocumentId(Uri)
|
||||
* @see #getTreeDocumentId(Uri)
|
||||
*/
|
||||
public static Uri buildViaUri(String authority, String documentId) {
|
||||
public static Uri buildTreeDocumentUri(String authority, String documentId) {
|
||||
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
|
||||
.appendPath(PATH_VIA).appendPath(documentId).build();
|
||||
.appendPath(PATH_TREE).appendPath(documentId).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build URI representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
|
||||
* document provider. When queried, a provider will return a single row with
|
||||
* columns defined by {@link Document}.
|
||||
* Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
|
||||
* a document provider. When queried, a provider will return a single row
|
||||
* with columns defined by {@link Document}.
|
||||
*
|
||||
* @see DocumentsProvider#queryDocument(String, String[])
|
||||
* @see #getDocumentId(Uri)
|
||||
@@ -585,42 +587,46 @@ public final class DocumentsContract {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build URI representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
|
||||
* document provider. Instead of directly accessing the target document,
|
||||
* gain access via another document. The target document must be a
|
||||
* descendant (child, grandchild, etc) of the via document.
|
||||
* Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
|
||||
* a document provider. When queried, a provider will return a single row
|
||||
* with columns defined by {@link Document}.
|
||||
* <p>
|
||||
* However, instead of directly accessing the target document, the returned
|
||||
* URI will leverage access granted through a subtree URI, typically
|
||||
* returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target document
|
||||
* must be a descendant (child, grandchild, etc) of the subtree.
|
||||
* <p>
|
||||
* This is typically used to access documents under a user-selected
|
||||
* directory, since it doesn't require the user to separately confirm each
|
||||
* new document access.
|
||||
* directory tree, since it doesn't require the user to separately confirm
|
||||
* each new document access.
|
||||
*
|
||||
* @param viaUri a related document (directory) that the caller is
|
||||
* leveraging to gain access to the target document. The target
|
||||
* document must be a descendant of this directory.
|
||||
* @param treeUri the subtree to leverage to gain access to the target
|
||||
* document. The target directory must be a descendant of this
|
||||
* subtree.
|
||||
* @param documentId the target document, which the caller may not have
|
||||
* direct access to.
|
||||
* @see Intent#ACTION_PICK_DIRECTORY
|
||||
* @see Intent#ACTION_OPEN_DOCUMENT_TREE
|
||||
* @see DocumentsProvider#isChildDocument(String, String)
|
||||
* @see #buildDocumentUri(String, String)
|
||||
*/
|
||||
public static Uri buildDocumentViaUri(Uri viaUri, String documentId) {
|
||||
public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) {
|
||||
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(viaUri.getAuthority()).appendPath(PATH_VIA)
|
||||
.appendPath(getViaDocumentId(viaUri)).appendPath(PATH_DOCUMENT)
|
||||
.authority(treeUri.getAuthority()).appendPath(PATH_TREE)
|
||||
.appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
|
||||
.appendPath(documentId).build();
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public static Uri buildDocumentMaybeViaUri(Uri baseUri, String documentId) {
|
||||
if (isViaUri(baseUri)) {
|
||||
return buildDocumentViaUri(baseUri, documentId);
|
||||
public static Uri buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId) {
|
||||
if (isTreeUri(baseUri)) {
|
||||
return buildDocumentUriUsingTree(baseUri, documentId);
|
||||
} else {
|
||||
return buildDocumentUri(baseUri.getAuthority(), documentId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build URI representing the children of the given directory in a document
|
||||
* Build URI representing the children of the target directory in a document
|
||||
* provider. When queried, a provider will return zero or more rows with
|
||||
* columns defined by {@link Document}.
|
||||
*
|
||||
@@ -637,28 +643,33 @@ public final class DocumentsContract {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build URI representing the children of the given directory in a document
|
||||
* provider. Instead of directly accessing the target document, gain access
|
||||
* via another document. The target document must be a descendant (child,
|
||||
* grandchild, etc) of the via document.
|
||||
* Build URI representing the children of the target directory in a document
|
||||
* provider. When queried, a provider will return zero or more rows with
|
||||
* columns defined by {@link Document}.
|
||||
* <p>
|
||||
* However, instead of directly accessing the target directory, the returned
|
||||
* URI will leverage access granted through a subtree URI, typically
|
||||
* returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target
|
||||
* directory must be a descendant (child, grandchild, etc) of the subtree.
|
||||
* <p>
|
||||
* This is typically used to access documents under a user-selected
|
||||
* directory, since it doesn't require the user to separately confirm each
|
||||
* new document access.
|
||||
* directory tree, since it doesn't require the user to separately confirm
|
||||
* each new document access.
|
||||
*
|
||||
* @param viaUri a related document (directory) that the caller is
|
||||
* leveraging to gain access to the target document. The target
|
||||
* document must be a descendant of this directory.
|
||||
* @param parentDocumentId the target document, which the caller may not
|
||||
* have direct access to.
|
||||
* @see Intent#ACTION_PICK_DIRECTORY
|
||||
* @param treeUri the subtree to leverage to gain access to the target
|
||||
* document. The target directory must be a descendant of this
|
||||
* subtree.
|
||||
* @param parentDocumentId the document to return children for, which the
|
||||
* caller may not have direct access to, and which must be a
|
||||
* directory with MIME type of {@link Document#MIME_TYPE_DIR}.
|
||||
* @see Intent#ACTION_OPEN_DOCUMENT_TREE
|
||||
* @see DocumentsProvider#isChildDocument(String, String)
|
||||
* @see #buildChildDocumentsUri(String, String)
|
||||
*/
|
||||
public static Uri buildChildDocumentsViaUri(Uri viaUri, String parentDocumentId) {
|
||||
public static Uri buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId) {
|
||||
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(viaUri.getAuthority()).appendPath(PATH_VIA)
|
||||
.appendPath(getViaDocumentId(viaUri)).appendPath(PATH_DOCUMENT)
|
||||
.authority(treeUri.getAuthority()).appendPath(PATH_TREE)
|
||||
.appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
|
||||
.appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build();
|
||||
}
|
||||
|
||||
@@ -683,21 +694,24 @@ public final class DocumentsContract {
|
||||
* {@link DocumentsProvider}.
|
||||
*
|
||||
* @see #buildDocumentUri(String, String)
|
||||
* @see #buildDocumentViaUri(Uri, String)
|
||||
* @see #buildDocumentUriUsingTree(Uri, String)
|
||||
*/
|
||||
public static boolean isDocumentUri(Context context, Uri uri) {
|
||||
final List<String> paths = uri.getPathSegments();
|
||||
if (paths.size() >= 2
|
||||
&& (PATH_DOCUMENT.equals(paths.get(0)) || PATH_VIA.equals(paths.get(0)))) {
|
||||
if (paths.size() == 2 && PATH_DOCUMENT.equals(paths.get(0))) {
|
||||
return isDocumentsProvider(context, uri.getAuthority());
|
||||
}
|
||||
if (paths.size() == 4 && PATH_TREE.equals(paths.get(0))
|
||||
&& PATH_DOCUMENT.equals(paths.get(2))) {
|
||||
return isDocumentsProvider(context, uri.getAuthority());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public static boolean isViaUri(Uri uri) {
|
||||
public static boolean isTreeUri(Uri uri) {
|
||||
final List<String> paths = uri.getPathSegments();
|
||||
return (paths.size() >= 2 && PATH_VIA.equals(paths.get(0)));
|
||||
return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0)));
|
||||
}
|
||||
|
||||
private static boolean isDocumentsProvider(Context context, String authority) {
|
||||
@@ -733,7 +747,7 @@ public final class DocumentsContract {
|
||||
if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) {
|
||||
return paths.get(1);
|
||||
}
|
||||
if (paths.size() >= 4 && PATH_VIA.equals(paths.get(0))
|
||||
if (paths.size() >= 4 && PATH_TREE.equals(paths.get(0))
|
||||
&& PATH_DOCUMENT.equals(paths.get(2))) {
|
||||
return paths.get(3);
|
||||
}
|
||||
@@ -742,12 +756,10 @@ public final class DocumentsContract {
|
||||
|
||||
/**
|
||||
* Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
|
||||
*
|
||||
* @see #isViaUri(Uri)
|
||||
*/
|
||||
public static String getViaDocumentId(Uri documentUri) {
|
||||
public static String getTreeDocumentId(Uri documentUri) {
|
||||
final List<String> paths = documentUri.getPathSegments();
|
||||
if (paths.size() >= 2 && PATH_VIA.equals(paths.get(0))) {
|
||||
if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) {
|
||||
return paths.get(1);
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid URI: " + documentUri);
|
||||
|
||||
@@ -20,10 +20,14 @@ import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE;
|
||||
import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
|
||||
import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
|
||||
import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
|
||||
import static android.provider.DocumentsContract.buildDocumentUri;
|
||||
import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree;
|
||||
import static android.provider.DocumentsContract.buildTreeDocumentUri;
|
||||
import static android.provider.DocumentsContract.getDocumentId;
|
||||
import static android.provider.DocumentsContract.getRootId;
|
||||
import static android.provider.DocumentsContract.getSearchDocumentsQuery;
|
||||
import static android.provider.DocumentsContract.isViaUri;
|
||||
import static android.provider.DocumentsContract.getTreeDocumentId;
|
||||
import static android.provider.DocumentsContract.isTreeUri;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
@@ -117,6 +121,7 @@ import java.util.Objects;
|
||||
* </p>
|
||||
*
|
||||
* @see Intent#ACTION_OPEN_DOCUMENT
|
||||
* @see Intent#ACTION_OPEN_DOCUMENT_TREE
|
||||
* @see Intent#ACTION_CREATE_DOCUMENT
|
||||
*/
|
||||
public abstract class DocumentsProvider extends ContentProvider {
|
||||
@@ -128,8 +133,8 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
private static final int MATCH_SEARCH = 4;
|
||||
private static final int MATCH_DOCUMENT = 5;
|
||||
private static final int MATCH_CHILDREN = 6;
|
||||
private static final int MATCH_DOCUMENT_VIA = 7;
|
||||
private static final int MATCH_CHILDREN_VIA = 8;
|
||||
private static final int MATCH_DOCUMENT_TREE = 7;
|
||||
private static final int MATCH_CHILDREN_TREE = 8;
|
||||
|
||||
private String mAuthority;
|
||||
|
||||
@@ -149,8 +154,8 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
|
||||
mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
|
||||
mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
|
||||
mMatcher.addURI(mAuthority, "via/*/document/*", MATCH_DOCUMENT_VIA);
|
||||
mMatcher.addURI(mAuthority, "via/*/document/*/children", MATCH_CHILDREN_VIA);
|
||||
mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE);
|
||||
mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE);
|
||||
|
||||
// Sanity check our setup
|
||||
if (!info.exported) {
|
||||
@@ -169,23 +174,24 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
|
||||
/**
|
||||
* Test if a document is descendant (child, grandchild, etc) from the given
|
||||
* parent. Providers must override this to support directory selection. You
|
||||
* should avoid making network requests to keep this request fast.
|
||||
* parent. For example, providers must implement this to support
|
||||
* {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. You should avoid making network
|
||||
* requests to keep this request fast.
|
||||
*
|
||||
* @param parentDocumentId parent to verify against.
|
||||
* @param documentId child to verify.
|
||||
* @return if given document is a descendant of the given parent.
|
||||
* @see DocumentsContract.Root#FLAG_SUPPORTS_DIR_SELECTION
|
||||
* @see DocumentsContract.Root#FLAG_SUPPORTS_IS_CHILD
|
||||
*/
|
||||
public boolean isChildDocument(String parentDocumentId, String documentId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
private void enforceVia(Uri documentUri) {
|
||||
if (DocumentsContract.isViaUri(documentUri)) {
|
||||
final String parent = DocumentsContract.getViaDocumentId(documentUri);
|
||||
final String child = DocumentsContract.getDocumentId(documentUri);
|
||||
private void enforceTree(Uri documentUri) {
|
||||
if (isTreeUri(documentUri)) {
|
||||
final String parent = getTreeDocumentId(documentUri);
|
||||
final String child = getDocumentId(documentUri);
|
||||
if (Objects.equals(parent, child)) {
|
||||
return;
|
||||
}
|
||||
@@ -479,12 +485,12 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
return querySearchDocuments(
|
||||
getRootId(uri), getSearchDocumentsQuery(uri), projection);
|
||||
case MATCH_DOCUMENT:
|
||||
case MATCH_DOCUMENT_VIA:
|
||||
enforceVia(uri);
|
||||
case MATCH_DOCUMENT_TREE:
|
||||
enforceTree(uri);
|
||||
return queryDocument(getDocumentId(uri), projection);
|
||||
case MATCH_CHILDREN:
|
||||
case MATCH_CHILDREN_VIA:
|
||||
enforceVia(uri);
|
||||
case MATCH_CHILDREN_TREE:
|
||||
enforceTree(uri);
|
||||
if (DocumentsContract.isManageMode(uri)) {
|
||||
return queryChildDocumentsForManage(
|
||||
getDocumentId(uri), projection, sortOrder);
|
||||
@@ -512,8 +518,8 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
case MATCH_ROOT:
|
||||
return DocumentsContract.Root.MIME_TYPE_ITEM;
|
||||
case MATCH_DOCUMENT:
|
||||
case MATCH_DOCUMENT_VIA:
|
||||
enforceVia(uri);
|
||||
case MATCH_DOCUMENT_TREE:
|
||||
enforceTree(uri);
|
||||
return getDocumentType(getDocumentId(uri));
|
||||
default:
|
||||
return null;
|
||||
@@ -530,21 +536,20 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
* call the superclass. If the superclass returns {@code null}, the subclass
|
||||
* may implement custom behavior.
|
||||
* <p>
|
||||
* This is typically used to resolve a "via" URI into a concrete document
|
||||
* This is typically used to resolve a subtree URI into a concrete document
|
||||
* reference, issuing a narrower single-document URI permission grant along
|
||||
* the way.
|
||||
*
|
||||
* @see DocumentsContract#buildDocumentViaUri(Uri, String)
|
||||
* @see DocumentsContract#buildDocumentUriUsingTree(Uri, String)
|
||||
*/
|
||||
@Override
|
||||
public Uri canonicalize(Uri uri) {
|
||||
final Context context = getContext();
|
||||
switch (mMatcher.match(uri)) {
|
||||
case MATCH_DOCUMENT_VIA:
|
||||
enforceVia(uri);
|
||||
case MATCH_DOCUMENT_TREE:
|
||||
enforceTree(uri);
|
||||
|
||||
final Uri narrowUri = DocumentsContract.buildDocumentUri(uri.getAuthority(),
|
||||
DocumentsContract.getDocumentId(uri));
|
||||
final Uri narrowUri = buildDocumentUri(uri.getAuthority(), getDocumentId(uri));
|
||||
|
||||
// Caller may only have prefix grant, so extend them a grant to
|
||||
// the narrow URI.
|
||||
@@ -628,7 +633,7 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
throw new SecurityException(
|
||||
"Requested authority " + authority + " doesn't match provider " + mAuthority);
|
||||
}
|
||||
enforceVia(documentUri);
|
||||
enforceTree(documentUri);
|
||||
|
||||
final Bundle out = new Bundle();
|
||||
try {
|
||||
@@ -641,8 +646,8 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
|
||||
// No need to issue new grants here, since caller either has
|
||||
// manage permission or a prefix grant. We might generate a
|
||||
// "via" style URI if that's how they called us.
|
||||
final Uri newDocumentUri = DocumentsContract.buildDocumentMaybeViaUri(documentUri,
|
||||
// tree style URI if that's how they called us.
|
||||
final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
|
||||
newDocumentId);
|
||||
out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
|
||||
|
||||
@@ -653,12 +658,12 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
final String newDocumentId = renameDocument(documentId, displayName);
|
||||
|
||||
if (newDocumentId != null) {
|
||||
final Uri newDocumentUri = DocumentsContract.buildDocumentMaybeViaUri(
|
||||
documentUri, newDocumentId);
|
||||
final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
|
||||
newDocumentId);
|
||||
|
||||
// If caller came in with a narrow grant, issue them a
|
||||
// narrow grant for the newly renamed document.
|
||||
if (!isViaUri(newDocumentUri)) {
|
||||
if (!isTreeUri(newDocumentUri)) {
|
||||
final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
|
||||
documentUri);
|
||||
context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
|
||||
@@ -694,8 +699,8 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
*/
|
||||
public final void revokeDocumentPermission(String documentId) {
|
||||
final Context context = getContext();
|
||||
context.revokeUriPermission(DocumentsContract.buildDocumentUri(mAuthority, documentId), ~0);
|
||||
context.revokeUriPermission(DocumentsContract.buildViaUri(mAuthority, documentId), ~0);
|
||||
context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0);
|
||||
context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -705,7 +710,7 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
*/
|
||||
@Override
|
||||
public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
enforceVia(uri);
|
||||
enforceTree(uri);
|
||||
return openDocument(getDocumentId(uri), mode, null);
|
||||
}
|
||||
|
||||
@@ -717,7 +722,7 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
@Override
|
||||
public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
|
||||
throws FileNotFoundException {
|
||||
enforceVia(uri);
|
||||
enforceTree(uri);
|
||||
return openDocument(getDocumentId(uri), mode, signal);
|
||||
}
|
||||
|
||||
@@ -730,7 +735,7 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
@SuppressWarnings("resource")
|
||||
public final AssetFileDescriptor openAssetFile(Uri uri, String mode)
|
||||
throws FileNotFoundException {
|
||||
enforceVia(uri);
|
||||
enforceTree(uri);
|
||||
final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
|
||||
return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
|
||||
}
|
||||
@@ -744,7 +749,7 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
@SuppressWarnings("resource")
|
||||
public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
|
||||
throws FileNotFoundException {
|
||||
enforceVia(uri);
|
||||
enforceTree(uri);
|
||||
final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
|
||||
return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
|
||||
}
|
||||
@@ -757,7 +762,7 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
@Override
|
||||
public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
|
||||
throws FileNotFoundException {
|
||||
enforceVia(uri);
|
||||
enforceTree(uri);
|
||||
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
|
||||
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
|
||||
return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
|
||||
@@ -775,7 +780,7 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
public final AssetFileDescriptor openTypedAssetFile(
|
||||
Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
|
||||
throws FileNotFoundException {
|
||||
enforceVia(uri);
|
||||
enforceTree(uri);
|
||||
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
|
||||
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
|
||||
return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PICK_DIRECTORY" />
|
||||
<action android:name="android.intent.action.OPEN_DOCUMENT_TREE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
|
||||
@@ -24,7 +24,7 @@ import static com.android.documentsui.DocumentsActivity.State.ACTION_CREATE;
|
||||
import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT;
|
||||
import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE;
|
||||
import static com.android.documentsui.DocumentsActivity.State.ACTION_OPEN;
|
||||
import static com.android.documentsui.DocumentsActivity.State.ACTION_PICK_DIRECTORY;
|
||||
import static com.android.documentsui.DocumentsActivity.State.ACTION_OPEN_TREE;
|
||||
import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
|
||||
import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
|
||||
|
||||
@@ -203,7 +203,7 @@ public class DocumentsActivity extends Activity {
|
||||
final String mimeType = getIntent().getType();
|
||||
final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
|
||||
SaveFragment.show(getFragmentManager(), mimeType, title);
|
||||
} else if (mState.action == ACTION_PICK_DIRECTORY) {
|
||||
} else if (mState.action == ACTION_OPEN_TREE) {
|
||||
PickFragment.show(getFragmentManager());
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ public class DocumentsActivity extends Activity {
|
||||
moreApps.setPackage(null);
|
||||
RootsFragment.show(getFragmentManager(), moreApps);
|
||||
} else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE
|
||||
|| mState.action == ACTION_PICK_DIRECTORY) {
|
||||
|| mState.action == ACTION_OPEN_TREE) {
|
||||
RootsFragment.show(getFragmentManager(), null);
|
||||
}
|
||||
|
||||
@@ -240,8 +240,8 @@ public class DocumentsActivity extends Activity {
|
||||
mState.action = ACTION_CREATE;
|
||||
} else if (Intent.ACTION_GET_CONTENT.equals(action)) {
|
||||
mState.action = ACTION_GET_CONTENT;
|
||||
} else if (Intent.ACTION_PICK_DIRECTORY.equals(action)) {
|
||||
mState.action = ACTION_PICK_DIRECTORY;
|
||||
} else if (Intent.ACTION_OPEN_DOCUMENT_TREE.equals(action)) {
|
||||
mState.action = ACTION_OPEN_TREE;
|
||||
} else if (DocumentsContract.ACTION_MANAGE_ROOT.equals(action)) {
|
||||
mState.action = ACTION_MANAGE;
|
||||
}
|
||||
@@ -441,7 +441,7 @@ public class DocumentsActivity extends Activity {
|
||||
actionBar.setIcon(new ColorDrawable());
|
||||
|
||||
if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT
|
||||
|| mState.action == ACTION_PICK_DIRECTORY) {
|
||||
|| mState.action == ACTION_OPEN_TREE) {
|
||||
actionBar.setTitle(R.string.title_open);
|
||||
} else if (mState.action == ACTION_CREATE) {
|
||||
actionBar.setTitle(R.string.title_save);
|
||||
@@ -583,7 +583,7 @@ public class DocumentsActivity extends Activity {
|
||||
sortSize.setVisible(mState.showSize);
|
||||
|
||||
final boolean searchVisible;
|
||||
if (mState.action == ACTION_CREATE || mState.action == ACTION_PICK_DIRECTORY) {
|
||||
if (mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE) {
|
||||
createDir.setVisible(cwd != null && cwd.isCreateSupported());
|
||||
searchVisible = false;
|
||||
|
||||
@@ -828,7 +828,7 @@ public class DocumentsActivity extends Activity {
|
||||
|
||||
if (cwd == null) {
|
||||
// No directory means recents
|
||||
if (mState.action == ACTION_CREATE || mState.action == ACTION_PICK_DIRECTORY) {
|
||||
if (mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE) {
|
||||
RecentsCreateFragment.show(fm);
|
||||
} else {
|
||||
DirectoryFragment.showRecentsOpen(fm, anim);
|
||||
@@ -857,7 +857,7 @@ public class DocumentsActivity extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
if (mState.action == ACTION_PICK_DIRECTORY) {
|
||||
if (mState.action == ACTION_OPEN_TREE) {
|
||||
final PickFragment pick = PickFragment.get(fm);
|
||||
if (pick != null) {
|
||||
final CharSequence displayName = (mState.stack.size() <= 1) ? root.title
|
||||
@@ -1021,7 +1021,7 @@ public class DocumentsActivity extends Activity {
|
||||
}
|
||||
|
||||
public void onPickRequested(DocumentInfo pickTarget) {
|
||||
final Uri viaUri = DocumentsContract.buildViaUri(pickTarget.authority,
|
||||
final Uri viaUri = DocumentsContract.buildTreeDocumentUri(pickTarget.authority,
|
||||
pickTarget.documentId);
|
||||
new PickFinishTask(viaUri).executeOnExecutor(getCurrentExecutor());
|
||||
}
|
||||
@@ -1031,7 +1031,7 @@ public class DocumentsActivity extends Activity {
|
||||
final ContentValues values = new ContentValues();
|
||||
|
||||
final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
|
||||
if (mState.action == ACTION_CREATE || mState.action == ACTION_PICK_DIRECTORY) {
|
||||
if (mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE) {
|
||||
// Remember stack for last create
|
||||
values.clear();
|
||||
values.put(RecentColumns.KEY, mState.stack.buildKey());
|
||||
@@ -1064,7 +1064,7 @@ public class DocumentsActivity extends Activity {
|
||||
|
||||
if (mState.action == ACTION_GET_CONTENT) {
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
} else if (mState.action == ACTION_PICK_DIRECTORY) {
|
||||
} else if (mState.action == ACTION_OPEN_TREE) {
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
@@ -1202,7 +1202,7 @@ public class DocumentsActivity extends Activity {
|
||||
public static final int ACTION_OPEN = 1;
|
||||
public static final int ACTION_CREATE = 2;
|
||||
public static final int ACTION_GET_CONTENT = 3;
|
||||
public static final int ACTION_PICK_DIRECTORY = 4;
|
||||
public static final int ACTION_OPEN_TREE = 4;
|
||||
public static final int ACTION_MANAGE = 5;
|
||||
|
||||
public static final int MODE_UNKNOWN = 0;
|
||||
|
||||
@@ -77,13 +77,15 @@ public class PickFragment extends Fragment {
|
||||
public void setPickTarget(DocumentInfo pickTarget, CharSequence displayName) {
|
||||
mPickTarget = pickTarget;
|
||||
|
||||
if (mPickTarget != null) {
|
||||
mContainer.setVisibility(View.VISIBLE);
|
||||
final Locale locale = getResources().getConfiguration().locale;
|
||||
final String raw = getString(R.string.menu_select).toUpperCase(locale);
|
||||
mPick.setText(TextUtils.expandTemplate(raw, displayName));
|
||||
} else {
|
||||
mContainer.setVisibility(View.GONE);
|
||||
if (mContainer != null) {
|
||||
if (mPickTarget != null) {
|
||||
mContainer.setVisibility(View.VISIBLE);
|
||||
final Locale locale = getResources().getConfiguration().locale;
|
||||
final String raw = getString(R.string.menu_select).toUpperCase(locale);
|
||||
mPick.setText(TextUtils.expandTemplate(raw, displayName));
|
||||
} else {
|
||||
mContainer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public class RootsCache {
|
||||
mRecentsRoot.rootId = null;
|
||||
mRecentsRoot.icon = R.drawable.ic_root_recent;
|
||||
mRecentsRoot.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE
|
||||
| Root.FLAG_SUPPORTS_DIR_SELECTION;
|
||||
| Root.FLAG_SUPPORTS_IS_CHILD;
|
||||
mRecentsRoot.title = mContext.getString(R.string.root_recent);
|
||||
mRecentsRoot.availableBytes = -1;
|
||||
|
||||
@@ -350,7 +350,7 @@ public class RootsCache {
|
||||
final List<RootInfo> matching = Lists.newArrayList();
|
||||
for (RootInfo root : roots) {
|
||||
final boolean supportsCreate = (root.flags & Root.FLAG_SUPPORTS_CREATE) != 0;
|
||||
final boolean supportsDir = (root.flags & Root.FLAG_SUPPORTS_DIR_SELECTION) != 0;
|
||||
final boolean supportsIsChild = (root.flags & Root.FLAG_SUPPORTS_IS_CHILD) != 0;
|
||||
final boolean advanced = (root.flags & Root.FLAG_ADVANCED) != 0;
|
||||
final boolean localOnly = (root.flags & Root.FLAG_LOCAL_ONLY) != 0;
|
||||
final boolean empty = (root.flags & Root.FLAG_EMPTY) != 0;
|
||||
@@ -358,7 +358,7 @@ public class RootsCache {
|
||||
// Exclude read-only devices when creating
|
||||
if (state.action == State.ACTION_CREATE && !supportsCreate) continue;
|
||||
// Exclude roots that don't support directory picking
|
||||
if (state.action == State.ACTION_PICK_DIRECTORY && !supportsDir) continue;
|
||||
if (state.action == State.ACTION_OPEN_TREE && !supportsIsChild) continue;
|
||||
// Exclude advanced devices when not requested
|
||||
if (!state.showAdvanced && advanced) continue;
|
||||
// Exclude non-local devices when local only
|
||||
|
||||
@@ -145,7 +145,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
final RootInfo root = new RootInfo();
|
||||
root.rootId = rootId;
|
||||
root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
|
||||
| Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_DIR_SELECTION;
|
||||
| Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
|
||||
if (ROOT_ID_PRIMARY_EMULATED.equals(rootId)) {
|
||||
root.title = getContext().getString(R.string.root_internal_storage);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user