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