• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.Flags.enableExpandingPauseWorkButton;
19 import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
20 import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
21 import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.WORK;
22 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
23 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD;
24 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD;
25 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_COUNT;
26 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB;
27 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB;
28 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
29 import static com.android.launcher3.util.ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE;
30 import static com.android.launcher3.views.RecyclerViewFastScroller.FastScrollerLocation.ALL_APPS_SCROLLER;
31 import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
32 
33 import android.animation.Animator;
34 import android.animation.AnimatorListenerAdapter;
35 import android.animation.ValueAnimator;
36 import android.content.Context;
37 import android.graphics.Canvas;
38 import android.graphics.Color;
39 import android.graphics.Outline;
40 import android.graphics.Paint;
41 import android.graphics.Path;
42 import android.graphics.Path.Direction;
43 import android.graphics.Point;
44 import android.graphics.Rect;
45 import android.graphics.RectF;
46 import android.os.Bundle;
47 import android.os.Parcelable;
48 import android.os.Process;
49 import android.os.UserManager;
50 import android.util.AttributeSet;
51 import android.util.Log;
52 import android.util.SparseArray;
53 import android.view.KeyEvent;
54 import android.view.LayoutInflater;
55 import android.view.MotionEvent;
56 import android.view.View;
57 import android.view.ViewGroup;
58 import android.view.ViewOutlineProvider;
59 import android.view.WindowInsets;
60 import android.widget.Button;
61 import android.widget.RelativeLayout;
62 
63 import androidx.annotation.NonNull;
64 import androidx.annotation.Nullable;
65 import androidx.annotation.Px;
66 import androidx.annotation.VisibleForTesting;
67 import androidx.constraintlayout.widget.ConstraintLayout;
68 import androidx.core.graphics.ColorUtils;
69 import androidx.recyclerview.widget.RecyclerView;
70 
71 import com.android.launcher3.DeviceProfile;
72 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
73 import com.android.launcher3.DragSource;
74 import com.android.launcher3.DropTarget.DragObject;
75 import com.android.launcher3.Flags;
76 import com.android.launcher3.Insettable;
77 import com.android.launcher3.InsettableFrameLayout;
78 import com.android.launcher3.R;
79 import com.android.launcher3.Utilities;
80 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
81 import com.android.launcher3.allapps.search.AllAppsSearchUiDelegate;
82 import com.android.launcher3.allapps.search.SearchAdapterProvider;
83 import com.android.launcher3.config.FeatureFlags;
84 import com.android.launcher3.keyboard.FocusedItemDecorator;
85 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
86 import com.android.launcher3.model.StringCache;
87 import com.android.launcher3.model.data.ItemInfo;
88 import com.android.launcher3.pm.UserCache;
89 import com.android.launcher3.recyclerview.AllAppsRecyclerViewPool;
90 import com.android.launcher3.util.ItemInfoMatcher;
91 import com.android.launcher3.util.Preconditions;
92 import com.android.launcher3.util.Themes;
93 import com.android.launcher3.views.ActivityContext;
94 import com.android.launcher3.views.BaseDragLayer;
95 import com.android.launcher3.views.RecyclerViewFastScroller;
96 import com.android.launcher3.views.ScrimView;
97 import com.android.launcher3.views.SpringRelativeLayout;
98 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip;
99 import com.android.systemui.plugins.AllAppsRow;
100 
101 import java.util.ArrayList;
102 import java.util.Arrays;
103 import java.util.List;
104 import java.util.Optional;
105 import java.util.function.Predicate;
106 import java.util.stream.Stream;
107 
108 /**
109  * All apps container view with search support for use in a dragging activity.
110  *
111  * @param <T> Type of context inflating all apps.
112  */
113 public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
114         extends SpringRelativeLayout implements DragSource, Insettable,
115         OnDeviceProfileChangeListener, PersonalWorkSlidingTabStrip.OnActivePageChangedListener,
116         ScrimView.ScrimDrawingController {
117 
118 
119     private static final String TAG = "ActivityAllAppsContainerView";
120     public static final float PULL_MULTIPLIER = .02f;
121     public static final float FLING_VELOCITY_MULTIPLIER = 1200f;
122     protected static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page";
123     private static final long DEFAULT_SEARCH_TRANSITION_DURATION_MS = 300;
124     // Render the header protection at all times to debug clipping issues.
125     private static final boolean DEBUG_HEADER_PROTECTION = false;
126     /** Context of an activity or window that is inflating this container. */
127 
128     protected final T mActivityContext;
129     protected final List<AdapterHolder> mAH;
130     protected final Predicate<ItemInfo> mPersonalMatcher = ItemInfoMatcher.ofUser(
131             Process.myUserHandle());
132     protected WorkProfileManager mWorkManager;
133     protected final PrivateProfileManager mPrivateProfileManager;
134     protected final Point mFastScrollerOffset = new Point();
135     protected final int mScrimColor;
136     protected final float mHeaderThreshold;
137     protected final AllAppsSearchUiDelegate mSearchUiDelegate;
138 
139     // Used to animate Search results out and A-Z apps in, or vice-versa.
140     private final SearchTransitionController mSearchTransitionController;
141     private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
142     private final Rect mInsets = new Rect();
143     private final AllAppsStore<T> mAllAppsStore;
144     private final RecyclerView.OnScrollListener mScrollListener =
145             new RecyclerView.OnScrollListener() {
146                 @Override
147                 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
148                     updateHeaderScroll(recyclerView.computeVerticalScrollOffset());
149                 }
150             };
151     private final Paint mNavBarScrimPaint;
152     private final int mHeaderProtectionColor;
153     private final int mPrivateSpaceBottomExtraSpace;
154     private final Path mTmpPath = new Path();
155     private final RectF mTmpRectF = new RectF();
156     protected AllAppsPagedView mViewPager;
157     protected FloatingHeaderView mHeader;
158     protected final List<AllAppsRow> mAdditionalHeaderRows = new ArrayList<>();
159     protected View mBottomSheetBackground;
160     protected RecyclerViewFastScroller mFastScroller;
161     private ConstraintLayout mFastScrollLetterLayout;
162 
163     /**
164      * View that defines the search box. Result is rendered inside {@link #mSearchRecyclerView}.
165      */
166     protected View mSearchContainer;
167     protected SearchUiManager mSearchUiManager;
168     protected boolean mUsingTabs;
169     protected RecyclerViewFastScroller mTouchHandler;
170 
171     /** {@code true} when rendered view is in search state instead of the scroll state. */
172     private boolean mIsSearching;
173     private boolean mRebindAdaptersAfterSearchAnimation;
174     private int mNavBarScrimHeight = 0;
175     private SearchRecyclerView mSearchRecyclerView;
176     protected SearchAdapterProvider<?> mMainAdapterProvider;
177     private View mBottomSheetHandleArea;
178     private boolean mHasWorkApps;
179     private boolean mHasPrivateApps;
180     private float[] mBottomSheetCornerRadii;
181     private ScrimView mScrimView;
182     private int mHeaderColor;
183     private int mBottomSheetBackgroundColor;
184     private float mBottomSheetBackgroundAlpha = 1f;
185     private int mTabsProtectionAlpha;
186     @Nullable private AllAppsTransitionController mAllAppsTransitionController;
187 
ActivityAllAppsContainerView(Context context)188     public ActivityAllAppsContainerView(Context context) {
189         this(context, null);
190     }
191 
ActivityAllAppsContainerView(Context context, AttributeSet attrs)192     public ActivityAllAppsContainerView(Context context, AttributeSet attrs) {
193         this(context, attrs, 0);
194     }
195 
ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr)196     public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
197         super(context, attrs, defStyleAttr);
198         mActivityContext = ActivityContext.lookupContext(context);
199         mAllAppsStore = new AllAppsStore<>(mActivityContext);
200 
201         mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
202         mHeaderThreshold = getResources().getDimensionPixelSize(
203                 R.dimen.dynamic_grid_cell_border_spacing);
204         mHeaderProtectionColor = Themes.getAttrColor(context, R.attr.allappsHeaderProtectionColor);
205 
206         mWorkManager = new WorkProfileManager(
207                 mActivityContext.getSystemService(UserManager.class),
208                 this,
209                 mActivityContext.getStatsLogManager(),
210                 UserCache.INSTANCE.get(mActivityContext));
211         mPrivateProfileManager = new PrivateProfileManager(
212                 mActivityContext.getSystemService(UserManager.class),
213                 this,
214                 mActivityContext.getStatsLogManager(),
215                 UserCache.INSTANCE.get(mActivityContext));
216         mPrivateSpaceBottomExtraSpace = context.getResources().getDimensionPixelSize(
217                 R.dimen.ps_extra_bottom_padding);
218         mAH = Arrays.asList(null, null, null);
219         mNavBarScrimPaint = new Paint();
220         mNavBarScrimPaint.setColor(Themes.getNavBarScrimColor(mActivityContext));
221 
222         AllAppsStore.OnUpdateListener onAppsUpdated = this::onAppsUpdated;
223         mAllAppsStore.addUpdateListener(onAppsUpdated);
224 
225         // This is a focus listener that proxies focus from a view into the list view.  This is to
226         // work around the search box from getting first focus and showing the cursor.
227         setOnFocusChangeListener((v, hasFocus) -> {
228             if (hasFocus && getActiveRecyclerView() != null) {
229                 getActiveRecyclerView().requestFocus();
230             }
231         });
232         mSearchUiDelegate = createSearchUiDelegate();
233         initContent();
234 
235         mSearchTransitionController = new SearchTransitionController(this);
236     }
237 
238     /** Creates the delegate for initializing search. */
createSearchUiDelegate()239     protected AllAppsSearchUiDelegate createSearchUiDelegate() {
240         return new AllAppsSearchUiDelegate(this);
241     }
242 
getSearchUiDelegate()243     public AllAppsSearchUiDelegate getSearchUiDelegate() {
244         return mSearchUiDelegate;
245     }
246 
247     /**
248      * Initializes the view hierarchy and internal variables. Any initialization which actually uses
249      * these members should be done in {@link #onFinishInflate()}.
250      * In terms of subclass initialization, the following would be parallel order for activity:
251      *   initContent -> onPreCreate
252      *   constructor/init -> onCreate
253      *   onFinishInflate -> onPostCreate
254      */
initContent()255     protected void initContent() {
256         mMainAdapterProvider = mSearchUiDelegate.createMainAdapterProvider();
257 
258         mAH.set(AdapterHolder.MAIN, new AdapterHolder(AdapterHolder.MAIN,
259                 new AlphabeticalAppsList<>(mActivityContext,
260                         mAllAppsStore,
261                         null,
262                         mPrivateProfileManager)));
263         mAH.set(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK,
264                 new AlphabeticalAppsList<>(mActivityContext, mAllAppsStore, mWorkManager, null)));
265         mAH.set(SEARCH, new AdapterHolder(SEARCH,
266                 new AlphabeticalAppsList<>(mActivityContext, null, null, null)));
267 
268         getLayoutInflater().inflate(R.layout.all_apps_content, this);
269         mHeader = findViewById(R.id.all_apps_header);
270         mAdditionalHeaderRows.clear();
271         mAdditionalHeaderRows.addAll(getAdditionalHeaderRows());
272         mBottomSheetBackground = findViewById(R.id.bottom_sheet_background);
273         mBottomSheetHandleArea = findViewById(R.id.bottom_sheet_handle_area);
274         mSearchRecyclerView = findViewById(R.id.search_results_list_view);
275         mFastScroller = findViewById(R.id.fast_scroller);
276         mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup));
277         mFastScrollLetterLayout = findViewById(R.id.scroll_letter_layout);
278         setClipChildren(false);
279 
280         mSearchContainer = inflateSearchBar();
281         if (!isSearchBarFloating()) {
282             // Add the search box above everything else in this container (if the flag is enabled,
283             // it's added to drag layer in onAttach instead).
284             addView(mSearchContainer);
285             // The search container is visually at the top of the all apps UI, and should thus be
286             // focused by default. It's added to end of the children list, so it needs to be
287             // explicitly marked as focused by default.
288             mSearchContainer.setFocusedByDefault(true);
289         }
290         mSearchUiManager = (SearchUiManager) mSearchContainer;
291     }
292 
getAdditionalHeaderRows()293     public List<AllAppsRow> getAdditionalHeaderRows() {
294         return List.of();
295     }
296 
297     @Override
onFinishInflate()298     protected void onFinishInflate() {
299         super.onFinishInflate();
300 
301         mAH.get(SEARCH).setup(mSearchRecyclerView,
302                 /* Filter out A-Z apps */ itemInfo -> false);
303         rebindAdapters(true /* force */);
304         float cornerRadius = Themes.getDialogCornerRadius(getContext());
305         mBottomSheetCornerRadii = new float[]{
306                 cornerRadius,
307                 cornerRadius, // Top left radius in px
308                 cornerRadius,
309                 cornerRadius, // Top right radius in px
310                 0,
311                 0, // Bottom right
312                 0,
313                 0 // Bottom left
314         };
315         if (Flags.allAppsBlur()) {
316             int resId = Utilities.isDarkTheme(getContext())
317                     ? android.R.color.system_accent1_800 : android.R.color.system_accent1_100;
318             int layerAbove = ColorUtils.setAlphaComponent(getResources().getColor(resId, null),
319                     (int) (0.4f * 255));
320             int layerBelow = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.1f * 255));
321             mBottomSheetBackgroundColor = ColorUtils.compositeColors(layerAbove, layerBelow);
322         } else {
323             mBottomSheetBackgroundColor = getContext().getColor(R.color.materialColorSurfaceDim);
324         }
325         mBottomSheetBackgroundAlpha = Color.alpha(mBottomSheetBackgroundColor) / 255.0f;
326         updateBackgroundVisibility(mActivityContext.getDeviceProfile());
327         mSearchUiManager.initializeSearch(this);
328     }
329 
330     @Override
onAttachedToWindow()331     protected void onAttachedToWindow() {
332         super.onAttachedToWindow();
333         if (isSearchBarFloating()) {
334             // Note: for Taskbar this is removed in TaskbarAllAppsController#cleanUpOverlay when the
335             // panel is closed. Can't do so in onDetach because we are also a child of drag layer
336             // so can't remove its views during that dispatch.
337             mActivityContext.getDragLayer().addView(mSearchContainer);
338             mSearchUiDelegate.onInitializeSearchBar();
339         }
340         mActivityContext.addOnDeviceProfileChangeListener(this);
341     }
342 
343     @Override
onDetachedFromWindow()344     protected void onDetachedFromWindow() {
345         super.onDetachedFromWindow();
346         mActivityContext.removeOnDeviceProfileChangeListener(this);
347     }
348 
getSearchUiManager()349     public SearchUiManager getSearchUiManager() {
350         return mSearchUiManager;
351     }
352 
getSearchView()353     public View getSearchView() {
354         return mSearchContainer;
355     }
356 
357     /** Invoke when the current search session is finished. */
onClearSearchResult()358     public void onClearSearchResult() {
359         getMainAdapterProvider().clearHighlightedItem();
360         animateToSearchState(false);
361         rebindAdapters();
362     }
363 
364     /**
365      * Sets results list for search
366      */
setSearchResults(ArrayList<AdapterItem> results)367     public void setSearchResults(ArrayList<AdapterItem> results) {
368         getMainAdapterProvider().clearHighlightedItem();
369         if (getSearchResultList().setSearchResults(results)) {
370             getSearchRecyclerView().onSearchResultsChanged();
371         }
372         if (results != null) {
373             animateToSearchState(true);
374         }
375     }
376 
377     /**
378      * Sets results list for search.
379      *
380      * @param searchResultCode indicates if the result is final or intermediate for a given query
381      *                         since we can get search results from multiple sources.
382      */
setSearchResults(ArrayList<AdapterItem> results, int searchResultCode)383     public void setSearchResults(ArrayList<AdapterItem> results, int searchResultCode) {
384         setSearchResults(results);
385         mSearchUiDelegate.onSearchResultsChanged(results, searchResultCode);
386     }
387 
animateToSearchState(boolean goingToSearch)388     private void animateToSearchState(boolean goingToSearch) {
389         animateToSearchState(goingToSearch, DEFAULT_SEARCH_TRANSITION_DURATION_MS);
390     }
391 
setAllAppsTransitionController( AllAppsTransitionController allAppsTransitionController)392     public void setAllAppsTransitionController(
393             AllAppsTransitionController allAppsTransitionController) {
394         mAllAppsTransitionController = allAppsTransitionController;
395     }
396 
animateToSearchState(boolean goingToSearch, long durationMs)397     void animateToSearchState(boolean goingToSearch, long durationMs) {
398         if (!mSearchTransitionController.isRunning() && goingToSearch == isSearching()) {
399             return;
400         }
401         mFastScroller.setVisibility(goingToSearch ? INVISIBLE : VISIBLE);
402         if (goingToSearch) {
403             // Fade out the button to pause work apps.
404             mWorkManager.onActivePageChanged(SEARCH);
405         } else if (mAllAppsTransitionController != null) {
406             // If exiting search, revert predictive back scale on all apps
407             mAllAppsTransitionController.animateAllAppsToNoScale();
408         }
409         mSearchTransitionController.animateToState(goingToSearch, durationMs,
410                 /* onEndRunnable = */ () -> {
411                     mIsSearching = goingToSearch;
412                     updateSearchResultsVisibility();
413                     int previousPage = getCurrentPage();
414                     if (mRebindAdaptersAfterSearchAnimation) {
415                         rebindAdapters(false);
416                         mRebindAdaptersAfterSearchAnimation = false;
417                     }
418 
419                     if (goingToSearch) {
420                         mSearchUiDelegate.onAnimateToSearchStateCompleted();
421                     } else {
422                         setSearchResults(null);
423                         if (mViewPager != null) {
424                             mViewPager.setCurrentPage(previousPage);
425                         }
426                         onActivePageChanged(previousPage);
427                     }
428                 });
429     }
430 
shouldContainerScroll(MotionEvent ev)431     public boolean shouldContainerScroll(MotionEvent ev) {
432         BaseDragLayer dragLayer = mActivityContext.getDragLayer();
433         // IF the MotionEvent is inside the search box or handle area, and the container keeps on
434         // receiving touch input, container should move down.
435         if (dragLayer.isEventOverView(mSearchContainer, ev)
436                 || dragLayer.isEventOverView(mBottomSheetHandleArea, ev)) {
437             return true;
438         }
439         AllAppsRecyclerView rv = getActiveRecyclerView();
440         if (rv == null) {
441             return true;
442         }
443         if (rv.getScrollbar() != null
444                 && rv.getScrollbar().getThumbOffsetY() >= 0
445                 && dragLayer.isEventOverView(rv.getScrollbar(), ev)) {
446             return false;
447         }
448         // Scroll if not within the container view (e.g. over large-screen scrim).
449         if (!dragLayer.isEventOverView(getVisibleContainerView(), ev)) {
450             return true;
451         }
452         return rv.shouldContainerScroll(ev, dragLayer);
453     }
454 
455     /**
456      * Resets the UI to be ready for fresh interactions in the future. Exits search and returns to
457      * A-Z apps list.
458      *
459      * @param animate Whether to animate the header during the reset (e.g. switching profile tabs).
460      */
reset(boolean animate)461     public void reset(boolean animate) {
462         reset(animate, true);
463     }
464 
465     /**
466      * Resets the UI to be ready for fresh interactions in the future.
467      *
468      * @param animate Whether to animate the header during the reset (e.g. switching profile tabs).
469      * @param exitSearch Whether to force exit the search state and return to A-Z apps list.
470      */
reset(boolean animate, boolean exitSearch)471     public void reset(boolean animate, boolean exitSearch) {
472         // Scroll Main and Work RV to top. Search RV is done in `resetSearch`.
473         for (int i = 0; i < mAH.size(); i++) {
474             if (i != SEARCH && mAH.get(i).mRecyclerView != null) {
475                 mAH.get(i).mRecyclerView.scrollToTop();
476             }
477         }
478         if (mTouchHandler != null) {
479             mTouchHandler.endFastScrolling();
480         }
481         if (mHeader != null && mHeader.getVisibility() == VISIBLE) {
482             mHeader.reset(animate);
483         }
484         updateBackgroundVisibility(mActivityContext.getDeviceProfile());
485         // Reset the base recycler view after transitioning home.
486         updateHeaderScroll(0);
487         if (exitSearch) {
488             // Reset the search bar and search RV after transitioning home.
489             MAIN_EXECUTOR.getHandler().post(mSearchUiManager::resetSearch);
490         }
491         if (isSearching()) {
492             mWorkManager.reset();
493         }
494     }
495 
496     /**
497      * Exits search and returns to A-Z apps list. Scroll to the private space header.
498      */
resetAndScrollToPrivateSpaceHeader()499     public void resetAndScrollToPrivateSpaceHeader() {
500         // Animate to A-Z with 0 time to reset the animation with proper state management.
501         // We can't rely on `animateToSearchState` with delay inside `resetSearch` because that will
502         // conflict with following scrolling to bottom, so we need it with 0 time here.
503         animateToSearchState(false, 0);
504 
505         MAIN_EXECUTOR.getHandler().post(() -> {
506             // Reset the search bar after transitioning home.
507             // When `resetSearch` is called after `animateToSearchState` is finished, the inside
508             // `animateToSearchState` with delay is a just no-op and return early.
509             mSearchUiManager.resetSearch();
510             // Switch to the main tab
511             switchToTab(ActivityAllAppsContainerView.AdapterHolder.MAIN);
512             // Scroll to bottom
513             if (mPrivateProfileManager != null) {
514                 mPrivateProfileManager.scrollForHeaderToBeVisibleInContainer(
515                         getActiveAppsRecyclerView(),
516                         getPersonalAppList().getAdapterItems(),
517                         mPrivateProfileManager.getPsHeaderHeight(),
518                         mActivityContext.getDeviceProfile().allAppsCellHeightPx);
519             }
520         });
521     }
522 
523     @Override
dispatchKeyEvent(KeyEvent event)524     public boolean dispatchKeyEvent(KeyEvent event) {
525         mSearchUiManager.preDispatchKeyEvent(event);
526         return super.dispatchKeyEvent(event);
527     }
528 
getDescription()529     public String getDescription() {
530         if (!mUsingTabs && isSearching()) {
531             return getContext().getString(R.string.all_apps_search_results);
532         } else {
533             StringCache cache = mActivityContext.getStringCache();
534             if (mUsingTabs) {
535                 if (cache != null) {
536                     return isPersonalTab()
537                             ? cache.allAppsPersonalTabAccessibility
538                             : cache.allAppsWorkTabAccessibility;
539                 } else {
540                     return isPersonalTab()
541                             ? getContext().getString(R.string.all_apps_button_personal_label)
542                             : getContext().getString(R.string.all_apps_button_work_label);
543                 }
544             }
545             return getContext().getString(R.string.all_apps_button_label);
546         }
547     }
548 
isSearching()549     public boolean isSearching() {
550         return mIsSearching;
551     }
552 
553     @Override
onActivePageChanged(int currentActivePage)554     public void onActivePageChanged(int currentActivePage) {
555         if (mSearchTransitionController.isRunning()) {
556             // Will be called at the end of the animation.
557             return;
558         }
559         if (currentActivePage != SEARCH) {
560             mActivityContext.hideKeyboard();
561         }
562         if (mAH.get(currentActivePage).mRecyclerView != null) {
563             mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar(mFastScroller,
564                     ALL_APPS_SCROLLER);
565         }
566         // Header keeps track of active recycler view to properly render header protection.
567         mHeader.setActiveRV(currentActivePage);
568         reset(true /* animate */, !isSearching() /* exitSearch */);
569 
570         mWorkManager.onActivePageChanged(currentActivePage);
571     }
572 
rebindAdapters()573     protected void rebindAdapters() {
574         rebindAdapters(false /* force */);
575     }
576 
rebindAdapters(boolean force)577     protected void rebindAdapters(boolean force) {
578         Log.d(TAG, "rebindAdapters: force: " + force);
579         if (mSearchTransitionController.isRunning()) {
580             mRebindAdaptersAfterSearchAnimation = true;
581             return;
582         }
583         updateSearchResultsVisibility();
584 
585         boolean showTabs = shouldShowTabs();
586         if (showTabs == mUsingTabs && !force) {
587             Log.d(TAG, "rebindAdapters: Not needed.");
588             return;
589         }
590 
591         // replaceAppsRVcontainer() needs to use both mUsingTabs value to remove the old view AND
592         // showTabs value to create new view. Hence the mUsingTabs new value assignment MUST happen
593         // after this call.
594         replaceAppsRVContainer(showTabs);
595         mUsingTabs = showTabs;
596 
597         mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
598         mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
599         mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
600 
601         final AllAppsRecyclerView mainRecyclerView;
602         final AllAppsRecyclerView workRecyclerView;
603         if (mUsingTabs) {
604             mainRecyclerView = (AllAppsRecyclerView) mViewPager.getChildAt(0);
605             workRecyclerView = (AllAppsRecyclerView) mViewPager.getChildAt(1);
606             mAH.get(AdapterHolder.MAIN).setup(mainRecyclerView, mPersonalMatcher);
607             mAH.get(AdapterHolder.WORK).setup(workRecyclerView, mWorkManager.getItemInfoMatcher());
608             workRecyclerView.setId(R.id.apps_list_view_work);
609             if (enableExpandingPauseWorkButton()
610                     || FeatureFlags.ENABLE_EXPANDING_PAUSE_WORK_BUTTON.get()) {
611                 mAH.get(AdapterHolder.WORK).mRecyclerView.addOnScrollListener(
612                         mWorkManager.newScrollListener());
613             }
614             mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN);
615             findViewById(R.id.tab_personal)
616                     .setOnClickListener((View view) -> {
617                         Log.d(TAG, "rebindAdapters: " + "Clicked personal tab.");
618                         if (mViewPager.snapToPage(AdapterHolder.MAIN)) {
619                             mActivityContext.getStatsLogManager().logger()
620                                     .log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB);
621                         }
622                     });
623             findViewById(R.id.tab_work)
624                     .setOnClickListener((View view) -> {
625                         Log.d(TAG, "rebindAdapters: " + "Clicked work tab.");
626                         if (mViewPager.snapToPage(AdapterHolder.WORK)) {
627                             mActivityContext.getStatsLogManager().logger()
628                                     .log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB);
629                         }
630                     });
631             setDeviceManagementResources();
632             if (mHeader.isSetUp()) {
633                 onActivePageChanged(mViewPager.getNextPage());
634             }
635         } else {
636             mainRecyclerView = findViewById(R.id.apps_list_view);
637             workRecyclerView = null;
638             mAH.get(AdapterHolder.MAIN).setup(mainRecyclerView, mPersonalMatcher);
639             mAH.get(AdapterHolder.WORK).mRecyclerView = null;
640         }
641         setUpCustomRecyclerViewPool(
642                 mainRecyclerView,
643                 workRecyclerView,
644                 mAllAppsStore.getRecyclerViewPool());
645         setupHeader();
646 
647         if (isSearchBarFloating()) {
648             // Keep the scroller above the search bar.
649             RelativeLayout.LayoutParams scrollerLayoutParams =
650                     (LayoutParams) mFastScroller.getLayoutParams();
651             scrollerLayoutParams.bottomMargin = mSearchContainer.getHeight()
652                     + getResources().getDimensionPixelSize(
653                             R.dimen.fastscroll_bottom_margin_floating_search);
654         }
655 
656         mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
657         mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
658         mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
659     }
660 
661     /**
662      * If {@link ENABLE_ALL_APPS_RV_PREINFLATION} is enabled, wire custom
663      * {@link RecyclerView.RecycledViewPool} to main and work {@link AllAppsRecyclerView}.
664      *
665      * Then if {@link ALL_APPS_GONE_VISIBILITY} is enabled, update max pool size. This is because
666      * all apps rv's hidden visibility is changed to {@link View#GONE} from {@link View#INVISIBLE),
667      * thus we cannot rely on layout pass to update pool size.
668      */
setUpCustomRecyclerViewPool( @onNull AllAppsRecyclerView mainRecyclerView, @Nullable AllAppsRecyclerView workRecyclerView, @NonNull AllAppsRecyclerViewPool recycledViewPool)669     private static void setUpCustomRecyclerViewPool(
670             @NonNull AllAppsRecyclerView mainRecyclerView,
671             @Nullable AllAppsRecyclerView workRecyclerView,
672             @NonNull AllAppsRecyclerViewPool recycledViewPool) {
673         final boolean hasWorkProfile = workRecyclerView != null;
674         recycledViewPool.setHasWorkProfile(hasWorkProfile);
675         mainRecyclerView.setRecycledViewPool(recycledViewPool);
676         if (workRecyclerView != null) {
677             workRecyclerView.setRecycledViewPool(recycledViewPool);
678         }
679         mainRecyclerView.updatePoolSize(hasWorkProfile);
680     }
681 
replaceAppsRVContainer(boolean showTabs)682     private void replaceAppsRVContainer(boolean showTabs) {
683         Log.d(TAG, "replaceAppsRVContainer: showTabs: " + showTabs);
684         for (int i = AdapterHolder.MAIN; i <= AdapterHolder.WORK; i++) {
685             AdapterHolder adapterHolder = mAH.get(i);
686             if (adapterHolder.mRecyclerView != null) {
687                 adapterHolder.mRecyclerView.setLayoutManager(null);
688                 adapterHolder.mRecyclerView.setAdapter(null);
689             }
690         }
691         View oldView = getAppsRecyclerViewContainer();
692         int index = indexOfChild(oldView);
693         removeView(oldView);
694         int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
695         final View rvContainer = getLayoutInflater().inflate(layout, this, false);
696         addView(rvContainer, index);
697         if (showTabs) {
698             mViewPager = (AllAppsPagedView) rvContainer;
699             mViewPager.initParentViews(this);
700             mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
701             mViewPager.setOutlineProvider(new ViewOutlineProvider() {
702                 @Override
703                 public void getOutline(View view, Outline outline) {
704                     @Px final int bottomOffsetPx =
705                             (int) (ActivityAllAppsContainerView.this.getMeasuredHeight()
706                                     * PREDICTIVE_BACK_MIN_SCALE);
707                     outline.setRect(
708                             0,
709                             0,
710                             view.getMeasuredWidth(),
711                             view.getMeasuredHeight() + bottomOffsetPx);
712                 }
713             });
714 
715             mWorkManager.reset();
716             post(() -> mAH.get(AdapterHolder.WORK).applyPadding());
717         } else {
718             mWorkManager.detachWorkUtilityViews();
719             mViewPager = null;
720         }
721 
722         removeCustomRules(rvContainer);
723         removeCustomRules(getSearchRecyclerView());
724         if (isSearchBarFloating()) {
725             alignParentTop(rvContainer, showTabs);
726             alignParentTop(getSearchRecyclerView(), /* tabs= */ false);
727         } else {
728             layoutBelowSearchContainer(rvContainer, showTabs);
729             layoutBelowSearchContainer(getSearchRecyclerView(), /* tabs= */ false);
730         }
731 
732         updateSearchResultsVisibility();
733     }
734 
setupHeader()735     void setupHeader() {
736         mAdditionalHeaderRows.forEach(row -> mHeader.onPluginDisconnected(row));
737 
738         mHeader.setVisibility(View.VISIBLE);
739         boolean tabsHidden = !mUsingTabs;
740         mHeader.setup(
741                 mAH.get(AdapterHolder.MAIN).mRecyclerView,
742                 mAH.get(AdapterHolder.WORK).mRecyclerView,
743                 (SearchRecyclerView) mAH.get(SEARCH).mRecyclerView,
744                 getCurrentPage(),
745                 tabsHidden);
746 
747         int padding = mHeader.getMaxTranslation();
748         mAH.forEach(adapterHolder -> {
749             adapterHolder.mPadding.top = padding;
750             adapterHolder.applyPadding();
751             if (adapterHolder.mRecyclerView != null) {
752                 adapterHolder.mRecyclerView.scrollToTop();
753             }
754         });
755         mAdditionalHeaderRows.forEach(row -> mHeader.onPluginConnected(row, mActivityContext));
756 
757         removeCustomRules(mHeader);
758         if (isSearchBarFloating()) {
759             alignParentTop(mHeader, false /* includeTabsMargin */);
760         } else {
761             layoutBelowSearchContainer(mHeader, false /* includeTabsMargin */);
762         }
763     }
764 
765     /**
766      * Force header height update with an offset. Used by {@link UniversalSearchInputView} to
767      * request {@link FloatingHeaderView} to update its maxTranslation for multiline search bar.
768      */
forceUpdateHeaderHeight(int offset)769     public void forceUpdateHeaderHeight(int offset) {
770         if (Flags.multilineSearchBar()) {
771             mHeader.updateSearchBarOffset(offset);
772         }
773     }
774 
775     @Override
addChildrenForAccessibility(ArrayList<View> arrayList)776     public void addChildrenForAccessibility(ArrayList<View> arrayList) {
777         super.addChildrenForAccessibility(arrayList);
778         if (!Flags.floatingSearchBar()) {
779             // Searchbox container is visually at the top of the all apps UI but it's present in
780             // end of the children list.
781             // We need to move the searchbox to the top in a11y tree for a11y services to read the
782             // all apps screen in same as visual order.
783             arrayList.stream().filter(v -> v.getId() == R.id.search_container_all_apps)
784                     .findFirst().ifPresent(v -> {
785                         arrayList.remove(v);
786                         arrayList.add(0, v);
787                     });
788         }
789     }
790 
updateHeaderScroll(int scrolledOffset)791     protected void updateHeaderScroll(int scrolledOffset) {
792         float prog1 = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f);
793         int headerColor = getHeaderColor(prog1);
794         int tabsAlpha = mHeader.getPeripheralProtectionHeight(/* expectedHeight */ false) == 0 ? 0
795                 : (int) (Utilities.boundToRange(
796                         (scrolledOffset + mHeader.mSnappedScrolledY) / mHeaderThreshold, 0f, 1f)
797                         * 255);
798         if (headerColor != mHeaderColor || mTabsProtectionAlpha != tabsAlpha) {
799             mHeaderColor = headerColor;
800             mTabsProtectionAlpha = tabsAlpha;
801             invalidateHeader();
802         }
803         if (mSearchUiManager.getEditText() == null) {
804             return;
805         }
806 
807         float prog = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f);
808         boolean bgVisible = mSearchUiManager.getBackgroundVisibility();
809         if (scrolledOffset == 0 && !isSearching()) {
810             bgVisible = true;
811         } else if (scrolledOffset > mHeaderThreshold) {
812             bgVisible = false;
813         }
814         mSearchUiManager.setBackgroundVisibility(bgVisible, 1 - prog);
815     }
816 
getHeaderColor(float blendRatio)817     protected int getHeaderColor(float blendRatio) {
818         return ColorUtils.setAlphaComponent(
819                 ColorUtils.blendARGB(mScrimColor, mHeaderProtectionColor, blendRatio),
820                 (int) (mSearchContainer.getAlpha() * 255));
821     }
822 
823     /**
824      * @return true if the search bar is floating above this container (at the bottom of the screen)
825      */
isSearchBarFloating()826     protected boolean isSearchBarFloating() {
827         return mSearchUiDelegate.isSearchBarFloating();
828     }
829 
830     /**
831      * Whether the <em>floating</em> search bar should appear as a small pill when not focused.
832      * <p>
833      * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely
834      * makes sense to use that method to derive an appropriate value for the current/target state.
835      */
shouldFloatingSearchBarBePillWhenUnfocused()836     public boolean shouldFloatingSearchBarBePillWhenUnfocused() {
837         return false;
838     }
839 
840     /**
841      * How far from the bottom of the screen the <em>floating</em> search bar should rest when the
842      * IME is not present.
843      * <p>
844      * To hide offscreen, use a negative value.
845      * <p>
846      * Note: if the provided value is non-negative but less than the current bottom insets, the
847      * insets will be applied. As such, you can use 0 to default to this.
848      * <p>
849      * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely
850      * makes sense to use that method to derive an appropriate value for the current/target state.
851      */
getFloatingSearchBarRestingMarginBottom()852     public int getFloatingSearchBarRestingMarginBottom() {
853         return 0;
854     }
855 
856     /**
857      * How far from the start of the screen the <em>floating</em> search bar should rest.
858      * <p>
859      * To use original margin, return a negative value.
860      * <p>
861      * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely
862      * makes sense to use that method to derive an appropriate value for the current/target state.
863      */
getFloatingSearchBarRestingMarginStart()864     public int getFloatingSearchBarRestingMarginStart() {
865         DeviceProfile dp = mActivityContext.getDeviceProfile();
866         return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(mActivityContext);
867     }
868 
869     /**
870      * How far from the end of the screen the <em>floating</em> search bar should rest.
871      * <p>
872      * To use original margin, return a negative value.
873      * <p>
874      * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely
875      * makes sense to use that method to derive an appropriate value for the current/target state.
876      */
getFloatingSearchBarRestingMarginEnd()877     public int getFloatingSearchBarRestingMarginEnd() {
878         DeviceProfile dp = mActivityContext.getDeviceProfile();
879         return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(mActivityContext);
880     }
881 
layoutBelowSearchContainer(View v, boolean includeTabsMargin)882     private void layoutBelowSearchContainer(View v, boolean includeTabsMargin) {
883         if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
884             return;
885         }
886 
887         RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
888         layoutParams.addRule(RelativeLayout.ALIGN_TOP, R.id.search_container_all_apps);
889 
890         int topMargin = getContext().getResources().getDimensionPixelSize(
891                 R.dimen.all_apps_header_top_margin);
892         if (includeTabsMargin) {
893             topMargin += getContext().getResources().getDimensionPixelSize(
894                     R.dimen.all_apps_header_pill_height);
895         }
896         layoutParams.topMargin = topMargin;
897     }
898 
alignParentTop(View v, boolean includeTabsMargin)899     private void alignParentTop(View v, boolean includeTabsMargin) {
900         if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
901             return;
902         }
903 
904         RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
905         layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
906         layoutParams.topMargin =
907                 includeTabsMargin
908                         ? getContext().getResources().getDimensionPixelSize(
909                         R.dimen.all_apps_header_pill_height)
910                         : 0;
911     }
912 
removeCustomRules(View v)913     private void removeCustomRules(View v) {
914         if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
915             return;
916         }
917 
918         RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
919         layoutParams.removeRule(RelativeLayout.ABOVE);
920         layoutParams.removeRule(RelativeLayout.ALIGN_TOP);
921         layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
922     }
923 
createAdapter(AlphabeticalAppsList<T> appsList)924     protected BaseAllAppsAdapter<T> createAdapter(AlphabeticalAppsList<T> appsList) {
925         return new AllAppsGridAdapter<>(mActivityContext, getLayoutInflater(), appsList,
926                 mMainAdapterProvider);
927     }
928 
isInAllApps()929     public boolean isInAllApps() {
930         // TODO: Make this abstract
931         return true;
932     }
933 
934     /**
935      * Inflates the search bar
936      */
inflateSearchBar()937     protected View inflateSearchBar() {
938         return mSearchUiDelegate.inflateSearchBar();
939     }
940 
941     /** The adapter provider for the main section. */
getMainAdapterProvider()942     public final SearchAdapterProvider<?> getMainAdapterProvider() {
943         return mMainAdapterProvider;
944     }
945 
946     @Override
dispatchRestoreInstanceState(SparseArray<Parcelable> sparseArray)947     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> sparseArray) {
948         try {
949             // Many slice view id is not properly assigned, and hence throws null
950             // pointer exception in the underneath method. Catching the exception
951             // simply doesn't restore these slice views. This doesn't have any
952             // user visible effect because because we query them again.
953             super.dispatchRestoreInstanceState(sparseArray);
954         } catch (Exception e) {
955             Log.e("AllAppsContainerView", "restoreInstanceState viewId = 0", e);
956         }
957 
958         Bundle state = (Bundle) sparseArray.get(R.id.work_tab_state_id, null);
959         if (state != null) {
960             int currentPage = state.getInt(BUNDLE_KEY_CURRENT_PAGE, 0);
961             if (currentPage == AdapterHolder.WORK && mViewPager != null) {
962                 mViewPager.setCurrentPage(currentPage);
963                 rebindAdapters();
964             } else {
965                 reset(true);
966             }
967         }
968     }
969 
970     @Override
dispatchSaveInstanceState(SparseArray<Parcelable> container)971     protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
972         super.dispatchSaveInstanceState(container);
973         Bundle state = new Bundle();
974         state.putInt(BUNDLE_KEY_CURRENT_PAGE, getCurrentPage());
975         container.put(R.id.work_tab_state_id, state);
976     }
977 
getAppsStore()978     public AllAppsStore<T> getAppsStore() {
979         return mAllAppsStore;
980     }
981 
getWorkManager()982     public WorkProfileManager getWorkManager() {
983         return mWorkManager;
984     }
985 
986     /** Returns whether Private Profile has been setup. */
hasPrivateProfile()987     public boolean hasPrivateProfile() {
988         return mHasPrivateApps;
989     }
990 
991     @Override
onDeviceProfileChanged(DeviceProfile dp)992     public void onDeviceProfileChanged(DeviceProfile dp) {
993         for (AdapterHolder holder : mAH) {
994             holder.mAdapter.setAppsPerRow(dp.numShownAllAppsColumns);
995             holder.mAppsList.setNumAppsPerRowAllApps(dp.numShownAllAppsColumns);
996             if (holder.mRecyclerView != null) {
997                 // Remove all views and clear the pool, while keeping the data same. After this
998                 // call, all the viewHolders will be recreated.
999                 holder.mRecyclerView.swapAdapter(holder.mRecyclerView.getAdapter(), true);
1000                 holder.mRecyclerView.getRecycledViewPool().clear();
1001             }
1002         }
1003         updateBackgroundVisibility(dp);
1004 
1005         int navBarScrimColor = Themes.getNavBarScrimColor(mActivityContext);
1006         if (mNavBarScrimPaint.getColor() != navBarScrimColor) {
1007             mNavBarScrimPaint.setColor(navBarScrimColor);
1008             invalidate();
1009         }
1010     }
1011 
updateBackgroundVisibility(DeviceProfile deviceProfile)1012     protected void updateBackgroundVisibility(DeviceProfile deviceProfile) {
1013         mBottomSheetBackground.setVisibility(
1014                 deviceProfile.shouldShowAllAppsOnSheet() ? View.VISIBLE : View.GONE);
1015         // Note: The opaque sheet background and header protection are added in drawOnScrim.
1016         // For the taskbar entrypoint, the scrim is drawn by its abstract slide in view container,
1017         // so its header protection is derived from this scrim instead.
1018     }
1019 
1020     @VisibleForTesting
onAppsUpdated()1021     public void onAppsUpdated() {
1022         Log.d(TAG, "onAppsUpdated; number of apps: " + mAllAppsStore.getApps().length);
1023         mHasWorkApps = Stream.of(mAllAppsStore.getApps())
1024                 .anyMatch(mWorkManager.getItemInfoMatcher());
1025         mHasPrivateApps = Stream.of(mAllAppsStore.getApps())
1026                 .anyMatch(mPrivateProfileManager.getItemInfoMatcher());
1027         if (!isSearching()) {
1028             rebindAdapters();
1029         }
1030         if (mHasWorkApps) {
1031             mWorkManager.reset();
1032         }
1033         if (mHasPrivateApps) {
1034             mPrivateProfileManager.reset();
1035         }
1036 
1037         mActivityContext.getStatsLogManager().logger()
1038                 .withCardinality(mAllAppsStore.getApps().length)
1039                 .log(LAUNCHER_ALLAPPS_COUNT);
1040     }
1041 
1042     @Override
onInterceptTouchEvent(MotionEvent ev)1043     public boolean onInterceptTouchEvent(MotionEvent ev) {
1044         // The AllAppsContainerView houses the QSB and is hence visible from the Workspace
1045         // Overview states. We shouldn't intercept for the scrubber in these cases.
1046         if (!isInAllApps()) {
1047             mTouchHandler = null;
1048             return false;
1049         }
1050 
1051         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1052             AllAppsRecyclerView rv = getActiveRecyclerView();
1053             if (rv != null && rv.getScrollbar() != null
1054                     && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
1055                 mTouchHandler = rv.getScrollbar();
1056             } else {
1057                 mTouchHandler = null;
1058             }
1059         }
1060         if (mTouchHandler != null) {
1061             return mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
1062         }
1063         return false;
1064     }
1065 
1066     @Override
onTouchEvent(MotionEvent ev)1067     public boolean onTouchEvent(MotionEvent ev) {
1068         if (!isInAllApps()) {
1069             return false;
1070         }
1071 
1072         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1073             AllAppsRecyclerView rv = getActiveRecyclerView();
1074             if (rv != null && rv.getScrollbar() != null
1075                     && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
1076                 mTouchHandler = rv.getScrollbar();
1077             } else {
1078                 mTouchHandler = null;
1079 
1080             }
1081         }
1082         if (mTouchHandler != null) {
1083             mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
1084             return true;
1085         }
1086         if (isSearching()
1087                 && mActivityContext.getDragLayer().isEventOverView(getVisibleContainerView(), ev)) {
1088             // if in search state, consume touch event.
1089             return true;
1090         }
1091         return false;
1092     }
1093 
1094     /** The current active recycler view (A-Z list from one of the profiles, or search results). */
getActiveRecyclerView()1095     public AllAppsRecyclerView getActiveRecyclerView() {
1096         if (isSearching()) {
1097             return getSearchRecyclerView();
1098         }
1099         return getActiveAppsRecyclerView();
1100     }
1101 
1102     /** The current focus change listener in the search container. */
getSearchFocusChangeListener()1103     public OnFocusChangeListener getSearchFocusChangeListener() {
1104         return mAH.get(AdapterHolder.SEARCH).mOnFocusChangeListener;
1105     }
1106 
1107     /** The current apps recycler view in the container. */
getActiveAppsRecyclerView()1108     private AllAppsRecyclerView getActiveAppsRecyclerView() {
1109         if (!mUsingTabs || isPersonalTab()) {
1110             return mAH.get(AdapterHolder.MAIN).mRecyclerView;
1111         } else {
1112             return mAH.get(AdapterHolder.WORK).mRecyclerView;
1113         }
1114     }
1115 
1116     /**
1117      * The container for A-Z apps (the ViewPager for main+work tabs, or main RV). This is currently
1118      * hidden while searching.
1119      */
getAppsRecyclerViewContainer()1120     public ViewGroup getAppsRecyclerViewContainer() {
1121         return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
1122     }
1123 
1124     /** The RV for search results, which is hidden while A-Z apps are visible. */
getSearchRecyclerView()1125     public SearchRecyclerView getSearchRecyclerView() {
1126         return mSearchRecyclerView;
1127     }
1128 
isPersonalTab()1129     protected boolean isPersonalTab() {
1130         return mViewPager == null || mViewPager.getNextPage() == 0;
1131     }
1132 
1133     /**
1134      * Switches the current page to the provided {@code tab} if tabs are supported, otherwise does
1135      * nothing.
1136      */
switchToTab(int tab)1137     public void switchToTab(int tab) {
1138         if (mUsingTabs) {
1139             mViewPager.setCurrentPage(tab);
1140         }
1141     }
1142 
getLayoutInflater()1143     public LayoutInflater getLayoutInflater() {
1144         return mSearchUiDelegate.getLayoutInflater();
1145     }
1146 
1147     @Override
onDropCompleted(View target, DragObject d, boolean success)1148     public void onDropCompleted(View target, DragObject d, boolean success) {}
1149 
1150     @Override
setInsets(Rect insets)1151     public void setInsets(Rect insets) {
1152         mInsets.set(insets);
1153         DeviceProfile grid = mActivityContext.getDeviceProfile();
1154 
1155         applyAdapterSideAndBottomPaddings(grid);
1156 
1157         MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
1158         // Ignore left/right insets on bottom sheet because we are already centered in-screen.
1159         if (grid.shouldShowAllAppsOnSheet()) {
1160             mlp.leftMargin = mlp.rightMargin = 0;
1161         } else {
1162             mlp.leftMargin = insets.left;
1163             mlp.rightMargin = insets.right;
1164         }
1165         setLayoutParams(mlp);
1166 
1167         if (!grid.isVerticalBarLayout() || FeatureFlags.enableResponsiveWorkspace()) {
1168             int topPadding = grid.allAppsPadding.top;
1169             if (isSearchBarFloating() && !grid.shouldShowAllAppsOnSheet()) {
1170                 topPadding += getResources().getDimensionPixelSize(
1171                         R.dimen.all_apps_additional_top_padding_floating_search);
1172             }
1173             setPadding(grid.allAppsLeftRightMargin, topPadding, grid.allAppsLeftRightMargin, 0);
1174         }
1175         InsettableFrameLayout.dispatchInsets(this, insets);
1176     }
1177 
1178     /**
1179      * Returns a padding in case a scrim is shown on the bottom of the view and a padding is needed.
1180      */
computeNavBarScrimHeight(WindowInsets insets)1181     protected int computeNavBarScrimHeight(WindowInsets insets) {
1182         return 0;
1183     }
1184 
1185     /**
1186      * Returns the current height of nav bar scrim
1187      */
getNavBarScrimHeight()1188     public int getNavBarScrimHeight() {
1189         return mNavBarScrimHeight;
1190     }
1191 
1192     @Override
dispatchApplyWindowInsets(WindowInsets insets)1193     public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
1194         mNavBarScrimHeight = computeNavBarScrimHeight(insets);
1195         applyAdapterSideAndBottomPaddings(mActivityContext.getDeviceProfile());
1196         return super.dispatchApplyWindowInsets(insets);
1197     }
1198 
1199     @Override
dispatchDraw(Canvas canvas)1200     protected void dispatchDraw(Canvas canvas) {
1201         super.dispatchDraw(canvas);
1202 
1203         if (mNavBarScrimHeight > 0) {
1204             float left = (getWidth() - getWidth() / getScaleX()) / 2;
1205             float top = getHeight() / 2f + (getHeight() / 2f - mNavBarScrimHeight) / getScaleY();
1206             canvas.drawRect(left, top, getWidth() / getScaleX(),
1207                     top + mNavBarScrimHeight / getScaleY(), mNavBarScrimPaint);
1208         }
1209     }
1210 
updateSearchResultsVisibility()1211     protected void updateSearchResultsVisibility() {
1212         if (isSearching()) {
1213             getSearchRecyclerView().setVisibility(VISIBLE);
1214             getAppsRecyclerViewContainer().setVisibility(GONE);
1215             mHeader.setVisibility(GONE);
1216         } else {
1217             getSearchRecyclerView().setVisibility(GONE);
1218             getAppsRecyclerViewContainer().setVisibility(VISIBLE);
1219             mHeader.setVisibility(VISIBLE);
1220         }
1221         if (mHeader.isSetUp()) {
1222             mHeader.setActiveRV(getCurrentPage());
1223         }
1224     }
1225 
applyAdapterSideAndBottomPaddings(DeviceProfile grid)1226     private void applyAdapterSideAndBottomPaddings(DeviceProfile grid) {
1227         int bottomPadding = Math.max(mInsets.bottom, mNavBarScrimHeight);
1228         mAH.forEach(adapterHolder -> {
1229             adapterHolder.mPadding.bottom = bottomPadding;
1230             adapterHolder.mPadding.left = grid.allAppsPadding.left;
1231             adapterHolder.mPadding.right = grid.allAppsPadding.right;
1232             adapterHolder.applyPadding();
1233         });
1234     }
1235 
setDeviceManagementResources()1236     private void setDeviceManagementResources() {
1237         if (mActivityContext.getStringCache() != null) {
1238             Button personalTab = findViewById(R.id.tab_personal);
1239             personalTab.setText(mActivityContext.getStringCache().allAppsPersonalTab);
1240 
1241             Button workTab = findViewById(R.id.tab_work);
1242             workTab.setText(mActivityContext.getStringCache().allAppsWorkTab);
1243         }
1244     }
1245 
1246     /**
1247      * Returns true if the container has work apps.
1248      */
shouldShowTabs()1249     public boolean shouldShowTabs() {
1250         return mHasWorkApps;
1251     }
1252 
1253     // Used by tests only
isDescendantViewVisible(int viewId)1254     private boolean isDescendantViewVisible(int viewId) {
1255         final View view = findViewById(viewId);
1256         if (view == null) return false;
1257 
1258         if (!view.isShown()) return false;
1259 
1260         return view.getGlobalVisibleRect(new Rect());
1261     }
1262 
1263     /** Called in Launcher#bindStringCache() to update the UI when cache is updated. */
updateWorkUI()1264     public void updateWorkUI() {
1265         setDeviceManagementResources();
1266         if (mWorkManager.getWorkUtilityView() != null) {
1267             mWorkManager.getWorkUtilityView().updateStringFromCache();
1268         }
1269         inflateWorkCardsIfNeeded();
1270     }
1271 
inflateWorkCardsIfNeeded()1272     private void inflateWorkCardsIfNeeded() {
1273         AllAppsRecyclerView workRV = mAH.get(AdapterHolder.WORK).mRecyclerView;
1274         if (workRV != null) {
1275             for (int i = 0; i < workRV.getChildCount(); i++) {
1276                 View currentView  = workRV.getChildAt(i);
1277                 int currentItemViewType = workRV.getChildViewHolder(currentView).getItemViewType();
1278                 if (currentItemViewType == VIEW_TYPE_WORK_EDU_CARD) {
1279                     ((WorkEduCard) currentView).updateStringFromCache();
1280                 } else if (currentItemViewType == VIEW_TYPE_WORK_DISABLED_CARD) {
1281                     ((WorkPausedCard) currentView).updateStringFromCache();
1282                 }
1283             }
1284         }
1285     }
1286 
1287     @VisibleForTesting
setWorkManager(WorkProfileManager workManager)1288     public void setWorkManager(WorkProfileManager workManager) {
1289         mWorkManager = workManager;
1290     }
1291 
1292     @VisibleForTesting
isPersonalTabVisible()1293     public boolean isPersonalTabVisible() {
1294         return isDescendantViewVisible(R.id.tab_personal);
1295     }
1296 
1297     @VisibleForTesting
isWorkTabVisible()1298     public boolean isWorkTabVisible() {
1299         return isDescendantViewVisible(R.id.tab_work);
1300     }
1301 
getSearchResultList()1302     public AlphabeticalAppsList<T> getSearchResultList() {
1303         return mAH.get(SEARCH).mAppsList;
1304     }
1305 
getPersonalAppList()1306     public AlphabeticalAppsList<T> getPersonalAppList() {
1307         return mAH.get(MAIN).mAppsList;
1308     }
1309 
getWorkAppList()1310     public AlphabeticalAppsList<T> getWorkAppList() {
1311         return mAH.get(WORK).mAppsList;
1312     }
1313 
getFloatingHeaderView()1314     public FloatingHeaderView getFloatingHeaderView() {
1315         return mHeader;
1316     }
1317 
1318     @VisibleForTesting
getContentView()1319     public View getContentView() {
1320         return isSearching() ? getSearchRecyclerView() : getAppsRecyclerViewContainer();
1321     }
1322 
1323     /** The current page visible in all apps. */
getCurrentPage()1324     public int getCurrentPage() {
1325         return isSearching()
1326                 ? SEARCH
1327                 : mViewPager == null ? AdapterHolder.MAIN : mViewPager.getNextPage();
1328     }
1329 
getPrivateProfileManager()1330     public PrivateProfileManager getPrivateProfileManager() {
1331         return mPrivateProfileManager;
1332     }
1333 
1334     /**
1335      * Adds an update listener to animator that adds springs to the animation.
1336      */
addSpringFromFlingUpdateListener(ValueAnimator animator, float velocity , float progress )1337     public void addSpringFromFlingUpdateListener(ValueAnimator animator,
1338             float velocity /* release velocity */,
1339             float progress /* portion of the distance to travel*/) {
1340         animator.addListener(new AnimatorListenerAdapter() {
1341             @Override
1342             public void onAnimationStart(Animator animator) {
1343                 float distance = (1 - progress) * getHeight(); // px
1344                 float settleVelocity = Math.min(0, distance
1345                         / (AllAppsTransitionController.INTERP_COEFF * animator.getDuration())
1346                         + velocity);
1347                 absorbSwipeUpVelocity(Math.max(1000, Math.abs(
1348                         Math.round(settleVelocity * FLING_VELOCITY_MULTIPLIER))));
1349             }
1350         });
1351     }
1352 
1353     /** Invoked when the container is pulled. */
onPull(float deltaDistance, float displacement)1354     public void onPull(float deltaDistance, float displacement) {
1355         absorbPullDeltaDistance(PULL_MULTIPLIER * deltaDistance, PULL_MULTIPLIER * displacement);
1356         // Current motion spec is to actually push and not pull
1357         // on this surface. However, until EdgeEffect.onPush (b/190612804) is
1358         // implemented at view level, we will simply pull
1359     }
1360 
1361     @Override
getDrawingRect(Rect outRect)1362     public void getDrawingRect(Rect outRect) {
1363         super.getDrawingRect(outRect);
1364         outRect.offset(0, (int) getTranslationY());
1365     }
1366 
1367     @Override
setTranslationY(float translationY)1368     public void setTranslationY(float translationY) {
1369         super.setTranslationY(translationY);
1370         invalidateHeader();
1371     }
1372 
1373     @Override
setScaleY(float scaleY)1374     public void setScaleY(float scaleY) {
1375         super.setScaleY(scaleY);
1376         if (predictiveBackThreeButtonNav() && mNavBarScrimHeight > 0) {
1377             // Call invalidate to prevent navbar scrim from scaling. The navbar scrim is drawn
1378             // directly onto the canvas. To prevent it from being scaled with the canvas, there's a
1379             // counter scale applied in dispatchDraw.
1380             invalidate(20, getHeight() - mNavBarScrimHeight, getWidth(), getHeight());
1381         }
1382     }
1383 
1384     /**
1385      * Set {@link Animator.AnimatorListener} on {@link mAllAppsTransitionController} to observe
1386      * animation of backing out of all apps search view to all apps view.
1387      */
setAllAppsSearchBackAnimatorListener(Animator.AnimatorListener listener)1388     public void setAllAppsSearchBackAnimatorListener(Animator.AnimatorListener listener) {
1389         Preconditions.assertNotNull(mAllAppsTransitionController);
1390         if (mAllAppsTransitionController == null) {
1391             return;
1392         }
1393         mAllAppsTransitionController.setAllAppsSearchBackAnimationListener(listener);
1394     }
1395 
setScrimView(ScrimView scrimView)1396     public void setScrimView(ScrimView scrimView) {
1397         mScrimView = scrimView;
1398     }
1399 
1400     @Override
drawOnScrimWithScaleAndBottomOffset( Canvas canvas, float scale, @Px int bottomOffsetPx)1401     public void drawOnScrimWithScaleAndBottomOffset(
1402             Canvas canvas, float scale, @Px int bottomOffsetPx) {
1403         final View panel = mBottomSheetBackground;
1404         final boolean hasBottomSheet = panel.getVisibility() == VISIBLE;
1405         final float translationY = ((View) panel.getParent()).getTranslationY();
1406 
1407         final float horizontalScaleOffset = (1 - scale) * panel.getWidth() / 2;
1408         final float verticalScaleOffset = (1 - scale) * (panel.getHeight() - getHeight() / 2);
1409 
1410         final float topNoScale = panel.getTop() + translationY;
1411         final float topWithScale = topNoScale + verticalScaleOffset;
1412         final float leftWithScale = panel.getLeft() + horizontalScaleOffset;
1413         final float rightWithScale = panel.getRight() - horizontalScaleOffset;
1414         final float bottomWithOffset = panel.getBottom() + bottomOffsetPx;
1415         // Draw full background panel for tablets.
1416         if (hasBottomSheet) {
1417             mHeaderPaint.setColor(mBottomSheetBackgroundColor);
1418             mHeaderPaint.setAlpha((int) (mBottomSheetBackgroundAlpha * 255));
1419 
1420             mTmpRectF.set(
1421                     leftWithScale,
1422                     topWithScale,
1423                     rightWithScale,
1424                     bottomWithOffset);
1425             mTmpPath.reset();
1426             mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
1427             canvas.drawPath(mTmpPath, mHeaderPaint);
1428         }
1429 
1430         if (DEBUG_HEADER_PROTECTION) {
1431             mHeaderPaint.setColor(Color.MAGENTA);
1432             mHeaderPaint.setAlpha(255);
1433         } else {
1434             mHeaderPaint.setColor(mHeaderColor);
1435             mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor)));
1436         }
1437         if (mHeaderPaint.getColor() == mScrimColor || mHeaderPaint.getColor() == 0) {
1438             return;
1439         }
1440 
1441         if (hasBottomSheet) {
1442             mHeaderPaint.setAlpha((int) (mHeaderPaint.getAlpha() * mBottomSheetBackgroundAlpha));
1443         }
1444 
1445         // Draw header on background panel
1446         final float headerBottomNoScale =
1447                 getHeaderBottom() + getVisibleContainerView().getPaddingTop();
1448         final float headerHeightNoScale = headerBottomNoScale - topNoScale;
1449         final float headerBottomWithScaleOnTablet = topWithScale + headerHeightNoScale * scale;
1450         final float headerBottomOffset = (getVisibleContainerView().getHeight() * (1 - scale) / 2);
1451         final float headerBottomWithScaleOnPhone = headerBottomNoScale * scale + headerBottomOffset;
1452         final FloatingHeaderView headerView = getFloatingHeaderView();
1453         if (hasBottomSheet) {
1454             // Start adding header protection if search bar or tabs will attach to the top.
1455             if (!isSearchBarFloating() || mUsingTabs) {
1456                 mTmpRectF.set(
1457                         leftWithScale,
1458                         topWithScale,
1459                         rightWithScale,
1460                         headerBottomWithScaleOnTablet);
1461                 mTmpPath.reset();
1462                 mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
1463                 canvas.drawPath(mTmpPath, mHeaderPaint);
1464             }
1465         } else {
1466             canvas.drawRect(0, 0, canvas.getWidth(), headerBottomWithScaleOnPhone, mHeaderPaint);
1467         }
1468 
1469         // If tab exist (such as work profile), extend header with tab height
1470         final int tabsHeight = headerView.getPeripheralProtectionHeight(/* expectedHeight */ false);
1471         if (mTabsProtectionAlpha > 0 && tabsHeight != 0) {
1472             if (DEBUG_HEADER_PROTECTION) {
1473                 mHeaderPaint.setColor(Color.BLUE);
1474                 mHeaderPaint.setAlpha(255);
1475             } else {
1476                 float tabAlpha = getAlpha() * mTabsProtectionAlpha;
1477                 if (hasBottomSheet) {
1478                     tabAlpha *= mBottomSheetBackgroundAlpha;
1479                 }
1480                 mHeaderPaint.setAlpha((int) tabAlpha);
1481             }
1482             float left = 0f;
1483             float right = canvas.getWidth();
1484             if (hasBottomSheet) {
1485                 left = mBottomSheetBackground.getLeft() + horizontalScaleOffset;
1486                 right = mBottomSheetBackground.getRight() - horizontalScaleOffset;
1487             }
1488 
1489             final float tabTopWithScale = hasBottomSheet
1490                     ? headerBottomWithScaleOnTablet
1491                     : headerBottomWithScaleOnPhone;
1492             final float tabBottomWithScale = tabTopWithScale + tabsHeight * scale;
1493 
1494             canvas.drawRect(
1495                     left,
1496                     tabTopWithScale,
1497                     right,
1498                     tabBottomWithScale,
1499                     mHeaderPaint);
1500         }
1501     }
1502 
1503     /**
1504      * The height of the header protection as if the user scrolled down the app list.
1505      */
getHeaderProtectionHeight()1506     float getHeaderProtectionHeight() {
1507         float headerBottom = getHeaderBottom() - getTranslationY();
1508         if (mUsingTabs) {
1509             return headerBottom + mHeader.getPeripheralProtectionHeight(/* expectedHeight */ true);
1510         } else {
1511             return headerBottom;
1512         }
1513     }
1514 
getFastScrollerLetterList()1515     ConstraintLayout getFastScrollerLetterList() {
1516         return mFastScrollLetterLayout;
1517     }
1518 
1519     /**
1520      * redraws header protection
1521      */
invalidateHeader()1522     public void invalidateHeader() {
1523         if (mScrimView != null) {
1524             mScrimView.invalidate();
1525         }
1526     }
1527 
1528     /** Returns the position of the bottom edge of the header */
getHeaderBottom()1529     public int getHeaderBottom() {
1530         int bottom = (int) getTranslationY() + mHeader.getClipTop();
1531         if (isSearchBarFloating()) {
1532             if (mActivityContext.getDeviceProfile().shouldShowAllAppsOnSheet()) {
1533                 return bottom + mBottomSheetBackground.getTop();
1534             }
1535             return bottom;
1536         }
1537         return bottom + mHeader.getTop();
1538     }
1539 
isUsingTabs()1540     boolean isUsingTabs() {
1541         return mUsingTabs;
1542     }
1543 
1544     /**
1545      * Returns a view that denotes the visible part of all apps container view.
1546      */
getVisibleContainerView()1547     public View getVisibleContainerView() {
1548         return mBottomSheetBackground.getVisibility() == VISIBLE ? mBottomSheetBackground : this;
1549     }
1550 
onInitializeRecyclerView(RecyclerView rv)1551     protected void onInitializeRecyclerView(RecyclerView rv) {
1552         rv.addOnScrollListener(mScrollListener);
1553         mSearchUiDelegate.onInitializeRecyclerView(rv);
1554     }
1555 
1556     /** Returns the instance of @{code SearchTransitionController}. */
getSearchTransitionController()1557     public SearchTransitionController getSearchTransitionController() {
1558         return mSearchTransitionController;
1559     }
1560 
1561     /** Holds a {@link BaseAllAppsAdapter} and related fields. */
1562     public class AdapterHolder {
1563         public static final int MAIN = 0;
1564         public static final int WORK = 1;
1565         public static final int SEARCH = 2;
1566 
1567         private final int mType;
1568         public final BaseAllAppsAdapter<T> mAdapter;
1569         final RecyclerView.LayoutManager mLayoutManager;
1570         final AlphabeticalAppsList<T> mAppsList;
1571         final Rect mPadding = new Rect();
1572         AllAppsRecyclerView mRecyclerView;
1573         private OnFocusChangeListener mOnFocusChangeListener;
1574 
AdapterHolder(int type, AlphabeticalAppsList<T> appsList)1575         AdapterHolder(int type, AlphabeticalAppsList<T> appsList) {
1576             mType = type;
1577             mAppsList = appsList;
1578             mAdapter = createAdapter(mAppsList);
1579             mAppsList.setAdapter(mAdapter);
1580             mLayoutManager = mAdapter.getLayoutManager();
1581         }
1582 
setup(@onNull View rv, @Nullable Predicate<ItemInfo> matcher)1583         void setup(@NonNull View rv, @Nullable Predicate<ItemInfo> matcher) {
1584             mAppsList.updateItemFilter(matcher);
1585             mRecyclerView = (AllAppsRecyclerView) rv;
1586             mRecyclerView.bindFastScrollbar(mFastScroller, ALL_APPS_SCROLLER);
1587             mRecyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
1588             mRecyclerView.setApps(mAppsList);
1589             mRecyclerView.setLayoutManager(mLayoutManager);
1590             mRecyclerView.setAdapter(mAdapter);
1591             mRecyclerView.setHasFixedSize(true);
1592             // No animations will occur when changes occur to the items in this RecyclerView.
1593             mRecyclerView.setItemAnimator(null);
1594             onInitializeRecyclerView(mRecyclerView);
1595             // Use ViewGroupFocusHelper for SearchRecyclerView to draw focus outline for the
1596             // buttons in the view (e.g. query builder button and setting button)
1597             FocusedItemDecorator focusedItemDecorator = isSearch() ? new FocusedItemDecorator(
1598                     new ViewGroupFocusHelper(mRecyclerView)) : new FocusedItemDecorator(
1599                     mRecyclerView);
1600             mRecyclerView.addItemDecoration(focusedItemDecorator);
1601             mOnFocusChangeListener = focusedItemDecorator.getFocusListener();
1602             mAdapter.setIconFocusListener(mOnFocusChangeListener);
1603             applyPadding();
1604         }
1605 
applyPadding()1606         void applyPadding() {
1607             if (mRecyclerView != null) {
1608                 int bottomOffset = 0;
1609                 if (isWork() && mWorkManager.getWorkUtilityView() != null) {
1610                     bottomOffset = mInsets.bottom + mWorkManager.getWorkUtilityView().getHeight();
1611                 } else if (isMain() && mPrivateProfileManager != null) {
1612                     Optional<AdapterItem> privateSpaceHeaderItem = mAppsList.getAdapterItems()
1613                             .stream()
1614                             .filter(item -> item.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER)
1615                             .findFirst();
1616                     if (privateSpaceHeaderItem.isPresent()) {
1617                         bottomOffset = mPrivateSpaceBottomExtraSpace;
1618                     }
1619                 }
1620                 if (isSearchBarFloating()) {
1621                     bottomOffset += mSearchContainer.getHeight();
1622                 }
1623                 mRecyclerView.setPadding(mPadding.left, mPadding.top, mPadding.right,
1624                         mPadding.bottom + bottomOffset);
1625             }
1626         }
1627 
isWork()1628         private boolean isWork() {
1629             return mType == WORK;
1630         }
1631 
isSearch()1632         private boolean isSearch() {
1633             return mType == SEARCH;
1634         }
1635 
isMain()1636         private boolean isMain() {
1637             return mType == MAIN;
1638         }
1639     }
1640 }
1641