Add Drag and Drop states to RootList.
Also, changed Drag Shadow to 100% opaque.
Bug: 32772075
Change-Id: I8629e4c1caed3cef6460866e5fd39f14801ccbb4
diff --git a/src/com/android/documentsui/DragAndDropHelper.java b/src/com/android/documentsui/DragAndDropHelper.java
new file mode 100644
index 0000000..1b634c3
--- /dev/null
+++ b/src/com/android/documentsui/DragAndDropHelper.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import static com.android.documentsui.base.Shared.DEBUG;
+
+import android.util.Log;
+
+import com.android.documentsui.base.DocumentInfo;
+
+import java.util.List;
+
+/**
+ * A helper class for drag and drop operations
+ */
+public final class DragAndDropHelper {
+
+ private static final String TAG = "DragAndDropHelper";
+
+ private DragAndDropHelper() {}
+
+ /**
+ * Helper method to see whether an item can be dropped/copied into a particular destination.
+ * 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.
+ */
+ public static boolean canCopyTo(Object dragLocalState, DocumentInfo dst) {
+ if (dragLocalState == null || !(dragLocalState instanceof List<?>)) {
+ if (DEBUG) Log.d(TAG, "Invalid local state object. Will allow copy.");
+ return true;
+ }
+ List<?> src = (List<?>) dragLocalState;
+ if (src.contains(dst)) {
+ if (DEBUG) Log.d(TAG, "Drop target same as source. Ignoring.");
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/documentsui/DrawerController.java b/src/com/android/documentsui/DrawerController.java
index 12f00ee..3bf4bf9 100644
--- a/src/com/android/documentsui/DrawerController.java
+++ b/src/com/android/documentsui/DrawerController.java
@@ -167,6 +167,11 @@
}
@Override
+ public void onDragExited(View v, Object localState) {
+ // do nothing
+ }
+
+ @Override
public void onViewHovered(View v) {
}
diff --git a/src/com/android/documentsui/HorizontalBreadcrumb.java b/src/com/android/documentsui/HorizontalBreadcrumb.java
index 598decc..60d1f96 100644
--- a/src/com/android/documentsui/HorizontalBreadcrumb.java
+++ b/src/com/android/documentsui/HorizontalBreadcrumb.java
@@ -131,6 +131,11 @@
}
@Override
+ public void onDragExited(View v, Object localState) {
+ // do nothing
+ }
+
+ @Override
public void onViewHovered(View v) {
int pos = getChildAdapterPosition(v);
if (pos != mAdapter.getItemCount() - 1) {
diff --git a/src/com/android/documentsui/ItemDragListener.java b/src/com/android/documentsui/ItemDragListener.java
index 87c1274..f966b74 100644
--- a/src/com/android/documentsui/ItemDragListener.java
+++ b/src/com/android/documentsui/ItemDragListener.java
@@ -16,6 +16,8 @@
package com.android.documentsui;
+import static com.android.documentsui.base.Shared.DEBUG;
+
import android.content.ClipData;
import android.graphics.drawable.Drawable;
import android.util.Log;
@@ -24,8 +26,10 @@
import android.view.View.OnDragListener;
import com.android.documentsui.ItemDragListener.DragHost;
+import com.android.documentsui.base.DocumentInfo;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
@@ -67,6 +71,8 @@
handleLocationEvent(v, event.getX(), event.getY());
return true;
case DragEvent.ACTION_DRAG_EXITED:
+ mDragHost.onDragExited(v, event.getLocalState());
+ // fall through
case DragEvent.ACTION_DRAG_ENDED:
handleExitedEndedEvent(v, event);
return true;
@@ -97,7 +103,6 @@
private void handleExitedEndedEvent(View v, DragEvent event) {
mDragHost.setDropTargetHighlight(v, event.getLocalState(), false);
-
TimerTask task = (TimerTask) v.getTag(R.id.drag_hovering_tag);
if (task != null) {
task.cancel();
@@ -171,5 +176,12 @@
* @param localState the Local state object given by DragEvent
*/
void onDragEntered(View v, Object localState);
+
+ /**
+ * Notifies right away when drag shadow exits the view
+ * @param v the view which drop shadow just exited
+ * @param localState the Local state object given by DragEvent
+ */
+ void onDragExited(View v, Object localState);
}
}
diff --git a/src/com/android/documentsui/dirlist/DirectoryDragListener.java b/src/com/android/documentsui/dirlist/DirectoryDragListener.java
index 044d688..e7340a1 100644
--- a/src/com/android/documentsui/dirlist/DirectoryDragListener.java
+++ b/src/com/android/documentsui/dirlist/DirectoryDragListener.java
@@ -19,6 +19,7 @@
import android.view.DragEvent;
import android.view.View;
+import com.android.documentsui.DragAndDropHelper;
import com.android.documentsui.ItemDragListener;
import java.util.TimerTask;
@@ -36,10 +37,6 @@
final boolean result = super.onDrag(v, event);
switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_EXITED:
- // If drag exits, we want to update drag and drop status on the drop shadow
- mDragHost.dragExited(v);
- break;
case DragEvent.ACTION_DRAG_ENDED:
// getResult() is true if drag was accepted
mDragHost.dragStopped(event.getResult());
@@ -58,7 +55,7 @@
@Override
public @Nullable TimerTask createOpenTask(final View v, DragEvent event) {
- return mDragHost.canCopyTo(event.getLocalState(), v) ?
- super.createOpenTask(v, event) : null;
+ return DragAndDropHelper.canCopyTo(event.getLocalState(), mDragHost.getDestination(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 d44c2f7..4e04ef8 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -72,6 +72,7 @@
import com.android.documentsui.DirectoryReloadLock;
import com.android.documentsui.DirectoryResult;
import com.android.documentsui.DocumentsApplication;
+import com.android.documentsui.DragAndDropHelper;
import com.android.documentsui.FocusManager;
import com.android.documentsui.Injector;
import com.android.documentsui.Injector.ContentScoped;
@@ -896,18 +897,6 @@
}
}
- void dragExited(View v) {
- // For now, just always reset drag shadow when drag exits
- mActivity.getShadowBuilder().resetBackground();
- v.updateDragShadow(mActivity.getShadowBuilder());
- if (v.getParent() == mRecView) {
- DocumentHolder holder = getDocumentHolder(v);
- if (holder != null) {
- holder.resetDropHighlight();
- }
- }
- }
-
void dragStopped(boolean result) {
if (result) {
mSelectionMgr.clearSelection();
@@ -919,25 +908,32 @@
getActivity().runOnUiThread(runnable);
}
- /**
- * {@inheritDoc}
- *
- * In DirectoryFragment, we close the roots drawer right away.
- * We also want to update the Drag Shadow to indicate whether the
- * item is droppable or not.
- */
+ // In DirectoryFragment, we close the roots drawer right away.
+ // We also want to update the Drag Shadow to indicate whether the
+ // item is droppable or not.
@Override
public void onDragEntered(View v, Object localState) {
mActivity.setRootsDrawerOpen(false);
- mActivity.getShadowBuilder().setAppearDroppable(canCopyTo(localState, v));
+ mActivity.getShadowBuilder()
+ .setAppearDroppable(DragAndDropHelper.canCopyTo(localState, getDestination(v)));
v.updateDragShadow(mActivity.getShadowBuilder());
}
- /**
- * {@inheritDoc}
- *
- * In DirectoryFragment, we spring loads the hovered folder.
- */
+ // In DirectoryFragment, we always reset the background of the Drag Shadow once it
+ // exits.
+ @Override
+ public void onDragExited(View v, Object localState) {
+ mActivity.getShadowBuilder().resetBackground();
+ v.updateDragShadow(mActivity.getShadowBuilder());
+ if (v.getParent() == mRecView) {
+ DocumentHolder holder = getDocumentHolder(v);
+ if (holder != null) {
+ holder.resetDropHighlight();
+ }
+ }
+ }
+
+ // In DirectoryFragment, we spring loads the hovered folder.
@Override
public void onViewHovered(View view) {
BaseActivity activity = mActivity;
@@ -956,7 +952,7 @@
assert(DocumentClipper.getOpType(clipData) == FileOperationService.OPERATION_COPY);
- if (!canCopyTo(event.getLocalState(), v)) {
+ if (!DragAndDropHelper.canCopyTo(event.getLocalState(), getDestination(v))) {
return false;
}
@@ -978,24 +974,7 @@
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 canCopyTo(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) {
+ DocumentInfo getDestination(View v) {
String id = getModelId(v);
if (id != null) {
Cursor dstCursor = mModel.getItem(id);
@@ -1023,7 +1002,8 @@
if (!highlight) {
holder.resetDropHighlight();
} else {
- holder.setDroppableHighlight(canCopyTo(localState, v));
+ holder.setDroppableHighlight(
+ DragAndDropHelper.canCopyTo(localState, getDestination(v)));
}
}
}
diff --git a/src/com/android/documentsui/dirlist/DragStartListener.java b/src/com/android/documentsui/dirlist/DragStartListener.java
index af864d9..6598a87 100644
--- a/src/com/android/documentsui/dirlist/DragStartListener.java
+++ b/src/com/android/documentsui/dirlist/DragStartListener.java
@@ -138,6 +138,7 @@
mShadowFactory.apply(selection),
invalidDest,
View.DRAG_FLAG_GLOBAL
+ | View.DRAG_FLAG_OPAQUE
| View.DRAG_FLAG_GLOBAL_URI_READ
| View.DRAG_FLAG_GLOBAL_URI_WRITE);
diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java
index 71c66b7..e913857 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -50,6 +50,7 @@
import com.android.documentsui.ActionHandler;
import com.android.documentsui.BaseActivity;
import com.android.documentsui.DocumentsApplication;
+import com.android.documentsui.DragAndDropHelper;
import com.android.documentsui.Injector;
import com.android.documentsui.Injector.Injected;
import com.android.documentsui.ItemDragListener;
@@ -164,31 +165,19 @@
}
private boolean onRightClick(View v, int x, int y, Runnable callback) {
- int pos = mList.pointToPosition(x, y);
+ final int pos = mList.pointToPosition(x, y);
final Item item = mAdapter.getItem(pos);
- if (!(item instanceof RootItem)) {
+
+ // If a read-only root, no need to see if top level is writable (it's not)
+ if (!(item instanceof RootItem) || !((RootItem) item).root.supportsCreate()) {
return false;
}
+
final RootItem rootItem = (RootItem) item;
-
- if (!rootItem.root.supportsCreate()) {
- // If a read-only root, no need to see if top level is writable (it's not)
+ getRootDocument(rootItem, (DocumentInfo doc) -> {
+ rootItem.docInfo = doc;
callback.run();
- return true;
- }
- // We need to start a GetRootDocumentTask so we can know whether items can be directly
- // pasted into root
- GetRootDocumentTask task = new GetRootDocumentTask(
- rootItem.root,
- getBaseActivity(),
- (DocumentInfo doc) -> {
- rootItem.docInfo = doc;
- callback.run();
- });
- task.setTimeout(CONTEXT_MENU_ITEM_TIMEOUT);
- task.setForceCallback(true);
- task.executeOnExecutor(getBaseActivity().getExecutorForCurrentDirectory());
-
+ });
return true;
}
@@ -372,20 +361,38 @@
getActivity().runOnUiThread(runnable);
}
- /**
- * {@inheritDoc}
- *
- * In RootsFragment we don't do anything
- */
+ // In RootsFragment, we check whether the item corresponds to a RootItem, and whether
+ // the currently dragged objects can be droppable or not, and change the drop-shadow
+ // accordingly
@Override
public void onDragEntered(View v, Object localState) {
+ final int pos = (Integer) v.getTag(R.id.item_position_tag);
+ final Item item = mAdapter.getItem(pos);
+
+ // If a read-only root, no need to see if top level is writable (it's not)
+ if (!(item instanceof RootItem) || !((RootItem) item).root.supportsCreate()) {
+ getBaseActivity().getShadowBuilder().setAppearDroppable(false);
+ v.updateDragShadow(getBaseActivity().getShadowBuilder());
+ return;
+ }
+
+ final RootItem rootItem = (RootItem) item;
+ getRootDocument(rootItem, (DocumentInfo doc) -> {
+ rootItem.docInfo = doc;
+ getBaseActivity().getShadowBuilder().setAppearDroppable(
+ doc.isCreateSupported() && DragAndDropHelper.canCopyTo(localState, doc));
+ v.updateDragShadow(getBaseActivity().getShadowBuilder());
+ });
}
- /**
- * {@inheritDoc}
- *
- * In RootsFragment we open the hovered root.
- */
+ // In RootsFragment we always reset the drag shadow as it exits a RootItemView.
+ @Override
+ public void onDragExited(View v, Object localState) {
+ getBaseActivity().getShadowBuilder().resetBackground();
+ v.updateDragShadow(getBaseActivity().getShadowBuilder());
+ }
+
+ // In RootsFragment we open the hovered root.
@Override
public void onViewHovered(View v) {
// SpacerView doesn't have DragListener so this view is guaranteed to be a RootItemView.
@@ -446,6 +453,25 @@
}
}
+ @FunctionalInterface
+ interface RootUpdater {
+ void updateDocInfoForRoot(DocumentInfo doc);
+ }
+
+ private void getRootDocument(RootItem rootItem, RootUpdater updater) {
+ // We need to start a GetRootDocumentTask so we can know whether items can be directly
+ // pasted into root
+ GetRootDocumentTask task = new GetRootDocumentTask(
+ rootItem.root,
+ getBaseActivity(),
+ (DocumentInfo doc) -> {
+ updater.updateDocInfoForRoot(doc);
+ });
+ task.setTimeout(CONTEXT_MENU_ITEM_TIMEOUT);
+ task.setForceCallback(true);
+ task.executeOnExecutor(getBaseActivity().getExecutorForCurrentDirectory());
+ }
+
static void ejectClicked(View ejectIcon, RootInfo root, ActionHandler actionHandler) {
assert(ejectIcon != null);
assert(!root.ejecting);
diff --git a/tests/unit/com/android/documentsui/ItemDragListenerTest.java b/tests/unit/com/android/documentsui/ItemDragListenerTest.java
index 71799ac..d3c5051 100644
--- a/tests/unit/com/android/documentsui/ItemDragListenerTest.java
+++ b/tests/unit/com/android/documentsui/ItemDragListenerTest.java
@@ -129,6 +129,7 @@
mTestTimer.fastForwardTo(DELAY_AFTER_HOVERING);
assertSame(mTestView, mTestDragHost.mLastEnteredView);
+ assertSame(mTestView, mTestDragHost.mLastExitedView);
assertNull(mTestDragHost.mLastHoveredView);
}
@@ -200,6 +201,7 @@
private View mHighlightedView;
private View mLastHoveredView;
private View mLastEnteredView;
+ private View mLastExitedView;
@Override
public void setDropTargetHighlight(View v, Object localState, boolean highlight) {
@@ -220,5 +222,10 @@
public void onDragEntered(View v, Object localState) {
mLastEnteredView = v;
}
+
+ @Override
+ public void onDragExited(View v, Object localState) {
+ mLastExitedView = v;
+ }
}
}
diff --git a/tests/unit/com/android/documentsui/dirlist/DragScrollListenerTest.java b/tests/unit/com/android/documentsui/dirlist/DragScrollListenerTest.java
index 3a0cc4c..e5c31aa 100644
--- a/tests/unit/com/android/documentsui/dirlist/DragScrollListenerTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/DragScrollListenerTest.java
@@ -202,6 +202,10 @@
@Override
public void onDragEntered(View v, Object localState) {
}
+
+ @Override
+ public void onDragExited(View v, Object localState) {
+ }
}
private class TestScrollActionDelegate implements ScrollActionDelegate {