Fixing fast scroller touch handling in all-apps and widget sheet
Also removing scrim view, instead drawing the scrim manually

Bug: 73085356
Change-Id: I188c6c9b1685e22d5d97b38dd5d3e960b655c9ba
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index a7f0026..450d107 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -29,8 +29,6 @@
 
     <include layout="@layout/all_apps_rv_layout" />
 
-    <include layout="@layout/all_apps_fast_scroller" />
-
     <include layout="@layout/all_apps_floating_header" />
 
     <!-- Note: we are reusing/repurposing a system attribute for search layout, because of a
@@ -39,10 +37,5 @@
         android:id="@id/search_container_all_apps"
         layout="@layout/search_container_all_apps"/>
 
-    <View
-        android:id="@+id/nav_bar_bg"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_alignParentBottom="true"
-        android:background="?attr/allAppsNavBarScrimColor" />
+    <include layout="@layout/all_apps_fast_scroller" />
 </com.android.launcher3.allapps.AllAppsContainerView>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index ca397bd..978b5a1 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -47,13 +47,5 @@
             android:layout_height="match_parent"
             android:layout_gravity="end"
             android:layout_marginEnd="@dimen/fastscroll_end_margin" />
-
-        <View
-            android:id="@+id/nav_bar_bg"
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_gravity="bottom"
-            android:background="?attr/allAppsNavBarScrimColor"
-            android:focusable="false"  />
     </com.android.launcher3.views.TopRoundedCornerView>
 </com.android.launcher3.widget.WidgetsFullSheet>
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index cc13263..74b9cfa 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -33,8 +33,7 @@
  *   <li> Enable fast scroller.
  * </ul>
  */
-public abstract class BaseRecyclerView extends RecyclerView
-        implements RecyclerView.OnItemTouchListener {
+public abstract class BaseRecyclerView extends RecyclerView  {
 
     protected RecyclerViewFastScroller mScrollbar;
 
@@ -51,12 +50,6 @@
     }
 
     @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        addOnItemTouchListener(this);
-    }
-
-    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         bindFastScrollbar();
@@ -69,40 +62,8 @@
         onUpdateScrollbar(0);
     }
 
-    /**
-     * We intercept the touch handling only to support fast scrolling when initiated from the
-     * scroll bar.  Otherwise, we fall back to the default RecyclerView touch handling.
-     */
-    @Override
-    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
-        return handleTouchEvent(ev);
-    }
-
-    @Override
-    public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
-        handleTouchEvent(ev);
-    }
-
-    /**
-     * Handles the touch event and determines whether to show the fast scroller (or updates it if
-     * it is already showing).
-     */
-    private boolean handleTouchEvent(MotionEvent ev) {
-        // Move to mScrollbar's coordinate system.
-        // We need to take parent into account (view pager's location)
-        ViewGroup parent = (ViewGroup) getParent();
-        int left = parent.getLeft() - mScrollbar.getLeft();
-        int top = parent.getTop() + getTop() - mScrollbar.getTop() - getScrollBarTop();
-        ev.offsetLocation(left, top);
-        try {
-            return mScrollbar.handleTouchEvent(ev);
-        } finally {
-            ev.offsetLocation(-left, -top);
-        }
-    }
-
-    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
-        // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
+    public RecyclerViewFastScroller getScrollbar() {
+        return mScrollbar;
     }
 
     public int getScrollBarTop() {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 5cd54ca..26922ad 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -18,6 +18,9 @@
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 
 import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Process;
 import android.support.annotation.NonNull;
@@ -57,6 +60,7 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BottomUserEducationView;
+import com.android.launcher3.views.RecyclerViewFastScroller;
 
 /**
  * The all apps view container.
@@ -70,6 +74,9 @@
     private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
     private final AllAppsStore mAllAppsStore = new AllAppsStore();
 
+    private final Paint mNavBarScrimPaint;
+    private int mNavBarScrimHeight = 0;
+
     private SearchUiManager mSearchUiManager;
     private View mSearchContainer;
     private AllAppsPagedView mViewPager;
@@ -80,6 +87,9 @@
     private boolean mUsingTabs;
     private boolean mSearchModeWhileUsingTabs = false;
 
+    private RecyclerViewFastScroller mTouchHandler;
+    private final Point mFastScrollerOffset = new Point();
+
     public AllAppsContainerView(Context context) {
         this(context, null);
     }
@@ -101,6 +111,9 @@
         mAH[AdapterHolder.MAIN] = new AdapterHolder(false /* isWork */);
         mAH[AdapterHolder.WORK] = new AdapterHolder(true /* isWork */);
 
+        mNavBarScrimPaint = new Paint();
+        mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
+
         mAllAppsStore.addUpdateListener(this::onAppsUpdated);
 
         // Attach a scrim to be drawn behind all-apps and hotseat
