Merge "Using inbuild smooth scroller instead of custom fastscrolling logic" into ub-launcher3-rvc-dev
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index 3ee1293..f97eb28 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -15,206 +15,90 @@
  */
 package com.android.launcher3.allapps;
 
-import com.android.launcher3.util.Thunk;
+import androidx.recyclerview.widget.LinearSmoothScroller;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
-import java.util.HashSet;
-import java.util.List;
+import com.android.launcher3.allapps.AlphabeticalAppsList.FastScrollSectionInfo;
 
-import androidx.recyclerview.widget.RecyclerView;
+public class AllAppsFastScrollHelper {
 
-public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallback {
+    private static final int NO_POSITION = -1;
 
-    private static final int INITIAL_TOUCH_SETTLING_DURATION = 100;
-    private static final int REPEAT_TOUCH_SETTLING_DURATION = 200;
+    private int mTargetFastScrollPosition = NO_POSITION;
 
     private AllAppsRecyclerView mRv;
-    private AlphabeticalAppsList mApps;
+    private ViewHolder mLastSelectedViewHolder;
 
-    // Keeps track of the current and targeted fast scroll section (the section to scroll to after
-    // the initial delay)
-    int mTargetFastScrollPosition = -1;
-    @Thunk String mCurrentFastScrollSection;
-    @Thunk String mTargetFastScrollSection;
-
-    // The settled states affect the delay before the fast scroll animation is applied
-    private boolean mHasFastScrollTouchSettled;
-    private boolean mHasFastScrollTouchSettledAtLeastOnce;
-
-    // Set of all views animated during fast scroll.  We keep track of these ourselves since there
-    // is no way to reset a view once it gets scrapped or recycled without other hacks
-    private HashSet<RecyclerView.ViewHolder> mTrackedFastScrollViews = new HashSet<>();
-
-    // Smooth fast-scroll animation frames
-    @Thunk int mFastScrollFrameIndex;
-    @Thunk final int[] mFastScrollFrames = new int[10];
-
-    /**
-     * This runnable runs a single frame of the smooth scroll animation and posts the next frame
-     * if necessary.
-     */
-    @Thunk Runnable mSmoothSnapNextFrameRunnable = new Runnable() {
-        @Override
-        public void run() {
-            if (mFastScrollFrameIndex < mFastScrollFrames.length) {
-                mRv.scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]);
-                mFastScrollFrameIndex++;
-                mRv.postOnAnimation(mSmoothSnapNextFrameRunnable);
-            }
-        }
-    };
-
-    /**
-     * This runnable updates the current fast scroll section to the target fastscroll section.
-     */
-    Runnable mFastScrollToTargetSectionRunnable = new Runnable() {
-        @Override
-        public void run() {
-            // Update to the target section
-            mCurrentFastScrollSection = mTargetFastScrollSection;
-            mHasFastScrollTouchSettled = true;
-            mHasFastScrollTouchSettledAtLeastOnce = true;
-            updateTrackedViewsFastScrollFocusState();
-        }
-    };
-
-    public AllAppsFastScrollHelper(AllAppsRecyclerView rv, AlphabeticalAppsList apps) {
+    public AllAppsFastScrollHelper(AllAppsRecyclerView rv) {
         mRv = rv;
-        mApps = apps;
-    }
-
-    public void onSetAdapter(AllAppsGridAdapter adapter) {
-        adapter.setBindViewCallback(this);
     }
 
     /**
      * Smooth scrolls the recycler view to the given section.
-     *
-     * @return whether the fastscroller can scroll to the new section.
      */
-    public boolean smoothScrollToSection(int scrollY, int availableScrollHeight,
-            AlphabeticalAppsList.FastScrollSectionInfo info) {
-        if (mTargetFastScrollPosition != info.fastScrollToItem.position) {
-            mTargetFastScrollPosition = info.fastScrollToItem.position;
-            smoothSnapToPosition(scrollY, availableScrollHeight, info);
-            return true;
+    public void smoothScrollToSection(FastScrollSectionInfo info) {
+        if (mTargetFastScrollPosition == info.fastScrollToItem.position) {
+            return;
         }
-        return false;
-    }
-
-    /**
-     * Smoothly snaps to a given position.  We do this manually by calculating the keyframes
-     * ourselves and animating the scroll on the recycler view.
-     */
-    private void smoothSnapToPosition(int scrollY, int availableScrollHeight,
-            AlphabeticalAppsList.FastScrollSectionInfo info) {
-        mRv.removeCallbacks(mSmoothSnapNextFrameRunnable);
-        mRv.removeCallbacks(mFastScrollToTargetSectionRunnable);
-
-        trackAllChildViews();
-        if (mHasFastScrollTouchSettled) {
-            // In this case, the user has already settled once (and the fast scroll state has
-            // animated) and they are just fine-tuning their section from the last section, so
-            // we should make it feel fast and update immediately.
-            mCurrentFastScrollSection = info.sectionName;
-            mTargetFastScrollSection = null;
-            updateTrackedViewsFastScrollFocusState();
-        } else {
-            // Otherwise, the user has scrubbed really far, and we don't want to distract the user
-            // with the flashing fast scroll state change animation in addition to the fast scroll
-            // section popup, so reset the views to normal, and wait for the touch to settle again
-            // before animating the fast scroll state.
-            mCurrentFastScrollSection = null;
-            mTargetFastScrollSection = info.sectionName;
-            mHasFastScrollTouchSettled = false;
-            updateTrackedViewsFastScrollFocusState();
-
-            // Delay scrolling to a new section until after some duration.  If the user has been
-            // scrubbing a while and makes multiple big jumps, then reduce the time needed for the
-            // fast scroll to settle so it doesn't feel so long.
-            mRv.postDelayed(mFastScrollToTargetSectionRunnable,
-                    mHasFastScrollTouchSettledAtLeastOnce ?
-                            REPEAT_TOUCH_SETTLING_DURATION :
-                            INITIAL_TOUCH_SETTLING_DURATION);
-        }
-
-        // Calculate the full animation from the current scroll position to the final scroll
-        // position, and then run the animation for the duration.  If we are scrolling to the
-        // first fast scroll section, then just scroll to the top of the list itself.
-        List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
-                mApps.getFastScrollerSections();
-        int newPosition = info.fastScrollToItem.position;
-        int newScrollY = fastScrollSections.size() > 0 && fastScrollSections.get(0) == info
-                        ? 0
-                        : Math.min(availableScrollHeight, mRv.getCurrentScrollY(newPosition, 0));
-        int numFrames = mFastScrollFrames.length;
-        int deltaY = newScrollY - scrollY;
-        float ySign = Math.signum(deltaY);
-        int step = (int) (ySign * Math.ceil((float) Math.abs(deltaY) / numFrames));
-        for (int i = 0; i < numFrames; i++) {
-            // TODO(winsonc): We can interpolate this as well.
-            mFastScrollFrames[i] = (int) (ySign * Math.min(Math.abs(step), Math.abs(deltaY)));
-            deltaY -= step;
-        }
-        mFastScrollFrameIndex = 0;
-        mRv.postOnAnimation(mSmoothSnapNextFrameRunnable);
+        mTargetFastScrollPosition = info.fastScrollToItem.position;
+        mRv.getLayoutManager().startSmoothScroll(new MyScroller(mTargetFastScrollPosition));
     }
 
     public void onFastScrollCompleted() {
-        // TODO(winsonc): Handle the case when the user scrolls and releases before the animation
-        //                runs
-
-        // Stop animating the fast scroll position and state
-        mRv.removeCallbacks(mSmoothSnapNextFrameRunnable);
-        mRv.removeCallbacks(mFastScrollToTargetSectionRunnable);
-
-        // Reset the tracking variables
-        mHasFastScrollTouchSettled = false;
-        mHasFastScrollTouchSettledAtLeastOnce = false;
-        mCurrentFastScrollSection = null;
-        mTargetFastScrollSection = null;
-        mTargetFastScrollPosition = -1;
-
-        updateTrackedViewsFastScrollFocusState();
-        mTrackedFastScrollViews.clear();
+        mTargetFastScrollPosition = NO_POSITION;
+        setLastHolderSelected(false);
+        mLastSelectedViewHolder = null;
     }
 
-    @Override
-    public void onBindView(AllAppsGridAdapter.ViewHolder holder) {
-        // Update newly bound views to the current fast scroll state if we are fast scrolling
-        if (mCurrentFastScrollSection != null || mTargetFastScrollSection != null) {
-            mTrackedFastScrollViews.add(holder);
+
+    private void setLastHolderSelected(boolean isSelected) {
+        if (mLastSelectedViewHolder != null) {
+            mLastSelectedViewHolder.itemView.setActivated(isSelected);
+            mLastSelectedViewHolder.setIsRecyclable(!isSelected);
         }
     }
 
-    /**
-     * Starts tracking all the recycler view's children which are FastScrollFocusableViews.
-     */
-    private void trackAllChildViews() {
-        int childCount = mRv.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            RecyclerView.ViewHolder viewHolder = mRv.getChildViewHolder(mRv.getChildAt(i));
-            if (viewHolder != null) {
-                mTrackedFastScrollViews.add(viewHolder);
-            }
-        }
-    }
+    private class MyScroller extends LinearSmoothScroller {
 
-    /**
-     * Updates the fast scroll focus on all the children.
-     */
-    private void updateTrackedViewsFastScrollFocusState() {
-        for (RecyclerView.ViewHolder viewHolder : mTrackedFastScrollViews) {
-            int pos = viewHolder.getAdapterPosition();
-            boolean isActive = false;
-            if (mCurrentFastScrollSection != null
-                    && pos > RecyclerView.NO_POSITION
-                    && pos < mApps.getAdapterItems().size()) {
-                AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos);
-                isActive = item != null &&
-                        mCurrentFastScrollSection.equals(item.sectionName) &&
-                        item.position == mTargetFastScrollPosition;
+        private final int mTargetPosition;
+
+        public MyScroller(int targetPosition) {
+            super(mRv.getContext());
+
+            mTargetPosition = targetPosition;
+            setTargetPosition(targetPosition);
+        }
+
+        @Override
+        protected int getVerticalSnapPreference() {
+            return SNAP_TO_START;
+        }
+
+        @Override
+        protected void onStop() {
+            super.onStop();
+            if (mTargetPosition != mTargetFastScrollPosition) {
+                // Target changed, before the last scroll can finish
+                return;
             }
-            viewHolder.itemView.setActivated(isActive);
+
+            ViewHolder currentHolder = mRv.findViewHolderForAdapterPosition(mTargetPosition);
+            if (currentHolder == mLastSelectedViewHolder) {
+                return;
+            }
+
+            setLastHolderSelected(false);
+            mLastSelectedViewHolder = currentHolder;
+            setLastHolderSelected(true);
+        }
+
+        @Override
+        protected void onStart() {
+            super.onStart();
+            if (mTargetPosition != mTargetFastScrollPosition) {
+                setLastHolderSelected(false);
+                mLastSelectedViewHolder = null;
+            }
         }
     }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 4aebec0..3afa756 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -71,11 +71,6 @@
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
 
-
-    public interface BindViewCallback {
-        void onBindView(ViewHolder holder);
-    }
-
     /**
      * ViewHolder for each icon.
      */
@@ -186,7 +181,6 @@
 
     private int mAppsPerRow;
 
-    private BindViewCallback mBindViewCallback;
     private OnFocusChangeListener mIconFocusListener;
 
     // The text to show when there are no search results and no market search handler.
@@ -248,13 +242,6 @@
     }
 
     /**
-     * Sets the callback for when views are bound.
-     */
-    public void setBindViewCallback(BindViewCallback cb) {
-        mBindViewCallback = cb;
-    }
-
-    /**
      * Returns the grid layout manager.
      */
     public GridLayoutManager getLayoutManager() {
@@ -319,9 +306,6 @@
                 // nothing to do
                 break;
         }
-        if (mBindViewCallback != null) {
-            mBindViewCallback.onBindView(holder);
-        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 069472f..cbf02b7 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -53,12 +53,12 @@
 public class AllAppsRecyclerView extends BaseRecyclerView implements LogContainerProvider {
 
     private AlphabeticalAppsList mApps;
-    private AllAppsFastScrollHelper mFastScrollHelper;
     private final int mNumAppsPerRow;
 
     // The specific view heights that we use to calculate scroll
-    private SparseIntArray mViewHeights = new SparseIntArray();
-    private SparseIntArray mCachedScrollPositions = new SparseIntArray();
+    private final SparseIntArray mViewHeights = new SparseIntArray();
+    private final SparseIntArray mCachedScrollPositions = new SparseIntArray();
+    private final AllAppsFastScrollHelper mFastScrollHelper;
 
     // The empty-search result background
     private AllAppsBackgroundDrawable mEmptySearchBackground;
@@ -85,6 +85,7 @@
         mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
                 R.dimen.all_apps_empty_search_bg_top_offset);
         mNumAppsPerRow = LauncherAppState.getIDP(context).numColumns;
+        mFastScrollHelper = new AllAppsFastScrollHelper(this);
     }
 
     /**
@@ -92,7 +93,6 @@
      */
     public void setApps(AlphabeticalAppsList apps) {
         mApps = apps;
-        mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
     }
 
     public AlphabeticalAppsList getApps() {
@@ -221,9 +221,6 @@
             return "";
         }
 
-        // Stop the scroller if it is scrolling
-        stopScroll();
-
         // Find the fastscroll section that maps to this touch fraction
         List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
                 mApps.getFastScrollerSections();
@@ -236,10 +233,7 @@
             lastInfo = info;
         }
 
-        // Update the fast scroll
-        int scrollY = getCurrentScrollY();
-        int availableScrollHeight = getAvailableScrollHeight();
-        mFastScrollHelper.smoothScrollToSection(scrollY, availableScrollHeight, lastInfo);
+        mFastScrollHelper.smoothScrollToSection(lastInfo);
         return lastInfo.sectionName;
     }
 
@@ -257,7 +251,6 @@
                 mCachedScrollPositions.clear();
             }
         });
