Enable drag & drop -> Springload for breadcrumb.

Bug: 29519481
Change-Id: I54d98c48c08153c22b2f01c7728187927c4fe1a2
(cherry picked from commit 81d903bc083807c6048ac9ce1d1d3ca18b6643a3)
diff --git a/packages/DocumentsUI/res/drawable/breadcrumb_item_background.xml b/packages/DocumentsUI/res/drawable/breadcrumb_item_background.xml
new file mode 100644
index 0000000..c4bc77b
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable/breadcrumb_item_background.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res/com.android.documentsui"
+    android:color="?attr/colorControlHighlight">
+    <item
+        android:id="@android:id/mask"
+        android:drawable="@android:color/white"/>
+
+    <item>
+        <selector>
+            <item
+                app:state_highlighted="true"
+                android:drawable="@color/item_breadcrumb_background_hovered"/>
+            <item
+                app:state_highlighted="false"
+                android:drawable="@android:color/transparent">
+                <corners
+                    android:topLeftRadius="2dp"
+                    android:topRightRadius="2dp"
+                    android:bottomLeftRadius="2dp"
+                    android:bottomRightRadius="2dp"
+                />
+            </item>
+        </selector>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/DocumentsUI/res/layout/fixed_layout.xml b/packages/DocumentsUI/res/layout/fixed_layout.xml
index 2ea9366..6c65437 100644
--- a/packages/DocumentsUI/res/layout/fixed_layout.xml
+++ b/packages/DocumentsUI/res/layout/fixed_layout.xml
@@ -41,6 +41,7 @@
 
             <com.android.documentsui.HorizontalBreadcrumb
                 android:id="@+id/breadcrumb"
+                android:layout_marginRight="20dp"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content" />
 
diff --git a/packages/DocumentsUI/res/layout/navigation_breadcrumb_item.xml b/packages/DocumentsUI/res/layout/navigation_breadcrumb_item.xml
index ab9d3b2..b45d25d 100644
--- a/packages/DocumentsUI/res/layout/navigation_breadcrumb_item.xml
+++ b/packages/DocumentsUI/res/layout/navigation_breadcrumb_item.xml
@@ -28,17 +28,20 @@
 <LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="wrap_content"
-  android:layout_height="match_parent"
+  android:layout_height="@dimen/breadcrumb_item_height"
   android:layout_alignParentTop="true"
   android:gravity="center_vertical"
   android:orientation="horizontal">
 
-    <TextView
+    <com.android.documentsui.DragOverTextView
         android:id="@+id/breadcrumb_text"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:gravity="center_vertical"
-        android:textAppearance="@android:style/TextAppearance.Material.Widget.ActionBar.Title" />
+        android:paddingRight="@dimen/breadcrumb_item_padding"
+        android:paddingLeft="@dimen/breadcrumb_item_padding"
+        android:textAppearance="@android:style/TextAppearance.Material.Widget.ActionBar.Title"
+        android:background="@drawable/breadcrumb_item_background" />
 
     <ImageView
         android:id="@+id/breadcrumb_arrow"
diff --git a/packages/DocumentsUI/res/values/attrs.xml b/packages/DocumentsUI/res/values/attrs.xml
index b48c52f..27906f6 100644
--- a/packages/DocumentsUI/res/values/attrs.xml
+++ b/packages/DocumentsUI/res/values/attrs.xml
@@ -18,7 +18,7 @@
         <attr name="colorActionMode" format="color"/>
     </declare-styleable>
 
-    <declare-styleable name="RootItemView">
+    <declare-styleable name="HighlightedItemView">
         <attr name="state_highlighted" format="boolean"/>
     </declare-styleable>
 </resources>
diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
index cf0643d..0acaeb8 100644
--- a/packages/DocumentsUI/res/values/colors.xml
+++ b/packages/DocumentsUI/res/values/colors.xml
@@ -43,5 +43,6 @@
     <!-- TODO: Would be nice to move this to a color-set, but not sure how to support animation -->
     <color name="item_doc_background">#fffafafa</color>
     <color name="item_doc_background_selected">#ffe0f2f1</color>
+    <color name="item_breadcrumb_background_hovered">#1affffff</color>
 
 </resources>
diff --git a/packages/DocumentsUI/res/values/dimens.xml b/packages/DocumentsUI/res/values/dimens.xml
index cad26e2..5f1b349 100644
--- a/packages/DocumentsUI/res/values/dimens.xml
+++ b/packages/DocumentsUI/res/values/dimens.xml
@@ -33,6 +33,8 @@
     <dimen name="grid_padding_vert">4dp</dimen>
     <dimen name="list_item_height">72dp</dimen>
     <dimen name="list_item_padding">16dp</dimen>