@@ -108,27 +121,11 @@
                 .attach();
     }
 
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        applyTouchDelegate();
-    }
-
-    private void applyTouchDelegate() {
-        // TODO: Reimplement once fast scroller is fixed.
-    }
-
     public AllAppsStore getAppsStore() {
         return mAllAppsStore;
     }
 
     @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        applyTouchDelegate();
-    }
-
-    @Override
     public void onDeviceProfileChanged(DeviceProfile dp) {
         for (AdapterHolder holder : mAH) {
             if (holder.recyclerView != null) {
@@ -163,7 +160,38 @@
             return true;
         }
         AllAppsRecyclerView rv = getActiveRecyclerView();
-        return rv == null || rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+        if (rv == null) {
+            return true;
+        }
+        if (rv.getScrollbar().getThumbOffsetY() >= 0 &&
+                mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) {
+            return false;
+        }
+        return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            AllAppsRecyclerView rv = getActiveRecyclerView();
+            if (rv != null &&
+                    rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
+                mTouchHandler = rv.getScrollbar();
+            }
+        }
+        if (mTouchHandler != null) {
+            return mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mTouchHandler != null) {
+            mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
+            return true;
+        }
+        return false;
     }
 
     public AllAppsRecyclerView getActiveRecyclerView() {
@@ -282,14 +310,20 @@
         }
         setLayoutParams(mlp);
 
-        View navBarBg = findViewById(R.id.nav_bar_bg);
-        ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams();
-        navBarBgLp.height = insets.bottom;
-        navBarBg.setLayoutParams(navBarBgLp);
-
+        mNavBarScrimHeight = insets.bottom;
         InsettableFrameLayout.dispatchInsets(this, insets);
     }
 
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        if (mNavBarScrimHeight > 0) {
+            canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
+                    mNavBarScrimPaint);
+        }
+    }
+
     public SpringAnimationHandler getSpringAnimationHandler() {
         return mUsingTabs ? null : mAH[AdapterHolder.MAIN].animationHandler;
     }
@@ -320,8 +354,6 @@
 
         mAllAppsStore.registerIconContainer(mAH[AdapterHolder.MAIN].recyclerView);
         mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView);
