AllAppsRecyclerView.java revision b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520
193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung/*
293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * Copyright (C) 2015 The Android Open Source Project
393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung *
493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * Licensed under the Apache License, Version 2.0 (the "License");
593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * you may not use this file except in compliance with the License.
693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * You may obtain a copy of the License at
793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung *
893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung *      http://www.apache.org/licenses/LICENSE-2.0
993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung *
1093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * Unless required by applicable law or agreed to in writing, software
1193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * distributed under the License is distributed on an "AS IS" BASIS,
1293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * See the License for the specific language governing permissions and
1493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * limitations under the License.
1593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung */
165f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chungpackage com.android.launcher3.allapps;
1793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
1893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.content.Context;
19243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chungimport android.graphics.Canvas;
208f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chungimport android.os.Bundle;
2124cf70092b8b0281df071891573642f56e34f9e5Winson Chungimport android.support.v7.widget.LinearLayoutManager;
2293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.support.v7.widget.RecyclerView;
2393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.util.AttributeSet;
24243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chungimport android.view.ContextThemeWrapper;
25f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chungimport android.view.View;
26b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal
275f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chungimport com.android.launcher3.BaseRecyclerView;
28b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chungimport com.android.launcher3.BaseRecyclerViewFastScrollBar;
295f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chungimport com.android.launcher3.DeviceProfile;
305f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chungimport com.android.launcher3.Launcher;
318f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chungimport com.android.launcher3.Stats;
32b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyalimport com.android.launcher3.util.Thunk;
3393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
3493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport java.util.List;
3593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
3693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung/**
37ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song * A RecyclerView with custom fast scroll support for the all apps view.
3893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung */
398f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chungpublic class AllAppsRecyclerView extends BaseRecyclerView
408f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        implements Stats.LaunchSourceProvider {
4193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
42b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private static final int FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON = 0;
43b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private static final int FAST_SCROLL_MODE_FREE_SCROLL = 1;
44b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
45b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW = 0;
46b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS = 1;
47b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
4893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private AlphabeticalAppsList mApps;
4993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private int mNumAppsPerRow;
50208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung    private int mPredictionBarHeight;
51b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
52b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk BaseRecyclerViewFastScrollBar.FastScrollFocusableView mLastFastScrollFocusedView;
53b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk int mPrevFastScrollFocusedPosition;
54b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk int mFastScrollFrameIndex;
55b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk final int[] mFastScrollFrames = new int[10];
56b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal
57b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private final int mFastScrollMode = FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON;
58b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private final int mScrollBarMode = FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW;
5993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
602e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen    private Launcher mLauncher;
61b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private ScrollPositionState mScrollPosState = new ScrollPositionState();
622e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen
635f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung    public AllAppsRecyclerView(Context context) {
6493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        this(context, null);
6593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
6693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
675f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung    public AllAppsRecyclerView(Context context, AttributeSet attrs) {
6893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        this(context, attrs, 0);
6993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
7093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
715f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung    public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
7293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        this(context, attrs, defStyleAttr, 0);
7393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
7493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
755f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung    public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
7693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung            int defStyleRes) {
7793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        super(context, attrs, defStyleAttr);
78243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung        // We have a theme on this view, so we need to coerce the base activity context from that
79243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung        ContextThemeWrapper ctx = (ContextThemeWrapper) context;
80243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung        mLauncher = (Launcher) ctx.getBaseContext();
8193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
8293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
8393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
8493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Sets the list of apps in this view, used to determine the fastscroll position.
8593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
8693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    public void setApps(AlphabeticalAppsList apps) {
8793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        mApps = apps;
8893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
8993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
9093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
9193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Sets the number of apps per row in this recycler view.
9293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
93b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    public void setNumAppsPerRow(int numAppsPerRow) {
94208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        mNumAppsPerRow = numAppsPerRow;
95da25da3d557177febb9c7afb78e04b1ef4a45f17Winson Chung
962e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen        DeviceProfile grid = mLauncher.getDeviceProfile();
97da25da3d557177febb9c7afb78e04b1ef4a45f17Winson Chung        RecyclerView.RecycledViewPool pool = getRecycledViewPool();
98da25da3d557177febb9c7afb78e04b1ef4a45f17Winson Chung        int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
995f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_BAR_SPACER_TYPE, 1);
1005f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE, 1);
1015f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow);
1025f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
10393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
10493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
10593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
106208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung     * Sets the prediction bar height.
107208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung     */
108208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung    public void setPredictionBarHeight(int height) {
109208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        mPredictionBarHeight = height;
110208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung    }
111208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung
112208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung    /**
113ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung     * Scrolls this recycler view to the top.
114ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung     */
115ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung    public void scrollToTop() {
116ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        scrollToPosition(0);
11732f14071cc5808737c88da7b5f3047a8222f8843Winson Chung    }
11832f14071cc5808737c88da7b5f3047a8222f8843Winson Chung
11932f14071cc5808737c88da7b5f3047a8222f8843Winson Chung    /**
12032f14071cc5808737c88da7b5f3047a8222f8843Winson Chung     * Returns the current scroll position.
12132f14071cc5808737c88da7b5f3047a8222f8843Winson Chung     */
12232f14071cc5808737c88da7b5f3047a8222f8843Winson Chung    public int getScrollPosition() {
12332f14071cc5808737c88da7b5f3047a8222f8843Winson Chung        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
124b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        getCurScrollState(mScrollPosState, items);
125b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mScrollPosState.rowIndex != -1) {
12632f14071cc5808737c88da7b5f3047a8222f8843Winson Chung            int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
127b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            return getPaddingTop() + predictionBarHeight +
128b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    (mScrollPosState.rowIndex * mScrollPosState.rowHeight) -
129b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                            mScrollPosState.rowTopOffset;
13032f14071cc5808737c88da7b5f3047a8222f8843Winson Chung        }
13132f14071cc5808737c88da7b5f3047a8222f8843Winson Chung        return 0;
132ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung    }
133ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung
134243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung    /**
135243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung     * We need to override the draw to ensure that we don't draw the overscroll effect beyond the
136243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung     * background bounds.
137243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung     */
138243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung    @Override
139243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung    protected void dispatchDraw(Canvas canvas) {
140243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung        canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
141243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung                getWidth() - mBackgroundPadding.right,
142243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung                getHeight() - mBackgroundPadding.bottom);
143243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung        super.dispatchDraw(canvas);
144243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung    }
145243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung
14693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    @Override
14793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    protected void onFinishInflate() {
148ebe1734a67dff9ab46f3c6cce328a86b714ce620Hyunyoung Song        super.onFinishInflate();
149ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung
150ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        // Bind event handlers
15193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        addOnItemTouchListener(this);
15293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
15393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
1548f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung    @Override
1558f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung    public void fillInLaunchSourceData(Bundle sourceData) {
1568f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_ALL_APPS);
1578f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        if (mApps.hasFilter()) {
1588f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung            sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER,
1598f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung                    Stats.SUB_CONTAINER_ALL_APPS_SEARCH);
1608f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        } else {
1618f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung            sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER,
1628f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung                    Stats.SUB_CONTAINER_ALL_APPS_A_Z);
1638f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        }
1648f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung    }
1658f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung
16693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
16799d96ba6c8e3258f7d99a33d49da2aeb0da5d862Winson Chung     * Maps the touch (from 0..1) to the adapter position that should be visible.
16893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
169ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    @Override
170ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    public String scrollToPositionAtProgress(float touchFraction) {
171b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int rowCount = mApps.getNumAppRows();
172b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (rowCount == 0) {
1739121fbffafe0b90a23768e5ebc5fffab2a2175c2Winson Chung            return "";
1749121fbffafe0b90a23768e5ebc5fffab2a2175c2Winson Chung        }
17593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
176208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        // Stop the scroller if it is scrolling
177208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        stopScroll();
178208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung
179b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // Find the fastscroll section that maps to this touch fraction
180b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
181b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                mApps.getFastScrollerSections();
182b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        AlphabeticalAppsList.FastScrollSectionInfo lastInfo = fastScrollSections.get(0);
183b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW) {
184b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            for (int i = 1; i < fastScrollSections.size(); i++) {
185b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i);
186b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                if (info.touchFraction > touchFraction) {
187b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    break;
188b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                }
189b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                lastInfo = info;
190208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung            }
191b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        } else if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS){
192b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            lastInfo = fastScrollSections.get((int) (touchFraction * (fastScrollSections.size() - 1)));
193b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        } else {
194b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            throw new RuntimeException("Unexpected scroll bar mode");
195208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        }
196208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung
197b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // Map the touch position back to the scroll of the recycler view
198b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        getCurScrollState(mScrollPosState, mApps.getAdapterItems());
199b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
200b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight,
201b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                predictionBarHeight);
202b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
203b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
204b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
20593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        }
20693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
207b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mPrevFastScrollFocusedPosition != lastInfo.fastScrollToItem.position) {
208b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mPrevFastScrollFocusedPosition = lastInfo.fastScrollToItem.position;
209208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung
210b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            // Reset the last focused view
211b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            if (mLastFastScrollFocusedView != null) {
212b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                mLastFastScrollFocusedView.setFastScrollFocused(false, true);
213b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                mLastFastScrollFocusedView = null;
214b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            }
215f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
216b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            if (mFastScrollMode == FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON) {
217b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                smoothSnapToPosition(mPrevFastScrollFocusedPosition, mScrollPosState);
218b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            } else if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
219b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
220b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                if (vh != null &&
221b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                        vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
222b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    mLastFastScrollFocusedView =
223b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                            (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
224b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    mLastFastScrollFocusedView.setFastScrollFocused(true, true);
225b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                }
226b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            } else {
227b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                throw new RuntimeException("Unexpected fast scroll mode");
228ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song            }
229ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song        }
230b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        return lastInfo.sectionName;
231ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    }
232ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song
233b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    @Override
234b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    public void onFastScrollCompleted() {
235b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        super.onFastScrollCompleted();
236b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // Reset and clean up the last focused view
237b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mLastFastScrollFocusedView != null) {
238b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mLastFastScrollFocusedView.setFastScrollFocused(false, true);
239b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mLastFastScrollFocusedView = null;
240ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song        }
241b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        mPrevFastScrollFocusedPosition = -1;
242ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    }
243ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song
244f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    /**
245515f2897e876ba52ed6371d22e6d0f7d1016aaf3Winson Chung     * Updates the bounds for the scrollbar.
246f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     */
247ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    @Override
248b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    public void onUpdateScrollbar() {
24983f59abc9c566da5deb98afe7ea35cfb061f2920Winson Chung        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
25083f59abc9c566da5deb98afe7ea35cfb061f2920Winson Chung
251ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        // Skip early if there are no items or we haven't been measured
252ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        if (items.isEmpty() || mNumAppsPerRow == 0) {
253b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mScrollbar.setScrollbarThumbOffset(-1, -1);
254f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            return;
255f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        }
256f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
257f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        // Find the index and height of the first visible row (all rows have the same height)
258b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int rowCount = mApps.getNumAppRows();
259b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        getCurScrollState(mScrollPosState, items);
260b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mScrollPosState.rowIndex < 0) {
261b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mScrollbar.setScrollbarThumbOffset(-1, -1);
262b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            return;
263b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        }
264b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
2658c01f810ebcddeb2e9f3a6599c62f701e9c3cac9Winson Chung        int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
266b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, predictionBarHeight);
267b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    }
268f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
269b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    /**
270b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * This runnable runs a single frame of the smooth scroll animation and posts the next frame
271b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * if necessary.
272b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     */
273b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk Runnable mSmoothSnapNextFrameRunnable = new Runnable() {
274b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        @Override
275b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        public void run() {
276b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            if (mFastScrollFrameIndex < mFastScrollFrames.length) {
277b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]);
278b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                mFastScrollFrameIndex++;
279b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                postOnAnimation(mSmoothSnapNextFrameRunnable);
280b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            } else {
281b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                // Animation completed, set the fast scroll state on the target view
282b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
283b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                if (vh != null &&
284b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                        vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView &&
285b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                        mLastFastScrollFocusedView != vh.itemView) {
286b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    mLastFastScrollFocusedView =
287b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                            (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
288b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    mLastFastScrollFocusedView.setFastScrollFocused(true, true);
289b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                }
290f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            }
291f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        }
292b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    };
293b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
294b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    /**
295b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * Smoothly snaps to a given position.  We do this manually by calculating the keyframes
296b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * ourselves and animating the scroll on the recycler view.
297b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     */
298b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private void smoothSnapToPosition(final int position, ScrollPositionState scrollPosState) {
299b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        removeCallbacks(mSmoothSnapNextFrameRunnable);
300b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
301b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // Calculate the full animation from the current scroll position to the final scroll
302b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // position, and then run the animation for the duration.
303b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
304b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int curScrollY = getPaddingTop() + predictionBarHeight +
305b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
306b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int newScrollY = getScrollAtPosition(position, scrollPosState.rowHeight);
307b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int numFrames = mFastScrollFrames.length;
308b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        for (int i = 0; i < numFrames; i++) {
309b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            // TODO(winsonc): We can interpolate this as well.
310b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mFastScrollFrames[i] = (newScrollY - curScrollY) / numFrames;
311b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        }
312b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        mFastScrollFrameIndex = 0;
313b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        postOnAnimation(mSmoothSnapNextFrameRunnable);
314f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    }
315ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung
316ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung    /**
317b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * Returns the current scroll state of the apps rows, not including the prediction
318b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * bar.
319ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung     */
320ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung    private void getCurScrollState(ScrollPositionState stateOut,
321ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            List<AlphabeticalAppsList.AdapterItem> items) {
322ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        stateOut.rowIndex = -1;
323ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        stateOut.rowTopOffset = -1;
324ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        stateOut.rowHeight = -1;
325532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung
326ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        // Return early if there are no items or we haven't been measured
327ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        if (items.isEmpty() || mNumAppsPerRow == 0) {
328532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung            return;
329532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung        }
330532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung
331ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        int childCount = getChildCount();
332ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        for (int i = 0; i < childCount; i++) {
333ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            View child = getChildAt(i);
334ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            int position = getChildPosition(child);
335ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            if (position != NO_POSITION) {
336ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                AlphabeticalAppsList.AdapterItem item = items.get(position);
3375f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung                if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
338b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    stateOut.rowIndex = item.rowIndex;
339ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                    stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
340ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                    stateOut.rowHeight = child.getHeight();
341ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                    break;
342ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                }
343ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            }
344ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        }
345ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung    }
346b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
347b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    /**
348b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * Returns the scrollY for the given position in the adapter.
349b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     */
350b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private int getScrollAtPosition(int position, int rowHeight) {
351b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
352b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
353b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
354b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            return getPaddingTop() + predictionBarHeight + item.rowIndex * rowHeight;
355b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        } else {
356b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            return 0;
357b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        }
358b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    }
35993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung}
360