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

Commit 5a305b41 authored by Ben Lin's avatar Ben Lin
Browse files

Prevent Drag n' Drop/Spring-load onto a selected folder.

Bug: 29538691
Change-Id: I01b1bb59ea816d6dc328dbf7830640442041bcfb
parent 1c72b109
Loading
Loading
Loading
Loading
+12 −7
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ import com.android.internal.annotations.VisibleForTesting;
import java.util.Timer;
import java.util.TimerTask;

import javax.annotation.Nullable;

/**
 * An {@link OnDragListener} that adds support for "spring loading views". Use this when you want
 * items to pop-open when user hovers on them during a drag n drop.
@@ -59,7 +61,7 @@ public class ItemDragListener<H extends DragHost> implements OnDragListener {
            case DragEvent.ACTION_DRAG_STARTED:
                return true;
            case DragEvent.ACTION_DRAG_ENTERED:
                handleEnteredEvent(v);
                handleEnteredEvent(v, event);
                return true;
            case DragEvent.ACTION_DRAG_LOCATION:
                handleLocationEvent(v, event.getX(), event.getY());
@@ -75,11 +77,12 @@ public class ItemDragListener<H extends DragHost> implements OnDragListener {
        return false;
    }

    private void handleEnteredEvent(View v) {
    private void handleEnteredEvent(View v, DragEvent event) {
        @Nullable TimerTask task = createOpenTask(v, event);
        if (task == null) {
            return;
        }
        mDragHost.setDropTargetHighlight(v, true);

        TimerTask task = createOpenTask(v);
        assert (task != null);
        v.setTag(R.id.drag_hovering_tag, task);
        mHoverTimer.schedule(task, SPRING_TIMEOUT);
    }
@@ -110,8 +113,10 @@ public class ItemDragListener<H extends DragHost> implements OnDragListener {
        return handleDropEventChecked(v, event);
    }

    @VisibleForTesting
    TimerTask createOpenTask(final View v) {
    /**
     * Sub-classes such as {@link DirectoryDragListener} can override this method and return null.
     */
    public @Nullable TimerTask createOpenTask(final View v, DragEvent event) {
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
+10 −0
Original line number Diff line number Diff line
@@ -21,6 +21,10 @@ import android.view.View;

import com.android.documentsui.ItemDragListener;

import java.util.TimerTask;

import javax.annotation.Nullable;

class DirectoryDragListener extends ItemDragListener<DirectoryFragment> {

    DirectoryDragListener(DirectoryFragment fragment) {
@@ -45,4 +49,10 @@ class DirectoryDragListener extends ItemDragListener<DirectoryFragment> {
    public boolean handleDropEventChecked(View v, DragEvent event) {
        return mDragHost.handleDropEvent(v, event);
    }

    @Override
    public @Nullable TimerTask createOpenTask(final View v, DragEvent event) {
        return mDragHost.shouldCopyTo(event.getLocalState(), v) ?
                super.createOpenTask(v, event) : null;
    }
}
 No newline at end of file
+20 −9
Original line number Diff line number Diff line
@@ -1350,7 +1350,6 @@ public class DirectoryFragment extends Fragment
        if (getModelId(view) != null) {
           activity.springOpenDirectory(getDestination(view));
        }

        activity.setRootsDrawerOpen(false);
    }

@@ -1363,13 +1362,7 @@ public class DirectoryFragment extends Fragment

        assert(DocumentClipper.getOpType(clipData) == FileOperationService.OPERATION_COPY);

        // Don't copy from the cwd into the cwd. Note: this currently doesn't work for
        // multi-window drag, because localState isn't carried over from one process to
        // another.
        Object src = event.getLocalState();
        DocumentInfo dst = getDestination(v);
        if (Objects.equals(src, dst)) {
            if (DEBUG) Log.d(TAG, "Drop target same as source. Ignoring.");
        if (!shouldCopyTo(event.getLocalState(), v)) {
            return false;
        }

@@ -1379,13 +1372,31 @@ public class DirectoryFragment extends Fragment
        // The localState could also be null for copying from Recents in single window
        // mode, but Recents doesn't offer this functionality (no directories).
        Metrics.logUserAction(getContext(),
                src == null ? Metrics.USER_ACTION_DRAG_N_DROP_MULTI_WINDOW
                event.getLocalState() == null ? Metrics.USER_ACTION_DRAG_N_DROP_MULTI_WINDOW
                        : Metrics.USER_ACTION_DRAG_N_DROP);

        DocumentInfo dst = getDestination(v);
        mClipper.copyFromClipData(dst, getDisplayState().stack, clipData, activity.fileOpCallback);
        return true;
    }

    // Don't copy from the cwd into a provided list of prohibited directories. (ie. into cwd, into
    // a selected directory). Note: this currently doesn't work for multi-window drag, because
    // localState isn't carried over from one process to another.
    boolean shouldCopyTo(Object dragLocalState, View destinationView) {
        if (dragLocalState == null || !(dragLocalState instanceof List<?>)) {
            if (DEBUG) Log.d(TAG, "Invalid local state object. Will allow copy.");
            return true;
        }
        DocumentInfo dst = getDestination(destinationView);
        List<?> src = (List<?>) dragLocalState;
        if (src.contains(dst)) {
            if (DEBUG) Log.d(TAG, "Drop target same as source. Ignoring.");
            return false;
        }
        return true;
    }

    private DocumentInfo getDestination(View v) {
        String id = getModelId(v);
        if (id != null) {
+10 −3
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.services.FileOperationService.OpType;

import java.util.List;
import java.util.function.Function;

import javax.annotation.Nullable;
@@ -70,6 +71,7 @@ interface DragStartListener {
        private final Function<View, String> mIdFinder;
        private final ClipDataFactory mClipFactory;
        private final Function<Selection, DragShadowBuilder> mShadowFactory;
        private Function<Selection, List<DocumentInfo>> mDocsConverter;

        // use DragStartListener.create
        @VisibleForTesting
@@ -78,6 +80,7 @@ interface DragStartListener {
                MultiSelectManager selectionMgr,
                ViewFinder viewFinder,
                Function<View, String> idFinder,
                Function<Selection, List<DocumentInfo>> docsConverter,
                ClipDataFactory clipFactory,
                Function<Selection, DragShadowBuilder> shadowFactory) {

@@ -85,6 +88,7 @@ interface DragStartListener {
            mSelectionMgr = selectionMgr;
            mViewFinder = viewFinder;
            mIdFinder = idFinder;
            mDocsConverter = docsConverter;
            mClipFactory = clipFactory;
            mShadowFactory = shadowFactory;
        }
@@ -133,6 +137,8 @@ interface DragStartListener {
                mSelectionMgr.getSelection(selection);
            }

            final List<DocumentInfo> invalidDest = mDocsConverter.apply(selection);
            invalidDest.add(mState.stack.peek());
            // NOTE: Preparation of the ClipData object can require a lot of time
            // and ideally should be done in the background. Unfortunately
            // the current code layout and framework assumptions don't support
@@ -143,7 +149,7 @@ interface DragStartListener {
                            selection,
                            FileOperationService.OPERATION_COPY),
                    mShadowFactory.apply(selection),
                    mState.stack.peek(),
                    invalidDest,
                    View.DRAG_FLAG_GLOBAL
                            | View.DRAG_FLAG_GLOBAL_URI_READ
                            | View.DRAG_FLAG_GLOBAL_URI_WRITE);
@@ -159,10 +165,10 @@ interface DragStartListener {
                View view,
                ClipData data,
                DragShadowBuilder shadowBuilder,
                DocumentInfo currentDirectory,
                Object localState,
                int flags) {

            view.startDragAndDrop(data, shadowBuilder, currentDirectory, flags);
            view.startDragAndDrop(data, shadowBuilder, localState, flags);
        }
    }

@@ -185,6 +191,7 @@ interface DragStartListener {
                selectionMgr,
                viewFinder,
                idFinder,
                model::getDocuments,
                (Selection selection, @OpType int operationType) -> {
                    return clipper.getClipDataForDocuments(
                            model::getItemUri,
+2 −2
Original line number Diff line number Diff line
@@ -177,8 +177,8 @@ public class ItemDragListenerTest {
        }

        @Override
        public TimerTask createOpenTask(View v) {
            TimerTask task = super.createOpenTask(v);
        public TimerTask createOpenTask(View v, DragEvent event) {
            TimerTask task = super.createOpenTask(v, event);
            TestTimer.Task testTask = new TestTimer.Task(task);

            return testTask;
Loading