Experimental drag&drop in DocumentsUI
Change-Id: If8911f33c6e1de2252d5bdcd5862799c5c4a202a
diff --git a/src/com/android/documentsui/DirectoryFragment.java b/src/com/android/documentsui/DirectoryFragment.java
index 7d737ca..054556e 100644
--- a/src/com/android/documentsui/DirectoryFragment.java
+++ b/src/com/android/documentsui/DirectoryFragment.java
@@ -44,6 +44,7 @@
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
@@ -62,6 +63,7 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.ActionMode;
+import android.view.DragEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -88,6 +90,7 @@
import com.google.android.collect.Lists;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -202,6 +205,7 @@
mListView.setOnItemClickListener(mItemListener);
mListView.setMultiChoiceModeListener(mMultiListener);
mListView.setRecyclerListener(mRecycleListener);
+ setupDragAndDropOnDirectoryView(mListView);
// Indent our list divider to align with text
final Drawable divider = mListView.getDivider();
@@ -217,6 +221,7 @@
mGridView.setOnItemClickListener(mItemListener);
mGridView.setMultiChoiceModeListener(mMultiListener);
mGridView.setRecyclerListener(mRecycleListener);
+ setupDragAndDropOnDirectoryView(mGridView);
return view;
}
@@ -897,13 +902,8 @@
iconMime.setAlpha(1f);
iconThumb.setAlpha(0f);
iconThumb.setImageDrawable(null);
- if (docIcon != 0) {
- iconMime.setImageDrawable(
- IconUtils.loadPackageIcon(context, docAuthority, docIcon));
- } else {
- iconMime.setImageDrawable(IconUtils.loadMimeIcon(
- context, docMimeType, docAuthority, docId, state.derivedMode));
- }
+ iconMime.setImageDrawable(
+ getDocumentIcon(context, docAuthority, docId, docMimeType, docIcon, state));
}
boolean hasLine1 = false;
@@ -1011,6 +1011,8 @@
if (icon1 != null) icon1.setAlpha(iconAlpha);
if (icon2 != null) icon2.setAlpha(iconAlpha);
+ setupDragAndDropOnDocumentView(convertView, cursor);
+
return convertView;
}
@@ -1262,4 +1264,135 @@
}
return clipData;
}
+
+ private void setupDragAndDropOnDirectoryView(AbsListView view) {
+ // Listen for drops on non-directory items and empty space.
+ view.setOnDragListener(mOnDragListener);
+ }
+
+ private void setupDragAndDropOnDocumentView(View view, Cursor cursor) {
+ final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+ if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
+ // Make a directory item a drop target. Drop on non-directories and empty space
+ // is handled at the list/grid view level.
+ view.setOnDragListener(mOnDragListener);
+ }
+
+ // Temporary: attaching the listener to the title only.
+ // Attaching to the entire item conflicts with the item long click handler responsible
+ // for item selection.
+ final View title = view.findViewById(android.R.id.title);
+ title.setOnLongClickListener(mLongClickListener);
+ }
+
+ private View.OnDragListener mOnDragListener = new View.OnDragListener() {
+ @Override
+ public boolean onDrag(View v, DragEvent event) {
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ // TODO: Check if the event contains droppable data.
+ return true;
+
+ // TODO: Highlight potential drop target directory?
+ // TODO: Expand drop target directory on hover?
+ case DragEvent.ACTION_DRAG_ENTERED:
+ case DragEvent.ACTION_DRAG_LOCATION:
+ case DragEvent.ACTION_DRAG_EXITED:
+ case DragEvent.ACTION_DRAG_ENDED:
+ return true;
+
+ case DragEvent.ACTION_DROP:
+ int dstPosition = mCurrentView.getPositionForView(v);
+ DocumentInfo dstDir = null;
+ if (dstPosition != android.widget.AdapterView.INVALID_POSITION) {
+ Cursor dstCursor = mAdapter.getItem(dstPosition);
+ dstDir = DocumentInfo.fromDirectoryCursor(dstCursor);
+ // TODO: Do not drop into the directory where the documents came from.
+ }
+ copyFromClipData(event.getClipData(), dstDir);
+ return true;
+ }
+ return false;
+ }
+ };
+
+ private View.OnLongClickListener mLongClickListener = new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ final List<DocumentInfo> docs = getDraggableDocuments(v);
+ if (docs.isEmpty()) {
+ return false;
+ }
+ v.startDrag(
+ getClipDataFromDocuments(docs),
+ new DrawableShadowBuilder(getDragShadowIcon(docs)),
+ null,
+ View.DRAG_FLAG_GLOBAL
+ );
+ return true;
+ }
+ };
+
+ private List<DocumentInfo> getDraggableDocuments(View currentItemView) {
+ final int position = mCurrentView.getPositionForView(currentItemView);
+ if (position == android.widget.AdapterView.INVALID_POSITION) {
+ return Collections.EMPTY_LIST;
+ }
+
+ final List<DocumentInfo> selectedDocs = getSelectedDocuments();
+ if (!selectedDocs.isEmpty()) {
+ if (!mCurrentView.isItemChecked(position)) {
+ // There is a selection that does not include the current item, drag nothing.
+ return Collections.EMPTY_LIST;
+ }
+ return selectedDocs;
+ }
+
+ final Cursor cursor = mAdapter.getItem(position);
+ final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
+ return Lists.newArrayList(doc);
+ }
+
+ private Drawable getDragShadowIcon(List<DocumentInfo> docs) {
+ if (docs.size() == 1) {
+ final DocumentInfo doc = docs.get(0);
+ return getDocumentIcon(getActivity(), doc.authority, doc.documentId,
+ doc.mimeType, doc.icon, getDisplayState(this));
+ }
+ return getActivity().getDrawable(R.drawable.ic_doc_generic);
+ }
+
+ public static Drawable getDocumentIcon(Context context, String docAuthority, String docId,
+ String docMimeType, int docIcon, State state) {
+ if (docIcon != 0) {
+ return IconUtils.loadPackageIcon(context, docAuthority, docIcon);
+ } else {
+ return IconUtils.loadMimeIcon(context, docMimeType, docAuthority, docId,
+ state.derivedMode);
+ }
+ }
+
+ private class DrawableShadowBuilder extends View.DragShadowBuilder {
+
+ private final Drawable mShadow;
+
+ private final int mShadowDimension;
+
+ public DrawableShadowBuilder(Drawable shadow) {
+ mShadow = shadow;
+ mShadowDimension = getResources().getDimensionPixelSize(
+ R.dimen.drag_shadow_size);
+ mShadow.setBounds(0, 0, mShadowDimension, mShadowDimension);
+ }
+
+ public void onProvideShadowMetrics(
+ Point shadowSize, Point shadowTouchPoint) {
+ shadowSize.set(mShadowDimension, mShadowDimension);
+ shadowTouchPoint.set(mShadowDimension / 2, mShadowDimension / 2);
+ }
+
+ public void onDrawShadow(Canvas canvas) {
+ mShadow.draw(canvas);
+ }
+ }
}