+    <dimen name="breadcrumb_item_padding">8dp</dimen>
+    <dimen name="breadcrumb_item_height">36dp</dimen>
     <dimen name="list_divider_inset">72dp</dimen>
     <dimen name="dir_elevation">8dp</dimen>
     <dimen name="drag_shadow_size">120dp</dimen>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DragOverTextView.java b/packages/DocumentsUI/src/com/android/documentsui/DragOverTextView.java
new file mode 100644
index 0000000..e9fc2a0
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/DragOverTextView.java
@@ -0,0 +1,52 @@
+/*
+ * 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 android.content.Context;
+import android.util.AttributeSet;
+import android.view.View.OnDragListener;
+import android.widget.TextView;
+
+/**
+ * An {@link TextView} that uses drawable states to distinct between normal and highlighted states.
+ */
+
+public final class DragOverTextView extends TextView {
+    private static final int[] STATE_HIGHLIGHTED = {R.attr.state_highlighted};
+
+    private boolean mHighlighted = false;
+
+    public DragOverTextView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+
+        if (mHighlighted) {
+            mergeDrawableStates(drawableState, STATE_HIGHLIGHTED);
+        }
+
+        return drawableState;
+    }
+
+    public void setHighlight(boolean highlight) {
+        mHighlighted = highlight;
+        refreshDrawableState();
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/HorizontalBreadcrumb.java b/packages/DocumentsUI/src/com/android/documentsui/HorizontalBreadcrumb.java
index 9f6b79b..e473479 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/HorizontalBreadcrumb.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/HorizontalBreadcrumb.java
@@ -26,7 +26,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
-import android.widget.TextView;
 
 import com.android.documentsui.NavigationViewManager.Breadcrumb;
 import com.android.documentsui.NavigationViewManager.Environment;
@@ -38,7 +37,10 @@
 /**
  * Horizontal implementation of breadcrumb used for tablet / desktop device layouts
  */
-public final class HorizontalBreadcrumb extends RecyclerView implements Breadcrumb {
+public final class HorizontalBreadcrumb extends RecyclerView
+        implements Breadcrumb, ItemDragListener.DragHost {
+
+    private static final int USER_NO_SCROLL_OFFSET_THRESHOLD = 5;
 
     private LinearLayoutManager mLayoutManager;
     private BreadcrumbAdapter mAdapter;
@@ -64,7 +66,8 @@
         mListener = listener;
         mLayoutManager = new LinearLayoutManager(
                 getContext(), LinearLayoutManager.HORIZONTAL, false);
-        mAdapter = new BreadcrumbAdapter(state, env);
+        mAdapter = new BreadcrumbAdapter(
+                state, env, new ItemDragListener<>(this));
 
         setLayoutManager(mLayoutManager);
         addOnItemTouchListener(new ClickListener(getContext(), this::onSingleTapUp));
@@ -74,18 +77,62 @@
     public void show(boolean visibility) {
         if (visibility) {
             setVisibility(VISIBLE);
-            setAdapter(mAdapter);
-            mLayoutManager.scrollToPosition(mAdapter.getItemCount() - 1);
+            boolean shouldScroll = !hasUserDefineScrollOffset();
+            if (getAdapter() == null) {
+                setAdapter(mAdapter);
+            } else {
+                int currentItemCount = mAdapter.getItemCount();
+                int lastItemCount = mAdapter.getLastItemSize();
+                if (currentItemCount > lastItemCount) {
+                    mAdapter.notifyItemRangeInserted(lastItemCount,
+                            currentItemCount - lastItemCount);
+                    mAdapter.notifyItemChanged(lastItemCount - 1);
+                } else if (currentItemCount < lastItemCount) {
+                    mAdapter.notifyItemRangeRemoved(currentItemCount,
+                            lastItemCount - currentItemCount);
+                    mAdapter.notifyItemChanged(currentItemCount - 1);
+                }
+            }
+            if (shouldScroll) {
+                mLayoutManager.scrollToPosition(mAdapter.getItemCount() - 1);
+            }
         } else {
             setVisibility(GONE);
             setAdapter(null);
         }
+        mAdapter.updateLastItemSize();
+    }
+
+    private boolean hasUserDefineScrollOffset() {
+        final int maxOffset = computeHorizontalScrollRange() - computeHorizontalScrollExtent();
+        return (maxOffset - computeHorizontalScrollOffset() > USER_NO_SCROLL_OFFSET_THRESHOLD);
     }
 
     @Override
     public void postUpdate() {
     }
 
+    @Override
+    public void runOnUiThread(Runnable runnable) {
+        post(runnable);
+    }
+
+    @Override
+    public void setDropTargetHighlight(View v, boolean highlight) {
+        RecyclerView.ViewHolder vh = getChildViewHolder(v);
+        if (vh instanceof BreadcrumbHolder) {
+            ((BreadcrumbHolder) vh).setHighlighted(highlight);
+        }
+    }
+
+    @Override
+    public void onViewHovered(View v) {
+        int pos = getChildAdapterPosition(v);
+        if (pos != mAdapter.getItemCount() - 1) {
+            mListener.accept(pos);
+        }
+    }
+
     private void onSingleTapUp(MotionEvent e) {
         View itemView = findChildViewUnder(e.getX(), e.getY());
         int pos = getChildAdapterPosition(itemView);
@@ -97,12 +144,19 @@
     private static final class BreadcrumbAdapter
             extends RecyclerView.Adapter<BreadcrumbHolder> {
 
-        private Environment mEnv;
-        private com.android.documentsui.State mState;
+        private final Environment mEnv;
+        private final com.android.documentsui.State mState;
+        private final OnDragListener mDragListener;
+        // We keep the old item size so the breadcrumb will only re-render views that are necessary
+        private int mLastItemSize;
 
-        public BreadcrumbAdapter(com.android.documentsui.State state, Environment env) {
+        public BreadcrumbAdapter(com.android.documentsui.State state,
+                Environment env,
+                OnDragListener dragListener) {
             mState = state;
             mEnv = env;
+            mDragListener = dragListener;
+            mLastItemSize = mState.stack.size();
         }
 
         @Override
@@ -125,7 +179,10 @@
 
             if (position == getItemCount() - 1) {
                 holder.arrow.setVisibility(View.GONE);
+            } else {
+                holder.arrow.setVisibility(View.VISIBLE);
             }
+            holder.itemView.setOnDragListener(mDragListener);
         }
 
         private DocumentInfo getItem(int position) {
@@ -136,18 +193,34 @@
         public int getItemCount() {
             return mState.stack.size();
         }
+
+        public int getLastItemSize() {
+            return mLastItemSize;
+        }
+
+        public void updateLastItemSize() {
+            mLastItemSize = mState.stack.size();
+        }
     }
 
     private static class BreadcrumbHolder extends RecyclerView.ViewHolder {
 
-        protected TextView title;
+        protected DragOverTextView title;
         protected ImageView arrow;
 
         public BreadcrumbHolder(View itemView) {
             super(itemView);
-            title = (TextView) itemView.findViewById(R.id.breadcrumb_text);
+            title = (DragOverTextView) itemView.findViewById(R.id.breadcrumb_text);
             arrow = (ImageView) itemView.findViewById(R.id.breadcrumb_arrow);
         }
+
+        /**
+         * Highlights the associated item view.
+         * @param highlighted
+         */
+        public void setHighlighted(boolean highlighted) {
+            title.setHighlight(highlighted);
+        }
     }
 
     private static final class ClickListener extends GestureDetector
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryDragListener.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryDragListener.java
index 40ee14d..0860f4c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryDragListener.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryDragListener.java
@@ -31,16 +31,11 @@
     public boolean onDrag(View v, DragEvent event) {
         final boolean result = super.onDrag(v, event);
 
-        if (event.getAction() == DragEvent.ACTION_DRAG_ENDED) {
+        if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
+            mDragHost.dragStarted();
+        } else if (event.getAction() == DragEvent.ACTION_DRAG_ENDED) {
             // getResult() is true if drag was accepted
-            if (event.getResult()) {
-                mDragHost.clearSelection();
-            } else {
-                // When drag starts we might write a new clip file to disk.
-                // No drop event happens, remove clip file here. This may be called multiple times,
-                // but it should be OK because deletion is idempotent and cheap.
-                mDragHost.deleteDragClipFile();
-            }
+            mDragHost.dragStopped(event.getResult());
         }
 
         return result;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index afdcdf1..f96341a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -499,6 +499,26 @@
         state.dirState.put(mStateKey, container);
     }
 
+    void dragStarted() {
+        // When files are selected for dragging, ActionMode is started. This obscures the breadcrumb
+        // with an ActionBar. In order to make drag and drop to the breadcrumb possible, we first
+        // end ActionMode so the breadcrumb is visible to the user.
+        if (mActionMode != null) {
+            mActionMode.finish();
+        }
+    }
+
+    void dragStopped(boolean result) {
+        if (result) {
+            clearSelection();
+        } else {
+            // When drag starts we might write a new clip file to disk.
+            // No drop event happens, remove clip file here. This may be called multiple times,
+            // but it should be OK because deletion is idempotent and cheap.
+            deleteDragClipFile();
+        }
+    }
+
     public void onDisplayStateChanged() {
         updateDisplayState();
     }
@@ -1273,7 +1293,7 @@
         activity.setRootsDrawerOpen(false);
     }
 
-    void deleteDragClipFile() {
+    private void deleteDragClipFile() {
         mClipper.deleteDragClip();
     }