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