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 android.animation.ValueAnimator;
19 import android.content.Context;
20 import android.graphics.Canvas;
21 import android.graphics.Paint;
22 import android.graphics.Point;
23 import android.graphics.Rect;
24 import android.os.Bundle;
25 import android.os.Process;
26 import android.text.Selection;
27 import android.text.SpannableStringBuilder;
28 import android.util.AttributeSet;
29 import android.view.KeyEvent;
30 import android.view.LayoutInflater;
31 import android.view.MotionEvent;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.view.WindowInsets;
35 import androidx.annotation.NonNull;
36 import androidx.annotation.Nullable;
37 import androidx.annotation.StringRes;
38 import androidx.dynamicanimation.animation.DynamicAnimation;
39 import androidx.recyclerview.widget.LinearLayoutManager;
40 import androidx.recyclerview.widget.RecyclerView;
41 import com.android.launcher3.AppInfo;
42 import com.android.launcher3.DeviceProfile;
43 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
44 import com.android.launcher3.DragSource;
45 import com.android.launcher3.DropTarget.DragObject;
46 import com.android.launcher3.Insettable;
47 import com.android.launcher3.InsettableFrameLayout;
48 import com.android.launcher3.ItemInfo;
49 import com.android.launcher3.Launcher;
50 import com.android.launcher3.LauncherState;
51 import com.android.launcher3.R;
52 import com.android.launcher3.Utilities;
53 import com.android.launcher3.compat.AccessibilityManagerCompat;
54 import com.android.launcher3.config.FeatureFlags;
55 import com.android.launcher3.keyboard.FocusedItemDecorator;
56 import com.android.launcher3.testing.TestProtocol;
57 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
58 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
59 import com.android.launcher3.util.ItemInfoMatcher;
60 import com.android.launcher3.util.MultiValueAlpha;
61 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
62 import com.android.launcher3.util.Themes;
63 import com.android.launcher3.views.BottomUserEducationView;
64 import com.android.launcher3.views.RecyclerViewFastScroller;
65 import com.android.launcher3.views.SpringRelativeLayout;
66 
67 /**
68  * The all apps view container.
69  */
70 public class AllAppsContainerView extends SpringRelativeLayout implements DragSource,
71         Insettable, OnDeviceProfileChangeListener {
72 
73     private static final float FLING_VELOCITY_MULTIPLIER = 135f;
74     // Starts the springs after at least 55% of the animation has passed.
75     private static final float FLING_ANIMATION_THRESHOLD = 0.55f;
76     private static final int ALPHA_CHANNEL_COUNT = 2;
77 
78     private final Launcher mLauncher;
79     private final AdapterHolder[] mAH;
80     private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
81     private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
82     private final AllAppsStore mAllAppsStore = new AllAppsStore();
83 
84     private final Paint mNavBarScrimPaint;
85     private int mNavBarScrimHeight = 0;
86 
87     private SearchUiManager mSearchUiManager;
88     private View mSearchContainer;
89     private AllAppsPagedView mViewPager;
90     private FloatingHeaderView mHeader;
91 
92     private SpannableStringBuilder mSearchQueryBuilder = null;
93 
94     private boolean mUsingTabs;
95     private boolean mSearchModeWhileUsingTabs = false;
96 
97     private RecyclerViewFastScroller mTouchHandler;
98     private final Point mFastScrollerOffset = new Point();
99 
100     private final MultiValueAlpha mMultiValueAlpha;
101 
AllAppsContainerView(Context context)102     public AllAppsContainerView(Context context) {
103         this(context, null);
104     }
105 
AllAppsContainerView(Context context, AttributeSet attrs)106     public AllAppsContainerView(Context context, AttributeSet attrs) {
107         this(context, attrs, 0);
108     }
109 
AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr)110     public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
111         super(context, attrs, defStyleAttr);
112 
113         mLauncher = Launcher.getLauncher(context);
114         mLauncher.addOnDeviceProfileChangeListener(this);
115 
116         mSearchQueryBuilder = new SpannableStringBuilder();
117         Selection.setSelection(mSearchQueryBuilder, 0);
118 
119         mAH = new AdapterHolder[2];
120         mAH[AdapterHolder.MAIN] = new AdapterHolder(false /* isWork */);
121         mAH[AdapterHolder.WORK] = new AdapterHolder(true /* isWork */);
122 
123         mNavBarScrimPaint = new Paint();
124         mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
125 
126         mAllAppsStore.addUpdateListener(this::onAppsUpdated);
127 
128         addSpringView(R.id.all_apps_header);
129         addSpringView(R.id.apps_list_view);
130         addSpringView(R.id.all_apps_tabs_view_pager);
131 
132         mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT);
133     }
134 
getAppsStore()135     public AllAppsStore getAppsStore() {
136         return mAllAppsStore;
137     }
138 
getAlphaProperty(int index)139     public AlphaProperty getAlphaProperty(int index) {
140         return mMultiValueAlpha.getProperty(index);
141     }
142 
143     @Override
setDampedScrollShift(float shift)144     protected void setDampedScrollShift(float shift) {
145         // Bound the shift amount to avoid content from drawing on top (Y-val) of the QSB.
146         float maxShift = getSearchView().getHeight() / 2f;
147         super.setDampedScrollShift(Utilities.boundToRange(shift, -maxShift, maxShift));
148     }
149 
150     @Override
onDeviceProfileChanged(DeviceProfile dp)151     public void onDeviceProfileChanged(DeviceProfile dp) {
152         for (AdapterHolder holder : mAH) {
153             if (holder.recyclerView != null) {
154                 // Remove all views and clear the pool, while keeping the data same. After this
155                 // call, all the viewHolders will be recreated.
156                 holder.recyclerView.swapAdapter(holder.recyclerView.getAdapter(), true);
157                 holder.recyclerView.getRecycledViewPool().clear();
158             }
159         }
160     }
161 
onAppsUpdated()162     private void onAppsUpdated() {
163         if (FeatureFlags.ALL_APPS_TABS_ENABLED) {
164             boolean hasWorkApps = false;
165             for (AppInfo app : mAllAppsStore.getApps()) {
166                 if (mWorkMatcher.matches(app, null)) {
167                     hasWorkApps = true;
168                     break;
169                 }
170             }
171             rebindAdapters(hasWorkApps);
172         }
173     }
174 
175     /**
176      * Returns whether the view itself will handle the touch event or not.
177      */
shouldContainerScroll(MotionEvent ev)178     public boolean shouldContainerScroll(MotionEvent ev) {
179         // IF the MotionEvent is inside the search box, and the container keeps on receiving
180         // touch input, container should move down.
181         if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) {
182             return true;
183         }
184         AllAppsRecyclerView rv = getActiveRecyclerView();
185         if (rv == null) {
186             return true;
187         }
188         if (rv.getScrollbar().getThumbOffsetY() >= 0 &&
189                 mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) {
190             return false;
191         }
192         return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
193     }
194 
195     @Override
onInterceptTouchEvent(MotionEvent ev)196     public boolean onInterceptTouchEvent(MotionEvent ev) {
197 
198         // The AllAppsContainerView houses the QSB and is hence visible from the Workspace
199         // Overview states. We shouldn't intercept for the scrubber in these cases.
200         if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
201             mTouchHandler = null;
202             return false;
203         }
204 
205         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
206             AllAppsRecyclerView rv = getActiveRecyclerView();
207             if (rv != null &&
208                     rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
209                 mTouchHandler = rv.getScrollbar();
210             } else {
211                 mTouchHandler = null;
212             }
213         }
214         if (mTouchHandler != null) {
215             return mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
216         }
217         return false;
218     }
219 
220     @Override
onTouchEvent(MotionEvent ev)221     public boolean onTouchEvent(MotionEvent ev) {
222         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
223             AllAppsRecyclerView rv = getActiveRecyclerView();
224             if (rv != null && rv.getScrollbar()
225                 .isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
226                 mTouchHandler = rv.getScrollbar();
227             } else {
228                 mTouchHandler = null;
229             }
230         }
231 
232         if (mTouchHandler != null) {
233             mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
234             return true;
235         }
236         return false;
237     }
238 
getDescription()239     public String getDescription() {
240         @StringRes int descriptionRes;
241         if (mUsingTabs) {
242             descriptionRes =
243                     mViewPager.getNextPage() == 0
244                             ? R.string.all_apps_button_personal_label
245                             : R.string.all_apps_button_work_label;
246         } else {
247             descriptionRes = R.string.all_apps_button_label;
248         }
249         return getContext().getString(descriptionRes);
250     }
251 
getActiveRecyclerView()252     public AllAppsRecyclerView getActiveRecyclerView() {
253         if (!mUsingTabs || mViewPager.getNextPage() == 0) {
254             return mAH[AdapterHolder.MAIN].recyclerView;
255         } else {
256             return mAH[AdapterHolder.WORK].recyclerView;
257         }
258     }
259 
260     /**
261      * Resets the state of AllApps.
262      */
reset(boolean animate)263     public void reset(boolean animate) {
264         for (int i = 0; i < mAH.length; i++) {
265             if (mAH[i].recyclerView != null) {
266                 mAH[i].recyclerView.scrollToTop();
267             }
268         }
269         if (isHeaderVisible()) {
270             mHeader.reset(animate);
271         }
272         // Reset the search bar and base recycler view after transitioning home
273         mSearchUiManager.resetSearch();
274     }
275 
276     @Override
onFinishInflate()277     protected void onFinishInflate() {
278         super.onFinishInflate();
279 
280         // This is a focus listener that proxies focus from a view into the list view.  This is to
281         // work around the search box from getting first focus and showing the cursor.
282         setOnFocusChangeListener((v, hasFocus) -> {
283             if (hasFocus && getActiveRecyclerView() != null) {
284                 getActiveRecyclerView().requestFocus();
285             }
286         });
287 
288         mHeader = findViewById(R.id.all_apps_header);
289         rebindAdapters(mUsingTabs, true /* force */);
290 
291         mSearchContainer = findViewById(R.id.search_container_all_apps);
292         mSearchUiManager = (SearchUiManager) mSearchContainer;
293         mSearchUiManager.initialize(this);
294     }
295 
getSearchUiManager()296     public SearchUiManager getSearchUiManager() {
297         return mSearchUiManager;
298     }
299 
300     @Override
dispatchKeyEvent(KeyEvent event)301     public boolean dispatchKeyEvent(KeyEvent event) {
302         mSearchUiManager.preDispatchKeyEvent(event);
303         return super.dispatchKeyEvent(event);
304     }
305 
306     @Override
onDropCompleted(View target, DragObject d, boolean success)307     public void onDropCompleted(View target, DragObject d, boolean success) { }
308 
309     @Override
fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent)310     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
311         if (getApps().hasFilter()) {
312             targetParent.containerType = ContainerType.SEARCHRESULT;
313         } else {
314             targetParent.containerType = ContainerType.ALLAPPS;
315         }
316     }
317 
318     @Override
setInsets(Rect insets)319     public void setInsets(Rect insets) {
320         DeviceProfile grid = mLauncher.getDeviceProfile();
321         int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx
322                 + grid.cellLayoutPaddingLeftRightPx;
323 
324         for (int i = 0; i < mAH.length; i++) {
325             mAH[i].adapter.setAppsPerRow(grid.inv.numAllAppsColumns);
326             mAH[i].padding.bottom = insets.bottom;
327             mAH[i].padding.left = mAH[i].padding.right = leftRightPadding;
328             mAH[i].applyPadding();
329         }
330 
331         ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
332         if (grid.isVerticalBarLayout()) {
333             mlp.leftMargin = insets.left;
334             mlp.rightMargin = insets.right;
335             setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
336         } else {
337             mlp.leftMargin = mlp.rightMargin = 0;
338             setPadding(0, 0, 0, 0);
339         }
340         setLayoutParams(mlp);
341 
342         InsettableFrameLayout.dispatchInsets(this, insets);
343         mLauncher.getAllAppsController()
344                 .setScrollRangeDelta(mSearchUiManager.getScrollRangeDelta(insets));
345     }
346 
347     @Override
dispatchApplyWindowInsets(WindowInsets insets)348     public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
349         if (Utilities.ATLEAST_Q) {
350             mNavBarScrimHeight = insets.getTappableElementInsets().bottom;
351         } else {
352             mNavBarScrimHeight = insets.getStableInsetBottom();
353         }
354         return super.dispatchApplyWindowInsets(insets);
355     }
356 
357     @Override
dispatchDraw(Canvas canvas)358     protected void dispatchDraw(Canvas canvas) {
359         super.dispatchDraw(canvas);
360 
361         if (mNavBarScrimHeight > 0) {
362             canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
363                     mNavBarScrimPaint);
364         }
365     }
366 
367     @Override
getCanvasClipTopForOverscroll()368     public int getCanvasClipTopForOverscroll() {
369         // Do not clip if the QSB is attached to the spring, otherwise the QSB will get clipped.
370         return mSpringViews.get(getSearchView().getId()) ? 0 : mHeader.getTop();
371     }
372 
rebindAdapters(boolean showTabs)373     private void rebindAdapters(boolean showTabs) {
374         rebindAdapters(showTabs, false /* force */);
375     }
376 
rebindAdapters(boolean showTabs, boolean force)377     private void rebindAdapters(boolean showTabs, boolean force) {
378         if (showTabs == mUsingTabs && !force) {
379             return;
380         }
381         replaceRVContainer(showTabs);
382         mUsingTabs = showTabs;
383 
384         mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.MAIN].recyclerView);
385         mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.WORK].recyclerView);
386 
387         if (mUsingTabs) {
388             mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
389             mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
390             onTabChanged(mViewPager.getNextPage());
391         } else {
392             mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
393             mAH[AdapterHolder.WORK].recyclerView = null;
394         }
395         setupHeader();
396 
397         mAllAppsStore.registerIconContainer(mAH[AdapterHolder.MAIN].recyclerView);
398         mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView);
399     }
400 
replaceRVContainer(boolean showTabs)401     private void replaceRVContainer(boolean showTabs) {
402         for (int i = 0; i < mAH.length; i++) {
403             if (mAH[i].recyclerView != null) {
404                 mAH[i].recyclerView.setLayoutManager(null);
405             }
406         }
407         View oldView = getRecyclerViewContainer();
408         int index = indexOfChild(oldView);
409         removeView(oldView);
410         int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
411         View newView = LayoutInflater.from(getContext()).inflate(layout, this, false);
412         addView(newView, index);
413         if (showTabs) {
414             mViewPager = (AllAppsPagedView) newView;
415             mViewPager.initParentViews(this);
416             mViewPager.getPageIndicator().setContainerView(this);
417         } else {
418             mViewPager = null;
419         }
420     }
421 
getRecyclerViewContainer()422     public View getRecyclerViewContainer() {
423         return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
424     }
425 
onTabChanged(int pos)426     public void onTabChanged(int pos) {
427         mHeader.setMainActive(pos == 0);
428         reset(true /* animate */);
429         if (mAH[pos].recyclerView != null) {
430             mAH[pos].recyclerView.bindFastScrollbar();
431 
432             findViewById(R.id.tab_personal)
433                     .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
434             findViewById(R.id.tab_work)
435                     .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
436 
437         }
438         if (pos == AdapterHolder.WORK) {
439             BottomUserEducationView.showIfNeeded(mLauncher);
440         }
441     }
442 
443     // Used by tests only
isDescendantViewVisible(int viewId)444     private boolean isDescendantViewVisible(int viewId) {
445         final View view = findViewById(viewId);
446         if (view == null) return false;
447 
448         if (!view.isShown()) return false;
449 
450         return view.getGlobalVisibleRect(new Rect());
451     }
452 
453     // Used by tests only
isPersonalTabVisible()454     public boolean isPersonalTabVisible() {
455         return isDescendantViewVisible(R.id.tab_personal);
456     }
457 
458     // Used by tests only
isWorkTabVisible()459     public boolean isWorkTabVisible() {
460         return isDescendantViewVisible(R.id.tab_work);
461     }
462 
getApps()463     public AlphabeticalAppsList getApps() {
464         return mAH[AdapterHolder.MAIN].appsList;
465     }
466 
getFloatingHeaderView()467     public FloatingHeaderView getFloatingHeaderView() {
468         return mHeader;
469     }
470 
getSearchView()471     public View getSearchView() {
472         return mSearchContainer;
473     }
474 
getContentView()475     public View getContentView() {
476         return mViewPager == null ? getActiveRecyclerView() : mViewPager;
477     }
478 
getScrollBar()479     public RecyclerViewFastScroller getScrollBar() {
480         AllAppsRecyclerView rv = getActiveRecyclerView();
481         return rv == null ? null : rv.getScrollbar();
482     }
483 
setupHeader()484     public void setupHeader() {
485         mHeader.setVisibility(View.VISIBLE);
486         mHeader.setup(mAH, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView == null);
487 
488         int padding = mHeader.getMaxTranslation();
489         for (int i = 0; i < mAH.length; i++) {
490             mAH[i].padding.top = padding;
491             mAH[i].applyPadding();
492         }
493     }
494 
setLastSearchQuery(String query)495     public void setLastSearchQuery(String query) {
496         for (int i = 0; i < mAH.length; i++) {
497             mAH[i].adapter.setLastSearchQuery(query);
498         }
499         if (mUsingTabs) {
500             mSearchModeWhileUsingTabs = true;
501             rebindAdapters(false); // hide tabs
502         }
503     }
504 
onClearSearchResult()505     public void onClearSearchResult() {
506         if (mSearchModeWhileUsingTabs) {
507             rebindAdapters(true); // show tabs
508             mSearchModeWhileUsingTabs = false;
509         }
510     }
511 
onSearchResultsChanged()512     public void onSearchResultsChanged() {
513         for (int i = 0; i < mAH.length; i++) {
514             if (mAH[i].recyclerView != null) {
515                 mAH[i].recyclerView.onSearchResultsChanged();
516             }
517         }
518     }
519 
setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled)520     public void setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled) {
521         for (int i = 0; i < mAH.length; i++) {
522             mAH[i].applyVerticalFadingEdgeEnabled(enabled);
523         }
524     }
525 
addElevationController(RecyclerView.OnScrollListener scrollListener)526     public void addElevationController(RecyclerView.OnScrollListener scrollListener) {
527         if (!mUsingTabs) {
528             mAH[AdapterHolder.MAIN].recyclerView.addOnScrollListener(scrollListener);
529         }
530     }
531 
isHeaderVisible()532     public boolean isHeaderVisible() {
533         return mHeader != null && mHeader.getVisibility() == View.VISIBLE;
534     }
535 
onScrollUpEnd()536     public void onScrollUpEnd() {
537         highlightWorkTabIfNecessary();
538     }
539 
highlightWorkTabIfNecessary()540     void highlightWorkTabIfNecessary() {
541         if (mUsingTabs) {
542             ((PersonalWorkSlidingTabStrip) findViewById(R.id.tabs))
543                     .highlightWorkTabIfNecessary();
544         }
545     }
546 
547     /**
548      * Adds an update listener to {@param animator} that adds springs to the animation.
549      */
addSpringFromFlingUpdateListener(ValueAnimator animator, float velocity)550     public void addSpringFromFlingUpdateListener(ValueAnimator animator, float velocity) {
551         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
552             boolean shouldSpring = true;
553 
554             @Override
555             public void onAnimationUpdate(ValueAnimator valueAnimator) {
556                 if (shouldSpring
557                         && valueAnimator.getAnimatedFraction() >= FLING_ANIMATION_THRESHOLD) {
558                     int searchViewId = getSearchView().getId();
559                     addSpringView(searchViewId);
560 
561                     finishWithShiftAndVelocity(1, velocity * FLING_VELOCITY_MULTIPLIER,
562                             new DynamicAnimation.OnAnimationEndListener() {
563                                 @Override
564                                 public void onAnimationEnd(DynamicAnimation animation,
565                                         boolean canceled, float value, float velocity) {
566                                     removeSpringView(searchViewId);
567                                 }
568                             });
569 
570                     shouldSpring = false;
571                 }
572             }
573         });
574     }
575 
576     @Override
getDrawingRect(Rect outRect)577     public void getDrawingRect(Rect outRect) {
578         super.getDrawingRect(outRect);
579         outRect.offset(0, (int) getTranslationY());
580     }
581 
582     public class AdapterHolder {
583         public static final int MAIN = 0;
584         public static final int WORK = 1;
585 
586         public final AllAppsGridAdapter adapter;
587         final LinearLayoutManager layoutManager;
588         final AlphabeticalAppsList appsList;
589         final Rect padding = new Rect();
590         AllAppsRecyclerView recyclerView;
591         boolean verticalFadingEdge;
592 
AdapterHolder(boolean isWork)593         AdapterHolder(boolean isWork) {
594             appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork);
595             adapter = new AllAppsGridAdapter(mLauncher, appsList);
596             appsList.setAdapter(adapter);
597             layoutManager = adapter.getLayoutManager();
598         }
599 
setup(@onNull View rv, @Nullable ItemInfoMatcher matcher)600         void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
601             appsList.updateItemFilter(matcher);
602             recyclerView = (AllAppsRecyclerView) rv;
603             recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
604             recyclerView.setApps(appsList, mUsingTabs);
605             recyclerView.setLayoutManager(layoutManager);
606             recyclerView.setAdapter(adapter);
607             recyclerView.setHasFixedSize(true);
608             // No animations will occur when changes occur to the items in this RecyclerView.
609             recyclerView.setItemAnimator(null);
610             FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(recyclerView);
611             recyclerView.addItemDecoration(focusedItemDecorator);
612             adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
613             applyVerticalFadingEdgeEnabled(verticalFadingEdge);
614             applyPadding();
615         }
616 
applyPadding()617         void applyPadding() {
618             if (recyclerView != null) {
619                 recyclerView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
620             }
621         }
622 
applyVerticalFadingEdgeEnabled(boolean enabled)623         public void applyVerticalFadingEdgeEnabled(boolean enabled) {
624             verticalFadingEdge = enabled;
625             mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs
626                     && verticalFadingEdge);
627         }
628     }
629 
630     @Override
performAccessibilityAction(int action, Bundle arguments)631     public boolean performAccessibilityAction(int action, Bundle arguments) {
632         if (AccessibilityManagerCompat.processTestRequest(
633                 mLauncher, TestProtocol.GET_SCROLL_MESSAGE, action, arguments,
634                 response ->
635                         response.putInt(TestProtocol.SCROLL_Y_FIELD,
636                                 getActiveRecyclerView().getCurrentScrollY()))) {
637             return true;
638         }
639 
640         return super.performAccessibilityAction(action, arguments);
641     }
642 }
643