From cbcd39488b4bddfaa84dfe378ede2f707aedd6ca Mon Sep 17 00:00:00 2001 From: Tomasz Mikolajewski Date: Thu, 28 Jan 2016 12:39:25 +0900 Subject: Add DocumentsProvider::removeDocument(). Multi-parents are supported already in moveDocument(). For parity, this CL adds removeDocument, so it's possible to delete a file from a specific parent. Bug: 26481380 Change-Id: Icd4213abc0c3413931902f4f8984746c84c65e52 --- api/current.txt | 3 ++ api/system-current.txt | 3 ++ api/test-current.txt | 3 ++ core/java/android/provider/DocumentsContract.java | 49 +++++++++++++++++++++++ core/java/android/provider/DocumentsProvider.java | 31 ++++++++++++++ 5 files changed, 89 insertions(+) diff --git a/api/current.txt b/api/current.txt index 350c50acfd50..902f5c9bf2ec 100644 --- a/api/current.txt +++ b/api/current.txt @@ -31545,6 +31545,7 @@ package android.provider { 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 moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri); + method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, 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"; field public static final java.lang.String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF"; @@ -31570,6 +31571,7 @@ package android.provider { field public static final int FLAG_SUPPORTS_COPY = 128; // 0x80 field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100 + field public static final int FLAG_SUPPORTS_REMOVE = 2048; // 0x800 field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40 field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 @@ -31617,6 +31619,7 @@ package android.provider { method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException; method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; + method public boolean removeDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public java.lang.String renameDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public final void revokeDocumentPermission(java.lang.String); method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); diff --git a/api/system-current.txt b/api/system-current.txt index 50a789d2e5d8..53ac61fe7428 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -33444,6 +33444,7 @@ package android.provider { 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 moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri); + method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, 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"; field public static final java.lang.String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF"; @@ -33469,6 +33470,7 @@ package android.provider { field public static final int FLAG_SUPPORTS_COPY = 128; // 0x80 field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100 + field public static final int FLAG_SUPPORTS_REMOVE = 2048; // 0x800 field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40 field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 @@ -33516,6 +33518,7 @@ package android.provider { method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException; method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; + method public boolean removeDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public java.lang.String renameDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public final void revokeDocumentPermission(java.lang.String); method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); diff --git a/api/test-current.txt b/api/test-current.txt index 787c3ccc7566..07492d57d3e0 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -31558,6 +31558,7 @@ package android.provider { 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 moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri); + method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, 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"; field public static final java.lang.String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF"; @@ -31583,6 +31584,7 @@ package android.provider { field public static final int FLAG_SUPPORTS_COPY = 128; // 0x80 field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100 + field public static final int FLAG_SUPPORTS_REMOVE = 2048; // 0x800 field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40 field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 @@ -31630,6 +31632,7 @@ package android.provider { method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException; method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; + method public boolean removeDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public java.lang.String renameDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public final void revokeDocumentPermission(java.lang.String); method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index cdd88f60244f..5c58a2a5c19e 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -234,6 +234,9 @@ public final class DocumentsContract { * @see #FLAG_DIR_PREFERS_LAST_MODIFIED * @see #FLAG_VIRTUAL_DOCUMENT * @see #FLAG_ARCHIVE + * @see #FLAG_SUPPORTS_COPY + * @see #FLAG_SUPPORTS_MOVE + * @see #FLAG_SUPPORTS_REMOVE */ public static final String COLUMN_FLAGS = "flags"; @@ -370,6 +373,15 @@ public final class DocumentsContract { */ public static final int FLAG_ARCHIVE = 1 << 10; + /** + * Flag indicating that a document can be removed from a parent. + * + * @see #COLUMN_FLAGS + * @see DocumentsContract#removeDocument(ContentProviderClient, Uri, Uri) + * @see DocumentsProvider#removeDocument(String, String) + */ + public static final int FLAG_SUPPORTS_REMOVE = 1 << 11; + /** * Flag indicating that document titles should be hidden when viewing * this directory in a larger format grid. For example, a directory @@ -612,6 +624,8 @@ public final class DocumentsContract { public static final String METHOD_MOVE_DOCUMENT = "android:moveDocument"; /** {@hide} */ 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 EXTRA_PARENT_URI = "parentUri"; @@ -1203,6 +1217,41 @@ public final class DocumentsContract { return out.getParcelable(DocumentsContract.EXTRA_URI); } + /** + * Removes the given document from a parent directory. + * + *

In contrast to {@link #deleteDocument} it requires specifying the parent. + * This method is especially useful if the document can be in multiple parents. + * + * @param documentUri document with {@link Document#FLAG_SUPPORTS_REMOVE} + * @param parentDocumentUri parent document of the document to remove. + * @return true if the document was removed successfully. + */ + public static boolean removeDocument(ContentResolver resolver, Uri documentUri, + Uri parentDocumentUri) { + final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( + documentUri.getAuthority()); + try { + removeDocument(client, documentUri, parentDocumentUri); + return true; + } catch (Exception e) { + Log.w(TAG, "Failed to remove document", e); + return false; + } finally { + ContentProviderClient.releaseQuietly(client); + } + } + + /** {@hide} */ + public static void removeDocument(ContentProviderClient client, Uri documentUri, + Uri parentDocumentUri) throws RemoteException { + final Bundle in = new Bundle(); + in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); + in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, parentDocumentUri); + + client.call(METHOD_REMOVE_DOCUMENT, null, in); + } + /** * Open the given image for thumbnail purposes, using any embedded EXIF * thumbnail if available, and providing orientation hints from the parent diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index bae928d1755d..7a82cd1ec36f 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -21,6 +21,7 @@ import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT; 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; import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT; import static android.provider.DocumentsContract.buildDocumentUri; import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree; @@ -300,6 +301,25 @@ public abstract class DocumentsProvider extends ContentProvider { throws FileNotFoundException { throw new UnsupportedOperationException("Move not supported"); } + + /** + * Removes the requested document or a document tree. + * + *

In contrast to {@link #deleteDocument} it requires specifying the parent. + * This method is especially useful if the document can be in multiple parents. + * + *

It's the responsibility of the provider to revoke grants if the document is + * removed from the last parent, and effectively the document is deleted. + * + * @param documentId the document to remove. + * @param parentDocumentId the parent of the document to move. + */ + @SuppressWarnings("unused") + public boolean removeDocument(String documentId, String parentDocumentId) + throws FileNotFoundException { + throw new UnsupportedOperationException("Remove not supported"); + } + /** * 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 @@ -822,6 +842,17 @@ public abstract class DocumentsProvider extends ContentProvider { // Original document no longer exists, clean up any grants. revokeDocumentPermission(documentId); + } else if (METHOD_REMOVE_DOCUMENT.equals(method)) { + final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI); + final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri); + + enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null); + enforceWritePermissionInner(documentUri, getCallingPackage(), null); + removeDocument(documentId, parentSourceId); + + // It's responsibility of the provider to revoke any grants, as the document may be + // still attached to another parents. + } else { throw new UnsupportedOperationException("Method not supported " + method); } -- cgit v1.2.3-59-g8ed1b