-        mFastScrollHelper.onSetAdapter((AllAppsGridAdapter) adapter);
     }
 
     @Override
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 5653801..6a83332 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -67,7 +67,6 @@
 
     private final static int MAX_TRACK_ALPHA = 30;
     private final static int SCROLL_BAR_VIS_DURATION = 150;
-    private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 0.75f;
 
     private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
             Collections.singletonList(new Rect());
@@ -184,7 +183,7 @@
         if (mThumbOffsetY == y) {
             return;
         }
-        updatePopupY((int) y);
+        updatePopupY(y);
         mThumbOffsetY = y;
         invalidate();
     }
@@ -237,7 +236,7 @@
                 } else if (mRv.supportsFastScrolling()
                         && isNearScrollBar(mDownX)) {
                     calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY);
-                    updateFastScrollSectionNameAndThumbOffset(mLastY, y);
+                    updateFastScrollSectionNameAndThumbOffset(y);
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
@@ -252,7 +251,7 @@
                     calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY);
                 }
                 if (mIsDragging) {
-                    updateFastScrollSectionNameAndThumbOffset(mLastY, y);
+                    updateFastScrollSectionNameAndThumbOffset(y);
                 }
                 break;
             case MotionEvent.ACTION_UP:
@@ -281,7 +280,7 @@
         showActiveScrollbar(true);
     }
 
-    private void updateFastScrollSectionNameAndThumbOffset(int lastY, int y) {
+    private void updateFastScrollSectionNameAndThumbOffset(int y) {
         // Update the fastscroller section name at this touch position
         int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight;
         float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));