Loading core/api/current.txt +8 −0 Original line number Diff line number Diff line Loading @@ -38147,6 +38147,7 @@ package android.provider { method public static android.net.Uri buildRootUri(String, String); method public static android.net.Uri buildRootsUri(String); method public static android.net.Uri buildSearchDocumentsUri(String, String, String); method @FlaggedApi("android.provider.enable_documents_trash_api") @NonNull public static android.net.Uri buildTrashDocumentsUri(@NonNull String); method public static android.net.Uri buildTreeDocumentUri(String, String); method @Nullable public static android.net.Uri copyDocument(@NonNull android.content.ContentResolver, @NonNull android.net.Uri, @NonNull android.net.Uri) throws java.io.FileNotFoundException; method @Nullable public static android.net.Uri createDocument(@NonNull android.content.ContentResolver, @NonNull android.net.Uri, @NonNull String, @NonNull String) throws java.io.FileNotFoundException; Loading @@ -38168,6 +38169,8 @@ package android.provider { method @Nullable public static android.net.Uri moveDocument(@NonNull android.content.ContentResolver, @NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.net.Uri) throws java.io.FileNotFoundException; method public static boolean removeDocument(@NonNull android.content.ContentResolver, @NonNull android.net.Uri, @NonNull android.net.Uri) throws java.io.FileNotFoundException; method @Nullable public static android.net.Uri renameDocument(@NonNull android.content.ContentResolver, @NonNull android.net.Uri, @NonNull String) throws java.io.FileNotFoundException; method @FlaggedApi("android.provider.enable_documents_trash_api") @Nullable public static android.net.Uri restoreDocumentFromTrash(@NonNull android.content.ContentResolver, @NonNull android.net.Uri, @Nullable android.net.Uri) throws java.io.FileNotFoundException; method @FlaggedApi("android.provider.enable_documents_trash_api") @Nullable public static android.net.Uri trashDocument(@NonNull android.content.ContentResolver, @NonNull android.net.Uri) throws java.io.FileNotFoundException; field public static final String ACTION_DOCUMENT_SETTINGS = "android.provider.action.DOCUMENT_SETTINGS"; field public static final String EXTRA_ERROR = "error"; field public static final String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF"; Loading Loading @@ -38208,8 +38211,10 @@ package android.provider { field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100 field public static final int FLAG_SUPPORTS_REMOVE = 1024; // 0x400 field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40 field @FlaggedApi("android.provider.enable_documents_trash_api") public static final int FLAG_SUPPORTS_RESTORE = 131072; // 0x20000 field public static final int FLAG_SUPPORTS_SETTINGS = 2048; // 0x800 field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 field @FlaggedApi("android.provider.enable_documents_trash_api") public static final int FLAG_SUPPORTS_TRASH = 65536; // 0x10000 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 field public static final int FLAG_VIRTUAL_DOCUMENT = 512; // 0x200 field public static final int FLAG_WEB_LINKABLE = 4096; // 0x1000 Loading Loading @@ -38282,9 +38287,12 @@ package android.provider { method public abstract android.database.Cursor queryRoots(String[]) throws java.io.FileNotFoundException; method public android.database.Cursor querySearchDocuments(String, String, String[]) throws java.io.FileNotFoundException; method @Nullable public android.database.Cursor querySearchDocuments(@NonNull String, @Nullable String[], @NonNull android.os.Bundle) throws java.io.FileNotFoundException; method @FlaggedApi("android.provider.enable_documents_trash_api") @Nullable public android.database.Cursor queryTrashDocuments(@Nullable String[]) throws java.io.FileNotFoundException; method public void removeDocument(String, String) throws java.io.FileNotFoundException; method public String renameDocument(String, String) throws java.io.FileNotFoundException; method @FlaggedApi("android.provider.enable_documents_trash_api") @Nullable public String restoreDocumentFromTrash(@NonNull String, @Nullable String) throws java.io.FileNotFoundException; method public final void revokeDocumentPermission(String); method @FlaggedApi("android.provider.enable_documents_trash_api") @Nullable public String trashDocument(@NonNull String) throws java.io.FileNotFoundException; method public final int update(android.net.Uri, android.content.ContentValues, String, String[]); } core/java/android/provider/DocumentsContract.java +107 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.provider; import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkCollectionNotEmpty; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; Loading Loading @@ -581,6 +582,29 @@ public final class DocumentsContract { * @see #COLUMN_FLAGS */ public static final int FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE = 1 << 15; /** * Flag indicating that a document can be trashed. * * @see #COLUMN_FLAGS * @see DocumentsContract#trashDocument(ContentResolver, Uri) * @see DocumentsProvider#trashDocument(String) */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) public static final int FLAG_SUPPORTS_TRASH = 1 << 16; /** * Flag indicating that a document can be restored. * Only trashed documents can be restored * * @see #COLUMN_FLAGS * @see DocumentsContract#restoreDocumentFromTrash(ContentResolver, Uri, Uri) * @see DocumentsProvider#restoreDocumentFromTrash(String, String) */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) public static final int FLAG_SUPPORTS_RESTORE = 1 << 17; } /** Loading Loading @@ -875,6 +899,13 @@ public final class DocumentsContract { public static final String METHOD_CREATE_WEB_LINK_INTENT = "android:createWebLinkIntent"; /** {@hide} */ public static final String METHOD_GET_DOCUMENT_METADATA = "android:getDocumentMetadata"; /** {@hide} */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) public static final String METHOD_TRASH_DOCUMENT = "android:trashDocument"; /** {@hide} */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) public static final String METHOD_RESTORE_DOCUMENT_FROM_TRASH = "android:restoreDocumentFromTrash"; /** {@hide} */ public static final String EXTRA_PARENT_URI = "parentUri"; Loading @@ -888,6 +919,7 @@ public final class DocumentsContract { private static final String PATH_ROOT = "root"; private static final String PATH_RECENT = "recent"; private static final String PATH_TRASH = "trash"; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private static final String PATH_DOCUMENT = "document"; private static final String PATH_CHILDREN = "children"; Loading Loading @@ -935,6 +967,20 @@ public final class DocumentsContract { .appendPath(PATH_RECENT).build(); } /** * Returns URI representing the query trash documents of a specific document provider. * * @see DocumentsProvider#queryTrashDocuments(String[]) */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) @NonNull public static Uri buildTrashDocumentsUri(@NonNull String authority) { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority(authority).appendPath(PATH_TRASH).build(); } /** * Build URI representing access to descendant documents of the given * {@link Document#COLUMN_DOCUMENT_ID}. Loading Loading @@ -1560,6 +1606,67 @@ public final class DocumentsContract { } } /** * Trashes the given document. * * @param documentUri document with {@link Document#FLAG_SUPPORTS_TRASH} * @return the trashed document, or {@code null} if failed. * @throws FileNotFoundException if the documentUri does not exist. * @throws IllegalArgumentException if the document does not support trashing. * @throws UnsupportedOperationException if the document provider does not support trashing. */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) @Nullable public static Uri trashDocument(@NonNull ContentResolver content, @NonNull Uri documentUri) throws FileNotFoundException { try { final Bundle in = new Bundle(); in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); final Bundle out = content.call(documentUri.getAuthority(), METHOD_TRASH_DOCUMENT, null, in); return out.getParcelable(DocumentsContract.EXTRA_URI, android.net.Uri.class); } catch (Exception e) { Log.w(TAG, "Failed to trash document", e); rethrowIfNecessary(e); return null; } } /** * Restores a document from the trash. * * @param sourceDocumentUri trashed document to restore * @param targetParentDocumentUri parent document to restore the document to * @return the restored document, or {@code null} if failed. * @throws FileNotFoundException if the {@code sourceDocumentUri} does not exist or the * {@code targetParentDocumentUri} does not exist. * @throws IllegalStateException if the document cannot be restored (e.g., already * restored, or original parent is invalid). * @throws UnsupportedOperationException if the document provider does not support restoring. */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) @Nullable public static Uri restoreDocumentFromTrash(@NonNull ContentResolver content, @NonNull Uri sourceDocumentUri, @Nullable Uri targetParentDocumentUri) throws FileNotFoundException { try { final Bundle in = new Bundle(); in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri); in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri); final Bundle out = content.call(sourceDocumentUri.getAuthority(), METHOD_RESTORE_DOCUMENT_FROM_TRASH, null, in); return out.getParcelable(DocumentsContract.EXTRA_URI, android.net.Uri.class); } catch (Exception e) { Log.w(TAG, "Failed to restore document", e); rethrowIfNecessary(e); return null; } } /** * Ejects the given root. It throws {@link IllegalStateException} when ejection failed. * Loading core/java/android/provider/DocumentsProvider.java +105 −1 Original line number Diff line number Diff line Loading @@ -27,6 +27,8 @@ 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.METHOD_RESTORE_DOCUMENT_FROM_TRASH; import static android.provider.DocumentsContract.METHOD_TRASH_DOCUMENT; import static android.provider.DocumentsContract.buildDocumentUri; import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree; import static android.provider.DocumentsContract.buildTreeDocumentUri; Loading @@ -37,8 +39,10 @@ import static android.provider.DocumentsContract.isTreeUri; import android.Manifest; import android.annotation.CallSuper; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.AuthenticationRequiredException; import android.content.ClipDescription; import android.content.ContentProvider; Loading Loading @@ -153,6 +157,7 @@ public abstract class DocumentsProvider extends ContentProvider { private static final int MATCH_CHILDREN = 6; private static final int MATCH_DOCUMENT_TREE = 7; private static final int MATCH_CHILDREN_TREE = 8; private static final int MATCH_TRASH = 9; private String mAuthority; Loading Loading @@ -200,6 +205,7 @@ public abstract class DocumentsProvider extends ContentProvider { mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN); mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE); mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE); mMatcher.addURI(mAuthority, "trash", MATCH_TRASH); } /** Loading Loading @@ -309,6 +315,66 @@ public abstract class DocumentsProvider extends ContentProvider { throw new UnsupportedOperationException("Delete not supported"); } /** * Trash the requested document. * <p> * Upon trashing a document, the document will be marked as trashed. It will not be accessible * other then trash page until {@link #restoreDocumentFromTrash(String, String)}. * Any URI permission grants for the given document will be revoked. * * @param documentId the document to trash. * @return the document id of the trashed document or return {@code null} in case of failure. * @throws AuthenticationRequiredException If authentication is required from * the user (such as login credentials), but it is not guaranteed * that the client will handle this properly. */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) @Nullable @SuppressLint("OnNameExpected") public String trashDocument(@NonNull String documentId) throws FileNotFoundException { throw new UnsupportedOperationException("Trash not supported"); } /** * @return a cursor of trashed documents. This will be called for each * provider (for local just externalstorage provider) and combine the results. * @param projection list of {@link Document} columns to put into the * cursor. If {@code null} all supported columns should be * included. * @throws AuthenticationRequiredException If authentication is required from * the user (such as login credentials), but it is not guaranteed * that the client will handle this properly. */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) @Nullable @SuppressLint("OnNameExpected") public Cursor queryTrashDocuments(@Nullable String[] projection) throws FileNotFoundException { throw new UnsupportedOperationException("Query Trash not supported"); } /** * Restore the specified trash document. * <p> * Upon restoring a document, the document will be restored from trash to the * specified target parent document. * * @param documentId the document to restore. * @param targetParentDocumentId the target location to restore the document to. If * {@code null}, the restore path will be obtained * from the {@code documentId} * @return the document id of the restored document or return {@code null} in case of failure. * @throws AuthenticationRequiredException If authentication is required from * the user (such as login credentials), but it is not * guaranteed that the client will handle this properly. */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) @Nullable @SuppressLint("OnNameExpected") public String restoreDocumentFromTrash(@NonNull String documentId, @Nullable String targetParentDocumentId) throws FileNotFoundException { throw new UnsupportedOperationException("Restore not supported"); } /** * Copy the requested document or a document tree. * <p> Loading Loading @@ -916,6 +982,10 @@ public abstract class DocumentsProvider extends ContentProvider { case MATCH_RECENT: return queryRecentDocuments( getRootId(uri), projection, queryArgs, cancellationSignal); case MATCH_TRASH: if (Flags.enableDocumentsTrashApi()) { return queryTrashDocuments(projection); } case MATCH_SEARCH: return querySearchDocuments(getRootId(uri), projection, queryArgs); case MATCH_DOCUMENT: Loading Loading @@ -1293,9 +1363,43 @@ public abstract class DocumentsProvider extends ContentProvider { out.putParcelable(DocumentsContract.EXTRA_RESULT, path); } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) { return getDocumentMetadata(documentId); } else if (Flags.enableDocumentsTrashApi()) { if (METHOD_TRASH_DOCUMENT.equals(method)) { enforceWritePermissionInner(documentUri, getCallingAttributionSource()); final String newDocumentId = trashDocument(documentId); if (newDocumentId != null) { final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri, newDocumentId); out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); } // Document moved to trash, clean up any grants. revokeDocumentPermission(documentId); } else if (METHOD_RESTORE_DOCUMENT_FROM_TRASH.equals(method)) { enforceReadPermissionInner(documentUri, getCallingAttributionSource()); String targetId = null; if (extraTargetUri != null) { enforceWritePermissionInner(extraTargetUri, getCallingAttributionSource()); targetId = DocumentsContract.getDocumentId(extraTargetUri); } final String newDocumentId = restoreDocumentFromTrash(documentId, targetId); if (newDocumentId != null) { final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri, newDocumentId); out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); } } else { throw new UnsupportedOperationException("Method not supported " + method); } } else { throw new UnsupportedOperationException("Method not supported " + method); } return out; } Loading core/java/android/provider/flags.aconfig +9 −0 Original line number Diff line number Diff line Loading @@ -69,3 +69,12 @@ flag { description: "Feature flag for regional preferences APIs" bug: "370379000" } flag { name: "enable_documents_trash_api" is_exported: true namespace: "documentsui" is_fixed_read_only: true description: "This flag will enable documents trash APIs" bug: "409260986" } Loading
core/api/current.txt +8 −0 Original line number Diff line number Diff line Loading @@ -38147,6 +38147,7 @@ package android.provider { method public static android.net.Uri buildRootUri(String, String); method public static android.net.Uri buildRootsUri(String); method public static android.net.Uri buildSearchDocumentsUri(String, String, String); method @FlaggedApi("android.provider.enable_documents_trash_api") @NonNull public static android.net.Uri buildTrashDocumentsUri(@NonNull String); method public static android.net.Uri buildTreeDocumentUri(String, String); method @Nullable public static android.net.Uri copyDocument(@NonNull android.content.ContentResolver, @NonNull android.net.Uri, @NonNull android.net.Uri) throws java.io.FileNotFoundException; method @Nullable public static android.net.Uri createDocument(@NonNull android.content.ContentResolver, @NonNull android.net.Uri, @NonNull String, @NonNull String) throws java.io.FileNotFoundException; Loading @@ -38168,6 +38169,8 @@ package android.provider { method @Nullable public static android.net.Uri moveDocument(@NonNull android.content.ContentResolver, @NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.net.Uri) throws java.io.FileNotFoundException; method public static boolean removeDocument(@NonNull android.content.ContentResolver, @NonNull android.net.Uri, @NonNull android.net.Uri) throws java.io.FileNotFoundException; method @Nullable public static android.net.Uri renameDocument(@NonNull android.content.ContentResolver, @NonNull android.net.Uri, @NonNull String) throws java.io.FileNotFoundException; method @FlaggedApi("android.provider.enable_documents_trash_api") @Nullable public static android.net.Uri restoreDocumentFromTrash(@NonNull android.content.ContentResolver, @NonNull android.net.Uri, @Nullable android.net.Uri) throws java.io.FileNotFoundException; method @FlaggedApi("android.provider.enable_documents_trash_api") @Nullable public static android.net.Uri trashDocument(@NonNull android.content.ContentResolver, @NonNull android.net.Uri) throws java.io.FileNotFoundException; field public static final String ACTION_DOCUMENT_SETTINGS = "android.provider.action.DOCUMENT_SETTINGS"; field public static final String EXTRA_ERROR = "error"; field public static final String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF"; Loading Loading @@ -38208,8 +38211,10 @@ package android.provider { field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100 field public static final int FLAG_SUPPORTS_REMOVE = 1024; // 0x400 field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40 field @FlaggedApi("android.provider.enable_documents_trash_api") public static final int FLAG_SUPPORTS_RESTORE = 131072; // 0x20000 field public static final int FLAG_SUPPORTS_SETTINGS = 2048; // 0x800 field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 field @FlaggedApi("android.provider.enable_documents_trash_api") public static final int FLAG_SUPPORTS_TRASH = 65536; // 0x10000 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 field public static final int FLAG_VIRTUAL_DOCUMENT = 512; // 0x200 field public static final int FLAG_WEB_LINKABLE = 4096; // 0x1000 Loading Loading @@ -38282,9 +38287,12 @@ package android.provider { method public abstract android.database.Cursor queryRoots(String[]) throws java.io.FileNotFoundException; method public android.database.Cursor querySearchDocuments(String, String, String[]) throws java.io.FileNotFoundException; method @Nullable public android.database.Cursor querySearchDocuments(@NonNull String, @Nullable String[], @NonNull android.os.Bundle) throws java.io.FileNotFoundException; method @FlaggedApi("android.provider.enable_documents_trash_api") @Nullable public android.database.Cursor queryTrashDocuments(@Nullable String[]) throws java.io.FileNotFoundException; method public void removeDocument(String, String) throws java.io.FileNotFoundException; method public String renameDocument(String, String) throws java.io.FileNotFoundException; method @FlaggedApi("android.provider.enable_documents_trash_api") @Nullable public String restoreDocumentFromTrash(@NonNull String, @Nullable String) throws java.io.FileNotFoundException; method public final void revokeDocumentPermission(String); method @FlaggedApi("android.provider.enable_documents_trash_api") @Nullable public String trashDocument(@NonNull String) throws java.io.FileNotFoundException; method public final int update(android.net.Uri, android.content.ContentValues, String, String[]); }
core/java/android/provider/DocumentsContract.java +107 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.provider; import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkCollectionNotEmpty; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; Loading Loading @@ -581,6 +582,29 @@ public final class DocumentsContract { * @see #COLUMN_FLAGS */ public static final int FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE = 1 << 15; /** * Flag indicating that a document can be trashed. * * @see #COLUMN_FLAGS * @see DocumentsContract#trashDocument(ContentResolver, Uri) * @see DocumentsProvider#trashDocument(String) */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) public static final int FLAG_SUPPORTS_TRASH = 1 << 16; /** * Flag indicating that a document can be restored. * Only trashed documents can be restored * * @see #COLUMN_FLAGS * @see DocumentsContract#restoreDocumentFromTrash(ContentResolver, Uri, Uri) * @see DocumentsProvider#restoreDocumentFromTrash(String, String) */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) public static final int FLAG_SUPPORTS_RESTORE = 1 << 17; } /** Loading Loading @@ -875,6 +899,13 @@ public final class DocumentsContract { public static final String METHOD_CREATE_WEB_LINK_INTENT = "android:createWebLinkIntent"; /** {@hide} */ public static final String METHOD_GET_DOCUMENT_METADATA = "android:getDocumentMetadata"; /** {@hide} */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) public static final String METHOD_TRASH_DOCUMENT = "android:trashDocument"; /** {@hide} */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) public static final String METHOD_RESTORE_DOCUMENT_FROM_TRASH = "android:restoreDocumentFromTrash"; /** {@hide} */ public static final String EXTRA_PARENT_URI = "parentUri"; Loading @@ -888,6 +919,7 @@ public final class DocumentsContract { private static final String PATH_ROOT = "root"; private static final String PATH_RECENT = "recent"; private static final String PATH_TRASH = "trash"; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private static final String PATH_DOCUMENT = "document"; private static final String PATH_CHILDREN = "children"; Loading Loading @@ -935,6 +967,20 @@ public final class DocumentsContract { .appendPath(PATH_RECENT).build(); } /** * Returns URI representing the query trash documents of a specific document provider. * * @see DocumentsProvider#queryTrashDocuments(String[]) */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) @NonNull public static Uri buildTrashDocumentsUri(@NonNull String authority) { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority(authority).appendPath(PATH_TRASH).build(); } /** * Build URI representing access to descendant documents of the given * {@link Document#COLUMN_DOCUMENT_ID}. Loading Loading @@ -1560,6 +1606,67 @@ public final class DocumentsContract { } } /** * Trashes the given document. * * @param documentUri document with {@link Document#FLAG_SUPPORTS_TRASH} * @return the trashed document, or {@code null} if failed. * @throws FileNotFoundException if the documentUri does not exist. * @throws IllegalArgumentException if the document does not support trashing. * @throws UnsupportedOperationException if the document provider does not support trashing. */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) @Nullable public static Uri trashDocument(@NonNull ContentResolver content, @NonNull Uri documentUri) throws FileNotFoundException { try { final Bundle in = new Bundle(); in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); final Bundle out = content.call(documentUri.getAuthority(), METHOD_TRASH_DOCUMENT, null, in); return out.getParcelable(DocumentsContract.EXTRA_URI, android.net.Uri.class); } catch (Exception e) { Log.w(TAG, "Failed to trash document", e); rethrowIfNecessary(e); return null; } } /** * Restores a document from the trash. * * @param sourceDocumentUri trashed document to restore * @param targetParentDocumentUri parent document to restore the document to * @return the restored document, or {@code null} if failed. * @throws FileNotFoundException if the {@code sourceDocumentUri} does not exist or the * {@code targetParentDocumentUri} does not exist. * @throws IllegalStateException if the document cannot be restored (e.g., already * restored, or original parent is invalid). * @throws UnsupportedOperationException if the document provider does not support restoring. */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) @Nullable public static Uri restoreDocumentFromTrash(@NonNull ContentResolver content, @NonNull Uri sourceDocumentUri, @Nullable Uri targetParentDocumentUri) throws FileNotFoundException { try { final Bundle in = new Bundle(); in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri); in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri); final Bundle out = content.call(sourceDocumentUri.getAuthority(), METHOD_RESTORE_DOCUMENT_FROM_TRASH, null, in); return out.getParcelable(DocumentsContract.EXTRA_URI, android.net.Uri.class); } catch (Exception e) { Log.w(TAG, "Failed to restore document", e); rethrowIfNecessary(e); return null; } } /** * Ejects the given root. It throws {@link IllegalStateException} when ejection failed. * Loading
core/java/android/provider/DocumentsProvider.java +105 −1 Original line number Diff line number Diff line Loading @@ -27,6 +27,8 @@ 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.METHOD_RESTORE_DOCUMENT_FROM_TRASH; import static android.provider.DocumentsContract.METHOD_TRASH_DOCUMENT; import static android.provider.DocumentsContract.buildDocumentUri; import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree; import static android.provider.DocumentsContract.buildTreeDocumentUri; Loading @@ -37,8 +39,10 @@ import static android.provider.DocumentsContract.isTreeUri; import android.Manifest; import android.annotation.CallSuper; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.AuthenticationRequiredException; import android.content.ClipDescription; import android.content.ContentProvider; Loading Loading @@ -153,6 +157,7 @@ public abstract class DocumentsProvider extends ContentProvider { private static final int MATCH_CHILDREN = 6; private static final int MATCH_DOCUMENT_TREE = 7; private static final int MATCH_CHILDREN_TREE = 8; private static final int MATCH_TRASH = 9; private String mAuthority; Loading Loading @@ -200,6 +205,7 @@ public abstract class DocumentsProvider extends ContentProvider { mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN); mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE); mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE); mMatcher.addURI(mAuthority, "trash", MATCH_TRASH); } /** Loading Loading @@ -309,6 +315,66 @@ public abstract class DocumentsProvider extends ContentProvider { throw new UnsupportedOperationException("Delete not supported"); } /** * Trash the requested document. * <p> * Upon trashing a document, the document will be marked as trashed. It will not be accessible * other then trash page until {@link #restoreDocumentFromTrash(String, String)}. * Any URI permission grants for the given document will be revoked. * * @param documentId the document to trash. * @return the document id of the trashed document or return {@code null} in case of failure. * @throws AuthenticationRequiredException If authentication is required from * the user (such as login credentials), but it is not guaranteed * that the client will handle this properly. */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) @Nullable @SuppressLint("OnNameExpected") public String trashDocument(@NonNull String documentId) throws FileNotFoundException { throw new UnsupportedOperationException("Trash not supported"); } /** * @return a cursor of trashed documents. This will be called for each * provider (for local just externalstorage provider) and combine the results. * @param projection list of {@link Document} columns to put into the * cursor. If {@code null} all supported columns should be * included. * @throws AuthenticationRequiredException If authentication is required from * the user (such as login credentials), but it is not guaranteed * that the client will handle this properly. */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) @Nullable @SuppressLint("OnNameExpected") public Cursor queryTrashDocuments(@Nullable String[] projection) throws FileNotFoundException { throw new UnsupportedOperationException("Query Trash not supported"); } /** * Restore the specified trash document. * <p> * Upon restoring a document, the document will be restored from trash to the * specified target parent document. * * @param documentId the document to restore. * @param targetParentDocumentId the target location to restore the document to. If * {@code null}, the restore path will be obtained * from the {@code documentId} * @return the document id of the restored document or return {@code null} in case of failure. * @throws AuthenticationRequiredException If authentication is required from * the user (such as login credentials), but it is not * guaranteed that the client will handle this properly. */ @FlaggedApi(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) @Nullable @SuppressLint("OnNameExpected") public String restoreDocumentFromTrash(@NonNull String documentId, @Nullable String targetParentDocumentId) throws FileNotFoundException { throw new UnsupportedOperationException("Restore not supported"); } /** * Copy the requested document or a document tree. * <p> Loading Loading @@ -916,6 +982,10 @@ public abstract class DocumentsProvider extends ContentProvider { case MATCH_RECENT: return queryRecentDocuments( getRootId(uri), projection, queryArgs, cancellationSignal); case MATCH_TRASH: if (Flags.enableDocumentsTrashApi()) { return queryTrashDocuments(projection); } case MATCH_SEARCH: return querySearchDocuments(getRootId(uri), projection, queryArgs); case MATCH_DOCUMENT: Loading Loading @@ -1293,9 +1363,43 @@ public abstract class DocumentsProvider extends ContentProvider { out.putParcelable(DocumentsContract.EXTRA_RESULT, path); } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) { return getDocumentMetadata(documentId); } else if (Flags.enableDocumentsTrashApi()) { if (METHOD_TRASH_DOCUMENT.equals(method)) { enforceWritePermissionInner(documentUri, getCallingAttributionSource()); final String newDocumentId = trashDocument(documentId); if (newDocumentId != null) { final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri, newDocumentId); out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); } // Document moved to trash, clean up any grants. revokeDocumentPermission(documentId); } else if (METHOD_RESTORE_DOCUMENT_FROM_TRASH.equals(method)) { enforceReadPermissionInner(documentUri, getCallingAttributionSource()); String targetId = null; if (extraTargetUri != null) { enforceWritePermissionInner(extraTargetUri, getCallingAttributionSource()); targetId = DocumentsContract.getDocumentId(extraTargetUri); } final String newDocumentId = restoreDocumentFromTrash(documentId, targetId); if (newDocumentId != null) { final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri, newDocumentId); out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); } } else { throw new UnsupportedOperationException("Method not supported " + method); } } else { throw new UnsupportedOperationException("Method not supported " + method); } return out; } Loading
core/java/android/provider/flags.aconfig +9 −0 Original line number Diff line number Diff line Loading @@ -69,3 +69,12 @@ flag { description: "Feature flag for regional preferences APIs" bug: "370379000" } flag { name: "enable_documents_trash_api" is_exported: true namespace: "documentsui" is_fixed_read_only: true description: "This flag will enable documents trash APIs" bug: "409260986" }