AllAppsRecyclerView.java revision 1cddad6c6bff0b271e8ebde44013a5a09f273436
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;
24f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chungimport android.view.View;
25b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal
265f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chungimport com.android.launcher3.BaseRecyclerView;
27b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chungimport com.android.launcher3.BaseRecyclerViewFastScrollBar;
285f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chungimport com.android.launcher3.DeviceProfile;
298f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chungimport com.android.launcher3.Stats;
30b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyalimport com.android.launcher3.util.Thunk;
3193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
3293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport java.util.List;
3393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
3493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung/**
35ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song * A RecyclerView with custom fast scroll support for the all apps view.
3693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung */
378f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chungpublic class AllAppsRecyclerView extends BaseRecyclerView
388f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        implements Stats.LaunchSourceProvider {
3993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
40b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private static final int FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON = 0;
41b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private static final int FAST_SCROLL_MODE_FREE_SCROLL = 1;
42b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
43b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW = 0;
44b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS = 1;
45b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
4693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private AlphabeticalAppsList mApps;
4793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private int mNumAppsPerRow;
48208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung    private int mPredictionBarHeight;
49b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
50b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk BaseRecyclerViewFastScrollBar.FastScrollFocusableView mLastFastScrollFocusedView;
51b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk int mPrevFastScrollFocusedPosition;
52b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk int mFastScrollFrameIndex;
53b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk final int[] mFastScrollFrames = new int[10];
54b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal
55b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private final int mFastScrollMode = FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON;
56b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private final int mScrollBarMode = FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW;
5793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
58b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private ScrollPositionState mScrollPosState = new ScrollPositionState();
592e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen
605f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung    public AllAppsRecyclerView(Context context) {
6193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        this(context, null);
6293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
6393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
645f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung    public AllAppsRecyclerView(Context context, AttributeSet attrs) {
6593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        this(context, attrs, 0);
6693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
6793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
685f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung    public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
6993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        this(context, attrs, defStyleAttr, 0);
7093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
7193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
725f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung    public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
7393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung            int defStyleRes) {
7493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        super(context, attrs, defStyleAttr);
7593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
7693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
7793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
7893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Sets the list of apps in this view, used to determine the fastscroll position.
7993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
8093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    public void setApps(AlphabeticalAppsList apps) {
8193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        mApps = apps;
8293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
8393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
8493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
8593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Sets the number of apps per row in this recycler view.
8693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
871cddad6c6bff0b271e8ebde44013a5a09f273436Winson Chung    public void setNumAppsPerRow(DeviceProfile grid, int numAppsPerRow) {
88208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        mNumAppsPerRow = numAppsPerRow;
89da25da3d557177febb9c7afb78e04b1ef4a45f17Winson Chung
90da25da3d557177febb9c7afb78e04b1ef4a45f17Winson Chung        RecyclerView.RecycledViewPool pool = getRecycledViewPool();
91da25da3d557177febb9c7afb78e04b1ef4a45f17Winson Chung        int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
925f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_BAR_SPACER_TYPE, 1);
935f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE, 1);
945f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow);
955f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
9693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
9793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
9893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
99208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung     * Sets the prediction bar height.
100208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung     */
101208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung    public void setPredictionBarHeight(int height) {
102208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        mPredictionBarHeight = height;
103208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung    }
104208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung
105208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung    /**
106ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung     * Scrolls this recycler view to the top.
107ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung     */
108ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung    public void scrollToTop() {
109ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        scrollToPosition(0);
11032f14071cc5808737c88da7b5f3047a8222f8843Winson Chung    }
11132f14071cc5808737c88da7b5f3047a8222f8843Winson Chung
11232f14071cc5808737c88da7b5f3047a8222f8843Winson Chung    /**
11332f14071cc5808737c88da7b5f3047a8222f8843Winson Chung     * Returns the current scroll position.
11432f14071cc5808737c88da7b5f3047a8222f8843Winson Chung     */
11532f14071cc5808737c88da7b5f3047a8222f8843Winson Chung    public int getScrollPosition() {
11632f14071cc5808737c88da7b5f3047a8222f8843Winson Chung        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
117b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        getCurScrollState(mScrollPosState, items);
118b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mScrollPosState.rowIndex != -1) {
11932f14071cc5808737c88da7b5f3047a8222f8843Winson Chung            int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
120b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            return getPaddingTop() + predictionBarHeight +
121b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    (mScrollPosState.rowIndex * mScrollPosState.rowHeight) -
122b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                            mScrollPosState.rowTopOffset;
12332f14071cc5808737c88da7b5f3047a8222f8843Winson Chung        }
12432f14071cc5808737c88da7b5f3047a8222f8843Winson Chung        return 0;
125ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung    }
126ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung
127243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung    /**
128243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung     * We need to override the draw to ensure that we don't draw the overscroll effect beyond the
129243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung     * background bounds.
130243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung     */
131243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung    @Override
132243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung    protected void dispatchDraw(Canvas canvas) {
133243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung        canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
134243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung                getWidth() - mBackgroundPadding.right,
135243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung                getHeight() - mBackgroundPadding.bottom);
136243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung        super.dispatchDraw(canvas);
137243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung    }
138243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung
13993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    @Override
14093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    protected void onFinishInflate() {
141ebe1734a67dff9ab46f3c6cce328a86b714ce620Hyunyoung Song        super.onFinishInflate();
142ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung
143ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        // Bind event handlers
14493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        addOnItemTouchListener(this);
14593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
14693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
1478f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung    @Override
1488f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung    public void fillInLaunchSourceData(Bundle sourceData) {
1498f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_ALL_APPS);
1508f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        if (mApps.hasFilter()) {
1518f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung            sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER,
1528f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung                    Stats.SUB_CONTAINER_ALL_APPS_SEARCH);
1538f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        } else {
1548f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung            sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER,
1558f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung                    Stats.SUB_CONTAINER_ALL_APPS_A_Z);
1568f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        }
1578f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung    }
1588f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung
15993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
16099d96ba6c8e3258f7d99a33d49da2aeb0da5d862Winson Chung     * Maps the touch (from 0..1) to the adapter position that should be visible.
16193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
162ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    @Override
163ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    public String scrollToPositionAtProgress(float touchFraction) {
164b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int rowCount = mApps.getNumAppRows();
165b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (rowCount == 0) {
1669121fbffafe0b90a23768e5ebc5fffab2a2175c2Winson Chung            return "";
1679121fbffafe0b90a23768e5ebc5fffab2a2175c2Winson Chung        }
16893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
169208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        // Stop the scroller if it is scrolling
170208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        stopScroll();
171208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung
172b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // Find the fastscroll section that maps to this touch fraction
173b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
174b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                mApps.getFastScrollerSections();
175b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        AlphabeticalAppsList.FastScrollSectionInfo lastInfo = fastScrollSections.get(0);
176b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW) {
177b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            for (int i = 1; i < fastScrollSections.size(); i++) {
178b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i);
179b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                if (info.touchFraction > touchFraction) {
180b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    break;
181b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                }
182b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                lastInfo = info;
183208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung            }
184b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        } else if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS){
185b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            lastInfo = fastScrollSections.get((int) (touchFraction * (fastScrollSections.size() - 1)));
186b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        } else {
187b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            throw new RuntimeException("Unexpected scroll bar mode");
188208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        }
189208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung
190b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // Map the touch position back to the scroll of the recycler view
191b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        getCurScrollState(mScrollPosState, mApps.getAdapterItems());
192b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
193b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight,
194b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                predictionBarHeight);
195b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
196b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
197b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
19893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        }
19993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
200b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mPrevFastScrollFocusedPosition != lastInfo.fastScrollToItem.position) {
201b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mPrevFastScrollFocusedPosition = lastInfo.fastScrollToItem.position;
202208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung
203b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            // Reset the last focused view
204b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            if (mLastFastScrollFocusedView != null) {
205b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                mLastFastScrollFocusedView.setFastScrollFocused(false, true);
206b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                mLastFastScrollFocusedView = null;
207b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            }
208f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
209b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            if (mFastScrollMode == FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON) {
210b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                smoothSnapToPosition(mPrevFastScrollFocusedPosition, mScrollPosState);
211b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            } else if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
212b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
213b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                if (vh != null &&
214b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                        vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
215b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    mLastFastScrollFocusedView =
216b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                            (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
217b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    mLastFastScrollFocusedView.setFastScrollFocused(true, true);
218b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                }
219b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            } else {
220b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                throw new RuntimeException("Unexpected fast scroll mode");
221ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song            }
222ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song        }
223b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        return lastInfo.sectionName;
224ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    }
225ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song
226b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    @Override
227b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    public void onFastScrollCompleted() {
228b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        super.onFastScrollCompleted();
229b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // Reset and clean up the last focused view
230b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mLastFastScrollFocusedView != null) {
231b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mLastFastScrollFocusedView.setFastScrollFocused(false, true);
232b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mLastFastScrollFocusedView = null;
233ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song        }
234b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        mPrevFastScrollFocusedPosition = -1;
235ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    }
236ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song
237f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    /**
238515f2897e876ba52ed6371d22e6d0f7d1016aaf3Winson Chung     * Updates the bounds for the scrollbar.
239f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     */
240ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    @Override
241b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    public void onUpdateScrollbar() {
24283f59abc9c566da5deb98afe7ea35cfb061f2920Winson Chung        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
24383f59abc9c566da5deb98afe7ea35cfb061f2920Winson Chung
244ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        // Skip early if there are no items or we haven't been measured
245ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        if (items.isEmpty() || mNumAppsPerRow == 0) {
246b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mScrollbar.setScrollbarThumbOffset(-1, -1);
247f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            return;
248f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        }
249f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
250f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        // Find the index and height of the first visible row (all rows have the same height)
251b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int rowCount = mApps.getNumAppRows();
252b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        getCurScrollState(mScrollPosState, items);
253b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mScrollPosState.rowIndex < 0) {
254b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mScrollbar.setScrollbarThumbOffset(-1, -1);
255b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            return;
256b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        }
257b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
2588c01f810ebcddeb2e9f3a6599c62f701e9c3cac9Winson Chung        int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
259b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, predictionBarHeight);
260b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    }
261f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
262b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    /**
263b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * This runnable runs a single frame of the smooth scroll animation and posts the next frame
264b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * if necessary.
265b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     */
266b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk Runnable mSmoothSnapNextFrameRunnable = new Runnable() {
267b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        @Override
268b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        public void run() {
269b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            if (mFastScrollFrameIndex < mFastScrollFrames.length) {
270b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]);
271b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                mFastScrollFrameIndex++;
272b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                postOnAnimation(mSmoothSnapNextFrameRunnable);
273b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            } else {
274b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                // Animation completed, set the fast scroll state on the target view
275b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
276b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                if (vh != null &&
277b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                        vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView &&
278b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                        mLastFastScrollFocusedView != vh.itemView) {
279b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    mLastFastScrollFocusedView =
280b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                            (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
281b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    mLastFastScrollFocusedView.setFastScrollFocused(true, true);
282b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                }
283f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            }
284f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        }
285b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    };
286b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
287b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    /**
288b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * Smoothly snaps to a given position.  We do this manually by calculating the keyframes
289b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * ourselves and animating the scroll on the recycler view.
290b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     */
291b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private void smoothSnapToPosition(final int position, ScrollPositionState scrollPosState) {
292b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        removeCallbacks(mSmoothSnapNextFrameRunnable);
293b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
294b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // Calculate the full animation from the current scroll position to the final scroll
295b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // position, and then run the animation for the duration.
296b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
297b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int curScrollY = getPaddingTop() + predictionBarHeight +
298b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
299b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int newScrollY = getScrollAtPosition(position, scrollPosState.rowHeight);
300b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int numFrames = mFastScrollFrames.length;
301b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        for (int i = 0; i < numFrames; i++) {
302b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            // TODO(winsonc): We can interpolate this as well.
303b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mFastScrollFrames[i] = (newScrollY - curScrollY) / numFrames;
304b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        }
305b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        mFastScrollFrameIndex = 0;
306b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        postOnAnimation(mSmoothSnapNextFrameRunnable);
307f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    }
308ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung
309ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung    /**
310b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * Returns the current scroll state of the apps rows, not including the prediction
311b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * bar.
312ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung     */
313ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung    private void getCurScrollState(ScrollPositionState stateOut,
314ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            List<AlphabeticalAppsList.AdapterItem> items) {
315ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        stateOut.rowIndex = -1;
316ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        stateOut.rowTopOffset = -1;
317ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        stateOut.rowHeight = -1;
318532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung
319ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        // Return early if there are no items or we haven't been measured
320ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        if (items.isEmpty() || mNumAppsPerRow == 0) {
321532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung            return;
322532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung        }
323532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung
324ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        int childCount = getChildCount();
325ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        for (int i = 0; i < childCount; i++) {
326ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            View child = getChildAt(i);
327ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            int position = getChildPosition(child);
328ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            if (position != NO_POSITION) {
329ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                AlphabeticalAppsList.AdapterItem item = items.get(position);
3305f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung                if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
331b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    stateOut.rowIndex = item.rowIndex;
332ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                    stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
333ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                    stateOut.rowHeight = child.getHeight();
334ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                    break;
335ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                }
336ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            }
337ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        }
338ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung    }
339b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
340b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    /**
341b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * Returns the scrollY for the given position in the adapter.
342b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     */
343b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private int getScrollAtPosition(int position, int rowHeight) {
344b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
345b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
346b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
347b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            return getPaddingTop() + predictionBarHeight + item.rowIndex * rowHeight;
348b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        } else {
349b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            return 0;
350b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        }
351b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    }
35293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung}
353