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

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

Merge "Revert^2 "Implement document trash flow APIs"" into main

parents c52e0bbf 7b84af5c
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -132,6 +132,7 @@ aconfig_declarations_group {
        "libcore_exported_aconfig_flags_lib",
        "libcore_exported_aconfig_flags_lib",
        "libcore_readonly_aconfig_flags_lib",
        "libcore_readonly_aconfig_flags_lib",
        "libgui_flags_java_lib",
        "libgui_flags_java_lib",
        "mediaprovider_exported_aconfig_flag_lib",
        "networksecurity_exported_aconfig_flags_lib",
        "networksecurity_exported_aconfig_flags_lib",
        "power_flags_lib",
        "power_flags_lib",
        "sdk_sandbox_exported_flags_lib",
        "sdk_sandbox_exported_flags_lib",
+166 −7
Original line number Original line Diff line number Diff line
@@ -16,6 +16,10 @@


package com.android.internal.content;
package com.android.internal.content;


import static android.provider.Flags.enableDocumentsTrashApi;

import static com.android.providers.media.flags.Flags.enableTrashAndRestoreByFilePathApi;

import android.annotation.CallSuper;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
@@ -71,6 +75,8 @@ import java.util.Locale;
import java.util.Queue;
import java.util.Queue;
import java.util.Set;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
/**
 * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local
 * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local
@@ -92,6 +98,19 @@ public abstract class FileSystemProvider extends DocumentsProvider {
    private static final int DEFAULT_SEARCH_RESULT_LIMIT = 23;
    private static final int DEFAULT_SEARCH_RESULT_LIMIT = 23;
    private static final int MAX_SEARCH_RESULT_LIMIT = 1000;
    private static final int MAX_SEARCH_RESULT_LIMIT = 1000;


    /**
     * File prefix indicating that the file {@link MediaStore.MediaColumns#IS_TRASHED}.
     */
    protected static final String PREFIX_TRASHED = "trashed";

    /**
     * Default directory for trashed items
     */
    protected static final String DIRECTORY_TRASH_STORAGE = ".trash-storage";

    private static final Pattern PATTERN_EXPIRES_FILE = Pattern.compile(
            "(?i)^\\.(pending|trashed)-(\\d+)-([^/]+)$");

    private static String joinNewline(String... args) {
    private static String joinNewline(String... args) {
        return TextUtils.join("\n", args);
        return TextUtils.join("\n", args);
    }
    }
@@ -609,6 +628,117 @@ public abstract class FileSystemProvider extends DocumentsProvider {
        return DocumentsContract.openImageThumbnail(file);
        return DocumentsContract.openImageThumbnail(file);
    }
    }


    @Nullable
    @Override
    public String trashDocument(@NonNull String documentId)
            throws FileNotFoundException {
        if (!enableTrashAndRestoreByFilePathApi()) {
            throw new UnsupportedOperationException(
                    "MediaStore feature for trash is not supported");
        }

        File file = getFileForDocId(documentId);
        if (!file.exists()) {
            throw new FileNotFoundException("File does not exist for " + documentId);
        }

        String trashedPath = MediaStore.trashFile(getContext().getContentResolver(),
                file.getPath());
        File trashedFile = new File(trashedPath);
        final String trashedDocId = getDocIdForFile(trashedFile);
        onDocIdChanged(documentId);
        onDocIdDeleted(documentId, /* shouldRevokeUriPermission */ true);
        onDocIdChanged(trashedDocId);
        return trashedDocId;
    }

    protected final Cursor queryTrashDocuments(File parent, String[] projection)
            throws FileNotFoundException {
        MatrixCursor result = new MatrixCursor(resolveProjection(projection));
        includeTrashFiles(result, parent);
        // include MediaStore trashed files which are not in .trash-storage location
        includeMediaStoreTrashFiles(result);
        return result;
    }

    @Nullable
    @Override
    public String restoreDocumentFromTrash(@NonNull String documentId, @Nullable String targetId)
            throws FileNotFoundException {
        if (!enableTrashAndRestoreByFilePathApi()) {
            throw new UnsupportedOperationException(
                    "MediaStore feature for trash is not supported");
        }

        File file = getFileForDocId(documentId);
        if (!file.exists()) {
            throw new FileNotFoundException("File does not exist for " + documentId);
        }

        if (!isTrashFile(file)) {
            throw new IllegalArgumentException("DocumentId represents a non-trashed file");
        }

        String targetPath = null;
        if (targetId != null) {
            File targetFile = getFileForDocId(targetId);
            if (targetFile != null) {
                targetPath = targetFile.getAbsolutePath();
            }
        }
        String restoredPath = MediaStore.restoreFileFromTrash(getContext().getContentResolver(),
                file.getPath(), targetPath);

        File restoredFile = new File(restoredPath);
        final String restoredDocId = getDocIdForFile(restoredFile);
        onDocIdChanged(documentId);
        onDocIdChanged(restoredDocId);

        return restoredDocId;
    }


    private boolean isTrashFile(File file) {
        final Matcher matcher = PATTERN_EXPIRES_FILE.matcher(file.getName());
        return matcher.matches() && matcher.group(1).equals(PREFIX_TRASHED);
    }

    private void includeTrashFiles(MatrixCursor result, File parent) throws FileNotFoundException  {
        for (File file : parent.listFiles()) {
            if (isTrashFile(file)) {
                includeFile(result, null, file);
                continue;
            }
            if (file.isDirectory()) {
                includeTrashFiles(result, file);
            }
        }
    }

    private void includeMediaStoreTrashFiles(MatrixCursor result)
            throws FileNotFoundException {
        final Uri uri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL);
        final Bundle queryArgs = new Bundle();
        queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY);
        queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
                MediaStore.MediaColumns.RELATIVE_PATH + " NOT LIKE ?");
        queryArgs.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS,
                new String[]{DIRECTORY_TRASH_STORAGE + "/%"});
        String[] projection = new String[]{MediaStore.Files.FileColumns.DATA};

        try (Cursor cursor = getContext().getContentResolver().query(uri, projection,
                queryArgs, null)) {
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    final String data = cursor.getString(
                            cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA));
                    File file = new File(data);
                    includeFile(result, null, file);
                }
            }
        }
    }

    protected RowBuilder includeFile(final MatrixCursor result, String docId, File file)
    protected RowBuilder includeFile(final MatrixCursor result, String docId, File file)
            throws FileNotFoundException {
            throws FileNotFoundException {
        final String[] columns = result.getColumnNames();
        final String[] columns = result.getColumnNames();
@@ -627,17 +757,29 @@ public abstract class FileSystemProvider extends DocumentsProvider {
        final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS);
        final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS);
        if (flagIndex != -1) {
        if (flagIndex != -1) {
            final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR);
            final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR);
            boolean isTrashedFile = isTrashFile(file);
            int flags = 0;
            int flags = 0;
            if (file.canWrite()) {
            if (file.canWrite()) {
                flags |= Document.FLAG_SUPPORTS_DELETE;
                flags |= Document.FLAG_SUPPORTS_DELETE;
                if (!isTrashedFile) {
                    flags |= Document.FLAG_SUPPORTS_RENAME;
                    flags |= Document.FLAG_SUPPORTS_RENAME;
                    flags |= Document.FLAG_SUPPORTS_MOVE;
                    flags |= Document.FLAG_SUPPORTS_MOVE;

                    if (isDir) {
                    if (isDir) {
                        flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
                        flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
                    } else {
                    } else {
                        flags |= Document.FLAG_SUPPORTS_WRITE;
                        flags |= Document.FLAG_SUPPORTS_WRITE;
                    }
                    }
                }
                }
            }

            if (enableDocumentsTrashApi()) {
                if (isTrashFile(file)) {
                    flags |= Document.FLAG_SUPPORTS_RESTORE;
                } else if (isTrashSupported(file)) {
                    flags |= Document.FLAG_SUPPORTS_TRASH;
                }
            }


            if (isDir && shouldBlockDirectoryFromTree(docId)) {
            if (isDir && shouldBlockDirectoryFromTree(docId)) {
                flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
                flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
@@ -655,7 +797,13 @@ public abstract class FileSystemProvider extends DocumentsProvider {


        final int displayNameIndex = ArrayUtils.indexOf(columns, Document.COLUMN_DISPLAY_NAME);
        final int displayNameIndex = ArrayUtils.indexOf(columns, Document.COLUMN_DISPLAY_NAME);
        if (displayNameIndex != -1) {
        if (displayNameIndex != -1) {
            row.add(displayNameIndex, file.getName());
            String name = file.getName();
            final Matcher matcher = PATTERN_EXPIRES_FILE.matcher(name);
            if (matcher.matches() && matcher.group(1).equals(PREFIX_TRASHED)) {
                // .trashed-<timestamp>-<name>
                name = matcher.group(3);
            }
            row.add(displayNameIndex, name);
        }
        }


        final int lastModifiedIndex = ArrayUtils.indexOf(columns, Document.COLUMN_LAST_MODIFIED);
        final int lastModifiedIndex = ArrayUtils.indexOf(columns, Document.COLUMN_LAST_MODIFIED);
@@ -686,6 +834,17 @@ public abstract class FileSystemProvider extends DocumentsProvider {
        return false;
        return false;
    }
    }


    /**
     * Some providers may want to restrict access to certain directories and files,
     * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for
     * privacy reasons.
     * Such providers should override this method.
     */
    protected boolean isTrashSupported(@NonNull File document)
            throws FileNotFoundException {
        return false;
    }

    /**
    /**
     * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of
     * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of
     * a {@link String} {@code documentId}.
     * a {@link String} {@code documentId}.
+30 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.externalstorage;
package com.android.externalstorage;



import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.app.usage.StorageStatsManager;
import android.app.usage.StorageStatsManager;
@@ -638,6 +639,35 @@ public class ExternalStorageProvider extends FileSystemProvider {
        return result;
        return result;
    }
    }


    @Override
    protected boolean isTrashSupported(File file) {
        try {
            String documentId = getDocIdForFile(file);
            // Trash not supported on USB devices
            if (isOnRemovableUsbStorage(documentId)) {
                return false;
            }

            final RootInfo root = getRootFromDocId(documentId);
            final String canonicalPath = getPathFromDocId(documentId);
            return !isRestrictedPath(root.rootId, canonicalPath);
        } catch (Exception e) {
            return false;
        }
    }

    @Nullable
    @Override
    public Cursor queryTrashDocuments(String[] projection) throws FileNotFoundException {
        if (!mRoots.containsKey(ROOT_ID_PRIMARY_EMULATED)) {
            return null;
        }

        RootInfo rootInfo = mRoots.get(ROOT_ID_PRIMARY_EMULATED);
        File trashDir = new File(rootInfo.path, DIRECTORY_TRASH_STORAGE);
        return queryTrashDocuments(trashDir, projection);
    }

    @Override
    @Override
    public Path findDocumentPath(@Nullable String parentDocId, String childDocId)
    public Path findDocumentPath(@Nullable String parentDocId, String childDocId)
            throws FileNotFoundException {
            throws FileNotFoundException {
+4 −0
Original line number Original line Diff line number Diff line
@@ -50,6 +50,10 @@ public class CleanupTemporaryFilesRule implements TestRule {
     * @param directory the path to start from
     * @param directory the path to start from
     */
     */
    public static void removeFilesRecursively(File directory) {
    public static void removeFilesRecursively(File directory) {
        if (directory == null || !directory.exists() || directory.listFiles().length == 0) {
            return;
        }

        for (File childFile : directory.listFiles()) {
        for (File childFile : directory.listFiles()) {
            if (childFile.isDirectory()) {
            if (childFile.isDirectory()) {
                removeFilesRecursively(childFile);
                removeFilesRecursively(childFile);
+271 −236

File changed.

Preview size limit exceeded, changes collapsed.

Loading