-
-        applyTouchDelegate();
     }
 
     private void replaceRVContainer(boolean showTabs) {
@@ -352,7 +384,6 @@
     public void onTabChanged(int pos) {
         mHeader.setMainActive(pos == 0);
         reset();
-        applyTouchDelegate();
         if (mAH[pos].recyclerView != null) {
             mAH[pos].recyclerView.bindFastScrollbar();
 
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 53d19eb..4a393c9 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -97,7 +97,6 @@
             int defStyleRes) {
         super(context, attrs, defStyleAttr);
         Resources res = getResources();
-        addOnItemTouchListener(this);
         mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
                 R.dimen.all_apps_empty_search_bg_top_offset);
 
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 58c9148..1cd6699 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -22,6 +22,8 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.util.Property;
@@ -43,6 +45,7 @@
 public class RecyclerViewFastScroller extends View {
 
     private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
+    private static final Rect sTempRect = new Rect();
 
     private static final Property<RecyclerViewFastScroller, Integer> TRACK_WIDTH =
             new Property<RecyclerViewFastScroller, Integer>(Integer.class, "width") {
@@ -204,9 +207,9 @@
      * Handles the touch event and determines whether to show the fast scroller (or updates it if
      * it is already showing).
      */
-    public boolean handleTouchEvent(MotionEvent ev) {
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
+    public boolean handleTouchEvent(MotionEvent ev, Point offset) {
+        int x = (int) ev.getX() - offset.x;
+        int y = (int) ev.getY() - offset.y;
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN:
                 // Keep track of the down positions
@@ -260,7 +263,6 @@
     }
 
     private void calcTouchOffsetAndPrepToFastScroll(int downY, int lastY) {
-        mRv.getParent().requestDisallowInterceptTouchEvent(true);
         mIsDragging = true;
         if (mCanThumbDetach) {
             mIsThumbDetached = true;
@@ -358,4 +360,16 @@
                 mMaxWidth, mRv.getScrollbarTrackHeight() - mMaxWidth - height);
         mPopupView.setTranslationY(top);
     }
+
+    public boolean isHitInParent(float x, float y, Point outOffset) {
+        if (mThumbOffsetY < 0) {
+            return false;
+        }
+        getHitRect(sTempRect);
+        sTempRect.top += mRv.getScrollBarTop();
+        if (outOffset != null) {
+            outOffset.set(sTempRect.left, sTempRect.top);
+        }
+        return sTempRect.contains((int) x, (int) y);
+    }
 }
diff --git a/src/com/android/launcher3/views/TopRoundedCornerView.java b/src/com/android/launcher3/views/TopRoundedCornerView.java
index ba223c4..3ba8ca3 100644
--- a/src/com/android/launcher3/views/TopRoundedCornerView.java
+++ b/src/com/android/launcher3/views/TopRoundedCornerView.java
@@ -17,12 +17,14 @@
 
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.RectF;
 import android.util.AttributeSet;
 import android.widget.FrameLayout;
 
 import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
 
 /**
  * View with top rounded corners.
@@ -33,23 +35,41 @@
     private final Path mClipPath = new Path();
     private float[] mRadii;
 
+    private final Paint mNavBarScrimPaint;
+    private int mNavBarScrimHeight = 0;
+
     public TopRoundedCornerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
         int radius = getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
         mRadii = new float[] {radius, radius, radius, radius, 0, 0, 0, 0};
+
+        mNavBarScrimPaint = new Paint();
+        mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
     }
 
     public TopRoundedCornerView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
+    public void setNavBarScrimHeight(int height) {
+        if (mNavBarScrimHeight != height) {
+            mNavBarScrimHeight = height;
+            invalidate();
+        }
+    }
+
     @Override
     public void draw(Canvas canvas) {
         canvas.save();
         canvas.clipPath(mClipPath);
         super.draw(canvas);
         canvas.restore();
+
+        if (mNavBarScrimHeight > 0) {
+            canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
+                    mNavBarScrimPaint);
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index 48f8afe..b31feed 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -23,7 +23,6 @@
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.View;
 import android.view.animation.AnimationUtils;
 
 import com.android.launcher3.Insettable;
@@ -31,6 +30,8 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.R;
+import com.android.launcher3.views.RecyclerViewFastScroller;
+import com.android.launcher3.views.TopRoundedCornerView;
 
 /**
  * Popup for showing the full list of available widgets
@@ -46,7 +47,6 @@
 
     private final WidgetsListAdapter mAdapter;
 
-    private View mNavBarScrim;
     private WidgetsRecyclerView mRecyclerView;
 
     public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -65,7 +65,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mContent = findViewById(R.id.container);
-        mNavBarScrim = findViewById(R.id.nav_bar_bg);
 
         mRecyclerView = findViewById(R.id.widgets_list_view);
         mRecyclerView.setAdapter(mAdapter);
@@ -91,7 +90,6 @@
     public void setInsets(Rect insets) {
         mInsets.set(insets);
 
-        mNavBarScrim.getLayoutParams().height = insets.bottom;
         mRecyclerView.setPadding(
                 mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(),
                 mRecyclerView.getPaddingRight(), insets.bottom);
@@ -100,6 +98,8 @@
         } else {
             clearNavBarColor();
         }
+
+        ((TopRoundedCornerView) mContent).setNavBarScrimHeight(mInsets.bottom);
         requestLayout();
     }
 
@@ -195,7 +195,11 @@
         // Disable swipe down when recycler view is scrolling
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mNoIntercept = false;
-            if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
+            RecyclerViewFastScroller scroller = mRecyclerView.getScrollbar();
+            if (scroller.getThumbOffsetY() >= 0 &&
+                    mLauncher.getDragLayer().isEventOverView(scroller, ev)) {
+                mNoIntercept = true;
+            } else if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
                 mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer());
             }
         }
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 89c88a4..124058e 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -17,8 +17,12 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
+import android.graphics.Point;
 import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.launcher3.BaseRecyclerView;
@@ -27,13 +31,15 @@
 /**
  * The widgets recycler view.
  */
-public class WidgetsRecyclerView extends BaseRecyclerView {
+public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouchListener {
 
-    private static final String TAG = "WidgetsRecyclerView";
     private WidgetsListAdapter mAdapter;
 
     private final int mScrollbarTop;
 
+    private final Point mFastScrollerOffset = new Point();
+    private boolean mTouchDownOnScroller;
+
     public WidgetsRecyclerView(Context context) {
         this(context, null);
     }
@@ -46,6 +52,7 @@
         // API 21 and below only support 3 parameter ctor.
         super(context, attrs, defStyleAttr);
         mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
+        addOnItemTouchListener(this);
     }
 
     public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
@@ -56,7 +63,6 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        addOnItemTouchListener(this);
         // create a layout manager with Launcher's context so that scroll position
         // can be preserved during screen rotation.
         setLayoutManager(new LinearLayoutManager(getContext()));
@@ -145,4 +151,26 @@
     public int getScrollBarTop() {
         return mScrollbarTop;
     }
+
+    @Override
+    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+        if (e.getAction() == MotionEvent.ACTION_DOWN) {
+            mTouchDownOnScroller =
+                    mScrollbar.isHitInParent(e.getX(), e.getY(), mFastScrollerOffset);
+        }
+        if (mTouchDownOnScroller) {
+            return mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
+        }
+        return false;
+    }
+
+    @Override
+    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+        if (mTouchDownOnScroller) {
+            mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
+        }
+    }
+
+    @Override
+    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { }
 }
\ No newline at end of file