• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.allapps;
17 
18 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo;
19 import static com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
20 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_DOWN;
21 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_UP;
22 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SCROLLED_UNKNOWN_DIRECTION;
23 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SEARCH_SCROLLED_DOWN;
24 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SEARCH_SCROLLED_UP;
25 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN;
26 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END;
27 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_FAB_BUTTON_COLLAPSE;
28 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_FAB_BUTTON_EXTEND;
29 import static com.android.launcher3.util.LogConfig.SEARCH_LOGGING;
30 
31 import android.content.Context;
32 import android.graphics.Canvas;
33 import android.util.AttributeSet;
34 import android.util.Log;
35 
36 import androidx.recyclerview.widget.RecyclerView;
37 
38 import com.android.launcher3.DeviceProfile;
39 import com.android.launcher3.ExtendedEditText;
40 import com.android.launcher3.FastScrollRecyclerView;
41 import com.android.launcher3.LauncherAppState;
42 import com.android.launcher3.R;
43 import com.android.launcher3.Utilities;
44 import com.android.launcher3.logging.StatsLogManager;
45 import com.android.launcher3.views.ActivityContext;
46 
47 import java.util.List;
48 
49 /**
50  * A RecyclerView with custom fast scroll support for the all apps view.
51  */
52 public class AllAppsRecyclerView extends FastScrollRecyclerView {
53     protected static final String TAG = "AllAppsRecyclerView";
54     private static final boolean DEBUG = false;
55     private static final boolean DEBUG_LATENCY = Utilities.isPropertyEnabled(SEARCH_LOGGING);
56 
57     protected final int mNumAppsPerRow;
58     private final AllAppsFastScrollHelper mFastScrollHelper;
59     private int mCumulativeVerticalScroll;
60 
61     protected AlphabeticalAppsList<?> mApps;
62 
AllAppsRecyclerView(Context context)63     public AllAppsRecyclerView(Context context) {
64         this(context, null);
65     }
66 
AllAppsRecyclerView(Context context, AttributeSet attrs)67     public AllAppsRecyclerView(Context context, AttributeSet attrs) {
68         this(context, attrs, 0);
69     }
70 
AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr)71     public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
72         this(context, attrs, defStyleAttr, 0);
73     }
74 
AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)75     public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
76             int defStyleRes) {
77         super(context, attrs, defStyleAttr);
78         mNumAppsPerRow = LauncherAppState.getIDP(context).numColumns;
79         mFastScrollHelper = new AllAppsFastScrollHelper(this);
80     }
81 
82     /**
83      * Sets the list of apps in this view, used to determine the fastscroll position.
84      */
setApps(AlphabeticalAppsList<?> apps)85     public void setApps(AlphabeticalAppsList<?> apps) {
86         mApps = apps;
87     }
88 
getApps()89     public AlphabeticalAppsList<?> getApps() {
90         return mApps;
91     }
92 
updatePoolSize()93     protected void updatePoolSize() {
94         DeviceProfile grid = ActivityContext.lookupContext(getContext()).getDeviceProfile();
95         RecyclerView.RecycledViewPool pool = getRecycledViewPool();
96         int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
97         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
98         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
99         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows
100                 * (mNumAppsPerRow + 1));
101     }
102 
103     @Override
onDraw(Canvas c)104     public void onDraw(Canvas c) {
105         if (DEBUG) {
106             Log.d(TAG, "onDraw at = " + System.currentTimeMillis());
107         }
108         if (DEBUG_LATENCY) {
109             Log.d(SEARCH_LOGGING,  getClass().getSimpleName() + " onDraw; time stamp = "
110                     + System.currentTimeMillis());
111         }
112         super.onDraw(c);
113     }
114 
115     @Override
onSizeChanged(int w, int h, int oldw, int oldh)116     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
117         updatePoolSize();
118     }
119 
onSearchResultsChanged()120     public void onSearchResultsChanged() {
121         // Always scroll the view to the top so the user can see the changed results
122         scrollToTop();
123     }
124 
125     @Override
onScrollStateChanged(int state)126     public void onScrollStateChanged(int state) {
127         super.onScrollStateChanged(state);
128 
129         StatsLogManager mgr = ActivityContext.lookupContext(getContext()).getStatsLogManager();
130         switch (state) {
131             case SCROLL_STATE_DRAGGING:
132                 mCumulativeVerticalScroll = 0;
133                 requestFocus();
134                 mgr.logger().sendToInteractionJankMonitor(
135                         LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN, this);
136                 ActivityContext.lookupContext(getContext()).hideKeyboard();
137                 break;
138             case SCROLL_STATE_IDLE:
139                 mgr.logger().sendToInteractionJankMonitor(
140                         LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END, this);
141                 logCumulativeVerticalScroll();
142                 break;
143         }
144     }
145 
146     @Override
onScrolled(int dx, int dy)147     public void onScrolled(int dx, int dy) {
148         super.onScrolled(dx, dy);
149         mCumulativeVerticalScroll += dy;
150     }
151 
152     /**
153      * Maps the touch (from 0..1) to the adapter position that should be visible.
154      */
155     @Override
scrollToPositionAtProgress(float touchFraction)156     public String scrollToPositionAtProgress(float touchFraction) {
157         int rowCount = mApps.getNumAppRows();
158         if (rowCount == 0) {
159             return "";
160         }
161 
162         // Find the fastscroll section that maps to this touch fraction
163         List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
164                 mApps.getFastScrollerSections();
165         int count = fastScrollSections.size();
166         if (count == 0) {
167             return "";
168         }
169         int index = Utilities.boundToRange((int) (touchFraction * count), 0, count - 1);
170         AlphabeticalAppsList.FastScrollSectionInfo section = fastScrollSections.get(index);
171         mFastScrollHelper.smoothScrollToSection(section);
172         return section.sectionName;
173     }
174 
175     @Override
onFastScrollCompleted()176     public void onFastScrollCompleted() {
177         super.onFastScrollCompleted();
178         mFastScrollHelper.onFastScrollCompleted();
179     }
180 
181     @Override
isPaddingOffsetRequired()182     protected boolean isPaddingOffsetRequired() {
183         return true;
184     }
185 
186     @Override
getTopPaddingOffset()187     protected int getTopPaddingOffset() {
188         return -getPaddingTop();
189     }
190 
191     /**
192      * Updates the bounds for the scrollbar.
193      */
194     @Override
onUpdateScrollbar(int dy)195     public void onUpdateScrollbar(int dy) {
196         if (mApps == null) {
197             return;
198         }
199         List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
200 
201         // Skip early if there are no items or we haven't been measured
202         if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
203             mScrollbar.setThumbOffsetY(-1);
204             return;
205         }
206 
207         // Skip early if, there no child laid out in the container.
208         int scrollY = computeVerticalScrollOffset();
209         if (scrollY < 0) {
210             mScrollbar.setThumbOffsetY(-1);
211             return;
212         }
213 
214         // Only show the scrollbar if there is height to be scrolled
215         int availableScrollBarHeight = getAvailableScrollBarHeight();
216         int availableScrollHeight = getAvailableScrollHeight();
217         if (availableScrollHeight <= 0) {
218             mScrollbar.setThumbOffsetY(-1);
219             return;
220         }
221 
222         if (mScrollbar.isThumbDetached()) {
223             if (!mScrollbar.isDraggingThumb()) {
224                 // Calculate the current scroll position, the scrollY of the recycler view accounts
225                 // for the view padding, while the scrollBarY is drawn right up to the background
226                 // padding (ignoring padding)
227                 int scrollBarY = (int)
228                         (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
229 
230                 int thumbScrollY = mScrollbar.getThumbOffsetY();
231                 int diffScrollY = scrollBarY - thumbScrollY;
232                 if (diffScrollY * dy > 0f) {
233                     // User is scrolling in the same direction the thumb needs to catch up to the
234                     // current scroll position.  We do this by mapping the difference in movement
235                     // from the original scroll bar position to the difference in movement necessary
236                     // in the detached thumb position to ensure that both speed towards the same
237                     // position at either end of the list.
238                     if (dy < 0) {
239                         int offset = (int) ((dy * thumbScrollY) / (float) scrollBarY);
240                         thumbScrollY += Math.max(offset, diffScrollY);
241                     } else {
242                         int offset = (int) ((dy * (availableScrollBarHeight - thumbScrollY)) /
243                                 (float) (availableScrollBarHeight - scrollBarY));
244                         thumbScrollY += Math.min(offset, diffScrollY);
245                     }
246                     thumbScrollY = Math.max(0, Math.min(availableScrollBarHeight, thumbScrollY));
247                     mScrollbar.setThumbOffsetY(thumbScrollY);
248                     if (scrollBarY == thumbScrollY) {
249                         mScrollbar.reattachThumbToScroll();
250                     }
251                 } else {
252                     // User is scrolling in an opposite direction to the direction that the thumb
253                     // needs to catch up to the scroll position.  Do nothing except for updating
254                     // the scroll bar x to match the thumb width.
255                     mScrollbar.setThumbOffsetY(thumbScrollY);
256                 }
257             }
258         } else {
259             synchronizeScrollBarThumbOffsetToViewScroll(scrollY, availableScrollHeight);
260         }
261     }
262 
263     @Override
getScrollBarTop()264     public int getScrollBarTop() {
265         return ActivityContext.lookupContext(getContext()).getAppsView().isSearchSupported()
266                 ? getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding)
267                 : 0;
268     }
269 
270     @Override
getScrollBarMarginBottom()271     public int getScrollBarMarginBottom() {
272         return getRootWindowInsets() == null ? 0
273                 : getRootWindowInsets().getSystemWindowInsetBottom();
274     }
275 
276     @Override
hasOverlappingRendering()277     public boolean hasOverlappingRendering() {
278         return false;
279     }
280 
logCumulativeVerticalScroll()281     private void logCumulativeVerticalScroll() {
282         ActivityContext context = ActivityContext.lookupContext(getContext());
283         StatsLogManager mgr = context.getStatsLogManager();
284         ActivityAllAppsContainerView<?> appsView = context.getAppsView();
285         ExtendedEditText editText = appsView.getSearchUiManager().getEditText();
286         ContainerInfo containerInfo = ContainerInfo.newBuilder().setSearchResultContainer(
287                 SearchResultContainer
288                         .newBuilder()
289                         .setQueryLength((editText == null) ? -1 : editText.length())).build();
290         if (mCumulativeVerticalScroll == 0) {
291             // mCumulativeVerticalScroll == 0 when user comes back to original position, we
292             // don't know the direction of scrolling.
293             mgr.logger().withContainerInfo(containerInfo).log(
294                     LAUNCHER_ALLAPPS_SCROLLED_UNKNOWN_DIRECTION);
295             return;
296         } else if (appsView.isSearching()) {
297             // In search results page
298             mgr.logger().withContainerInfo(containerInfo).log((mCumulativeVerticalScroll > 0)
299                     ? LAUNCHER_ALLAPPS_SEARCH_SCROLLED_DOWN
300                     : LAUNCHER_ALLAPPS_SEARCH_SCROLLED_UP);
301             return;
302         } else if (appsView.mViewPager != null) {
303             int currentPage = appsView.mViewPager.getCurrentPage();
304             if (currentPage == ActivityAllAppsContainerView.AdapterHolder.WORK) {
305                 // In work A-Z list
306                 mgr.logger().withContainerInfo(containerInfo).log((mCumulativeVerticalScroll > 0)
307                         ? LAUNCHER_WORK_FAB_BUTTON_COLLAPSE
308                         : LAUNCHER_WORK_FAB_BUTTON_EXTEND);
309                 return;
310             }
311         }
312         // In personal A-Z list
313         mgr.logger().withContainerInfo(containerInfo).log((mCumulativeVerticalScroll > 0)
314                 ? LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_DOWN
315                 : LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_UP);
316     }
317 }
318