Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 18746e28 authored by Himanshu Arora's avatar Himanshu Arora Committed by Android (Google) Code Review
Browse files

Merge "Add APIs for documents trash flow" into main

parents 9ad194d4 58fb7e95
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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";
@@ -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
@@ -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[]);
  }
+107 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
    }

    /**
@@ -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";
@@ -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";
@@ -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}.
@@ -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.
     *
+105 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;

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

    /**
@@ -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>
@@ -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:
@@ -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;
    }
+9 −0
Original line number Diff line number Diff line
@@ -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"
}