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

Commit 14b943af authored by Shawn Lin's avatar Shawn Lin Committed by Android (Google) Code Review
Browse files

Merge "Support delete/undo in the File browser mode"

parents 80d69472 ea3de8be
Loading
Loading
Loading
Loading
+0 −2
Original line number Original line Diff line number Diff line
@@ -21,6 +21,4 @@ package com.android.documentsui;
public interface ActionModeAddons {
public interface ActionModeAddons {


    void finishActionMode();
    void finishActionMode();

    void finishOnConfirmed(int code);
}
}
+0 −9
Original line number Original line Diff line number Diff line
@@ -29,8 +29,6 @@ import android.view.MenuItem;
import android.view.View;
import android.view.View;


import com.android.documentsui.MenuManager.SelectionDetails;
import com.android.documentsui.MenuManager.SelectionDetails;
import com.android.documentsui.base.ConfirmationCallback;
import com.android.documentsui.base.ConfirmationCallback.Result;
import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.Menus;
import com.android.documentsui.base.Menus;
import com.android.documentsui.selection.Selection;
import com.android.documentsui.selection.Selection;
@@ -185,13 +183,6 @@ public class ActionModeController extends SelectionObserver
        }
        }
    }
    }


    @Override
    public void finishOnConfirmed(@Result int code) {
        if (code == ConfirmationCallback.CONFIRM) {
            finishActionMode();
        }
    }

    public ActionModeController reset(
    public ActionModeController reset(
            SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker) {
            SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker) {
        assert(mActionMode == null);
        assert(mActionMode == null);
+3 −1
Original line number Original line Diff line number Diff line
@@ -318,6 +318,7 @@ public final class Metrics {
    public static final int USER_ACTION_EXTRACT_TO = 28;
    public static final int USER_ACTION_EXTRACT_TO = 28;
    public static final int USER_ACTION_VIEW_IN_APPLICATION = 29;
    public static final int USER_ACTION_VIEW_IN_APPLICATION = 29;
    public static final int USER_ACTION_INSPECTOR = 30;
    public static final int USER_ACTION_INSPECTOR = 30;
    public static final int USER_ACTION_UNDO_DELETE = 31;


    @IntDef(flag = false, value = {
    @IntDef(flag = false, value = {
            USER_ACTION_OTHER,
            USER_ACTION_OTHER,
@@ -349,7 +350,8 @@ public final class Metrics {
            USER_ACTION_COMPRESS,
            USER_ACTION_COMPRESS,
            USER_ACTION_EXTRACT_TO,
            USER_ACTION_EXTRACT_TO,
            USER_ACTION_VIEW_IN_APPLICATION,
            USER_ACTION_VIEW_IN_APPLICATION,
            USER_ACTION_INSPECTOR
            USER_ACTION_INSPECTOR,
            USER_ACTION_UNDO_DELETE
    })
    })
    @Retention(RetentionPolicy.SOURCE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface UserAction {}
    public @interface UserAction {}
+64 −9
Original line number Original line Diff line number Diff line
@@ -32,7 +32,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.VisibleForTesting;
import android.util.Log;
import android.util.Log;


import com.android.documentsui.DirectoryResult;
import com.android.documentsui.base.DocumentFilters;
import com.android.documentsui.base.DocumentFilters;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.EventListener;
import com.android.documentsui.base.EventListener;
@@ -45,6 +44,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Set;
import java.util.Set;
@@ -73,6 +73,7 @@ public class Model {
    private @Nullable Cursor mCursor;
    private @Nullable Cursor mCursor;
    private int mCursorCount;
    private int mCursorCount;
    private String mIds[] = new String[0];
    private String mIds[] = new String[0];
    private Set<Selection> mDocumentsToBeDeleted = new HashSet<>();


    public Model(Features features) {
    public Model(Features features) {
        mFeatures = features;
        mFeatures = features;
@@ -109,13 +110,13 @@ public class Model {
        doc = null;
        doc = null;
        mIsLoading = false;
        mIsLoading = false;
        mFileNames.clear();
        mFileNames.clear();
        mDocumentsToBeDeleted.clear();
        notifyUpdateListeners();
        notifyUpdateListeners();
    }
    }


    @VisibleForTesting
    @VisibleForTesting
    protected void update(DirectoryResult result) {
    protected void update(DirectoryResult result) {
        assert(result != null);
        assert(result != null);

        if (DEBUG) Log.i(TAG, "Updating model with new result set.");
        if (DEBUG) Log.i(TAG, "Updating model with new result set.");


        if (result.exception != null) {
        if (result.exception != null) {
@@ -141,9 +142,56 @@ public class Model {
        notifyUpdateListeners();
        notifyUpdateListeners();
    }
    }


    public void markDocumentsToBeDeleted(Selection selection) {
        if (mDocumentsToBeDeleted.contains(selection)) {
            return;
        }
        mDocumentsToBeDeleted.add(selection);
        updateModelData();
        notifyUpdateListeners();
    }

    public void restoreDocumentsToBeDeleted(Selection selection) {
        if (!mDocumentsToBeDeleted.contains(selection)) {
            return;
        }
        mDocumentsToBeDeleted.remove(selection);
        updateModelData();
        notifyUpdateListeners();
    }

    private boolean isDocumentToBeDeleted(String id) {
        for (Selection s : mDocumentsToBeDeleted) {
            if (s.contains(id)) {
                return true;
            }
        }
        return false;
    }

    private void updateDocumentsToBeDeleted() {
        for (Iterator<Selection> i = mDocumentsToBeDeleted.iterator(); i.hasNext();) {
            Selection selection = i.next();
            for (String id : selection) {
                if (!mPositions.containsKey(id)) {
                    i.remove();
                    break;
                }
            }
        }
    }

    private int getDocumentsToBeDeletedCount() {
        int count = 0;
        for (Selection s : mDocumentsToBeDeleted) {
            count += s.size();
        }
        return count;
    }

    @VisibleForTesting
    @VisibleForTesting
    public int getItemCount() {
    public int getItemCount() {
        return mCursorCount;
        return mCursorCount - getDocumentsToBeDeletedCount();
    }
    }


    /**
    /**
@@ -151,9 +199,10 @@ public class Model {
     * according to the current sort order.
     * according to the current sort order.
     */
     */
    private void updateModelData() {
    private void updateModelData() {
        mIds = new String[mCursorCount];
        mFileNames.clear();
        mFileNames.clear();
        mCursor.moveToPosition(-1);
        mCursor.moveToPosition(-1);
        mPositions.clear();
        String[] tmpIds = new String[mCursorCount];
        for (int pos = 0; pos < mCursorCount; ++pos) {
        for (int pos = 0; pos < mCursorCount; ++pos) {
            if (!mCursor.moveToNext()) {
            if (!mCursor.moveToNext()) {
                Log.e(TAG, "Fail to move cursor to next pos: " + pos);
                Log.e(TAG, "Fail to move cursor to next pos: " + pos);
@@ -164,18 +213,24 @@ public class Model {
            // If the cursor is a merged cursor over multiple authorities, then prefix the ids
            // If the cursor is a merged cursor over multiple authorities, then prefix the ids
            // with the authority to avoid collisions.
            // with the authority to avoid collisions.
            if (mCursor instanceof MergeCursor) {
            if (mCursor instanceof MergeCursor) {
                mIds[pos] = getCursorString(mCursor, RootCursorWrapper.COLUMN_AUTHORITY)
                tmpIds[pos] = getCursorString(mCursor, RootCursorWrapper.COLUMN_AUTHORITY)
                        + "|" + getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
                        + "|" + getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
            } else {
            } else {
                mIds[pos] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
                tmpIds[pos] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
            }
            }
            mPositions.put(tmpIds[pos], pos);
            mFileNames.add(getCursorString(mCursor, Document.COLUMN_DISPLAY_NAME));
            mFileNames.add(getCursorString(mCursor, Document.COLUMN_DISPLAY_NAME));
        }
        }


        // Populate the positions.
        updateDocumentsToBeDeleted();
        mPositions.clear();

        mIds = new String[mCursorCount - getDocumentsToBeDeletedCount()];
        int index = 0;
        for (int i = 0; i < mCursorCount; ++i) {
        for (int i = 0; i < mCursorCount; ++i) {
            mPositions.put(mIds[i], i);
            if (!isDocumentToBeDeleted(tmpIds[i])) {
                mIds[index] = tmpIds[i];
                index++;
            }
        }
        }
    }
    }


+61 −38
Original line number Original line Diff line number Diff line
@@ -25,11 +25,11 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.Intent;
import android.net.Uri;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract;
import android.text.TextUtils;
import android.text.TextUtils;
import android.util.Log;
import android.util.Log;
import android.view.DragEvent;
import android.view.DragEvent;
import android.view.View;


import com.android.documentsui.AbstractActionHandler;
import com.android.documentsui.AbstractActionHandler;
import com.android.documentsui.ActionModeAddons;
import com.android.documentsui.ActionModeAddons;
@@ -42,8 +42,6 @@ import com.android.documentsui.Metrics;
import com.android.documentsui.Model;
import com.android.documentsui.Model;
import com.android.documentsui.R;
import com.android.documentsui.R;
import com.android.documentsui.TimeoutTask;
import com.android.documentsui.TimeoutTask;
import com.android.documentsui.base.ConfirmationCallback;
import com.android.documentsui.base.ConfirmationCallback.Result;
import com.android.documentsui.base.DebugFlags;
import com.android.documentsui.base.DebugFlags;
import com.android.documentsui.base.DocumentFilters;
import com.android.documentsui.base.DocumentFilters;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentInfo;
@@ -69,11 +67,15 @@ import com.android.documentsui.services.FileOperation;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.services.FileOperations;
import com.android.documentsui.services.FileOperations;
import com.android.documentsui.ui.DialogController;
import com.android.documentsui.ui.DialogController;
import com.android.documentsui.ui.Snackbars;

import androidx.annotation.VisibleForTesting;
import androidx.annotation.VisibleForTesting;
import android.support.design.widget.Snackbar;


import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;
import java.util.function.Consumer;


import javax.annotation.Nullable;
import javax.annotation.Nullable;


@@ -93,6 +95,8 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa
    private final DragAndDropManager mDragAndDropManager;
    private final DragAndDropManager mDragAndDropManager;
    private final Model mModel;
    private final Model mModel;


    private Snackbar mDeletionSnackbar;

    ActionHandler(
    ActionHandler(
            T activity,
            T activity,
            State state,
            State state,
@@ -301,17 +305,6 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa


        final @Nullable DocumentInfo srcParent = mState.stack.peek();
        final @Nullable DocumentInfo srcParent = mState.stack.peek();


        // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
        List<DocumentInfo> docs = mModel.getDocuments(selection);

        ConfirmationCallback result = (@Result int code) -> {
            // share the news with our caller, be it good or bad.
            mActionModeAddons.finishOnConfirmed(code);

            if (code != ConfirmationCallback.CONFIRM) {
                return;
            }

        UrisSupplier srcs;
        UrisSupplier srcs;
        try {
        try {
            srcs = UrisSupplier.create(
            srcs = UrisSupplier.create(
@@ -326,7 +319,16 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa
                    selection.size());
                    selection.size());
            return;
            return;
        }
        }

        mModel.markDocumentsToBeDeleted(selection);
        Consumer<View> action = v -> {
            Metrics.logUserAction(mActivity, Metrics.USER_ACTION_UNDO_DELETE);
            mModel.restoreDocumentsToBeDeleted(selection);
        };
        Snackbar.Callback callback = new Snackbar.Callback() {
            @Override
            public void onDismissed(Snackbar snackbar, int event) {
                super.onDismissed(snackbar, event);
                if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {
                    FileOperation operation = new FileOperation.Builder()
                    FileOperation operation = new FileOperation.Builder()
                            .withOpType(FileOperationService.OPERATION_DELETE)
                            .withOpType(FileOperationService.OPERATION_DELETE)
                            .withDestination(mState.stack)
                            .withDestination(mState.stack)
@@ -334,11 +336,26 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa
                            .withSrcParent(srcParent == null ? null : srcParent.derivedUri)
                            .withSrcParent(srcParent == null ? null : srcParent.derivedUri)
                            .build();
                            .build();


            FileOperations.start(mActivity, operation, mDialogs::showFileOperationStatus,
                    FileOperations.start(mActivity, operation, null,
                            FileOperations.createJobId());
                            FileOperations.createJobId());
                }
                if (mDeletionSnackbar == snackbar) {
                    mDeletionSnackbar = null;
                }
            }
        };
        };
        mDeletionSnackbar = showDeletionSnackbar(mActivity, selection.size(), action, callback);
    }

    public Snackbar showDeletionSnackbar(Activity activity, int docCount, Consumer<View> action,
                               Snackbar.Callback callback) {
        return Snackbars.showDelete(mActivity, docCount, action, callback);
    }


        mDialogs.confirmDelete(docs, result);
    public void dismissDeletionSnackBar() {
        if (mDeletionSnackbar != null) {
            mDeletionSnackbar.dismiss();
        }
    }
    }


    @Override
    @Override
@@ -393,6 +410,12 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa
        mActivity.startActivity(chooserIntent);
        mActivity.startActivity(chooserIntent);
    }
    }


    @Override
    public void loadDocumentsForCurrentStack() {
        dismissDeletionSnackBar();
        super.loadDocumentsForCurrentStack();
    }

    @Override
    @Override
    public void initLocation(Intent intent) {
    public void initLocation(Intent intent) {
        assert(intent != null);
        assert(intent != null);
Loading