Prevent Drag n' Drop/Spring-load onto a selected folder.
Bug: 29538691
Change-Id: I01b1bb59ea816d6dc328dbf7830640442041bcfb
diff --git a/src/com/android/documentsui/ItemDragListener.java b/src/com/android/documentsui/ItemDragListener.java
index 152d3a0..2d67ff9 100644
--- a/src/com/android/documentsui/ItemDragListener.java
+++ b/src/com/android/documentsui/ItemDragListener.java
@@ -29,6 +29,8 @@
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 @@
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 @@
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 @@
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() {
diff --git a/src/com/android/documentsui/dirlist/DirectoryDragListener.java b/src/com/android/documentsui/dirlist/DirectoryDragListener.java
index f0a7aae..8b0a663 100644
--- a/src/com/android/documentsui/dirlist/DirectoryDragListener.java
+++ b/src/com/android/documentsui/dirlist/DirectoryDragListener.java
@@ -21,6 +21,10 @@
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 @@
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
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 367a134..7982e5c 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -1350,7 +1350,6 @@
if (getModelId(view) != null) {
activity.springOpenDirectory(getDestination(view));
}
-
activity.setRootsDrawerOpen(false);
}
@@ -1363,13 +1362,7 @@
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 @@
// 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) {
diff --git a/src/com/android/documentsui/dirlist/DragStartListener.java b/src/com/android/documentsui/dirlist/DragStartListener.java
index 563e3a8..8690600 100644
--- a/src/com/android/documentsui/dirlist/DragStartListener.java
+++ b/src/com/android/documentsui/dirlist/DragStartListener.java
@@ -34,6 +34,7 @@
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 @@
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 @@
MultiSelectManager selectionMgr,
ViewFinder viewFinder,
Function<View, String> idFinder,
+ Function<Selection, List<DocumentInfo>> docsConverter,
ClipDataFactory clipFactory,
Function<Selection, DragShadowBuilder> shadowFactory) {
@@ -85,6 +88,7 @@
mSelectionMgr = selectionMgr;
mViewFinder = viewFinder;
mIdFinder = idFinder;
+ mDocsConverter = docsConverter;
mClipFactory = clipFactory;
mShadowFactory = shadowFactory;
}
@@ -133,6 +137,8 @@
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 @@
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 @@
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 @@
selectionMgr,
viewFinder,
idFinder,
+ model::getDocuments,
(Selection selection, @OpType int operationType) -> {
return clipper.getClipDataForDocuments(
model::getItemUri,