1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.allapps;
17 
18 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
19 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
20 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
21 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
22 
23 import android.animation.ValueAnimator;
24 import android.content.Context;
25 import android.content.res.Configuration;
26 import android.content.res.Resources;
27 import android.graphics.Canvas;
28 import android.graphics.Paint;
29 import android.graphics.Point;
30 import android.graphics.Rect;
31 import android.os.Process;
32 import android.text.Selection;
33 import android.text.SpannableStringBuilder;
34 import android.util.AttributeSet;
35 import android.view.KeyEvent;
36 import android.view.LayoutInflater;
37 import android.view.MotionEvent;
38 import android.view.View;
39 import android.view.ViewGroup;
40 import android.view.WindowInsets;
41 
42 import androidx.annotation.NonNull;
43 import androidx.annotation.Nullable;
44 import androidx.annotation.StringRes;
45 import androidx.recyclerview.widget.DefaultItemAnimator;
46 import androidx.recyclerview.widget.LinearLayoutManager;
47 import androidx.recyclerview.widget.RecyclerView;
48 
49 import com.android.launcher3.BaseDraggingActivity;
50 import com.android.launcher3.DeviceProfile;
51 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
52 import com.android.launcher3.DragSource;
53 import com.android.launcher3.DropTarget.DragObject;
54 import com.android.launcher3.Insettable;
55 import com.android.launcher3.InsettableFrameLayout;
56 import com.android.launcher3.R;
57 import com.android.launcher3.Utilities;
58 import com.android.launcher3.keyboard.FocusedItemDecorator;
59 import com.android.launcher3.model.data.AppInfo;
60 import com.android.launcher3.model.data.ItemInfo;
61 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
62 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
63 import com.android.launcher3.util.ItemInfoMatcher;
64 import com.android.launcher3.util.MultiValueAlpha;
65 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
66 import com.android.launcher3.util.Themes;
67 import com.android.launcher3.views.RecyclerViewFastScroller;
68 import com.android.launcher3.views.SpringRelativeLayout;
69 
70 import java.util.ArrayList;
71 
72 /**
73  * The all apps view container.
74  */
75 public class AllAppsContainerView extends SpringRelativeLayout implements DragSource,
76         Insettable, OnDeviceProfileChangeListener {
77 
78     private static final float FLING_VELOCITY_MULTIPLIER = 135f;
79     // Starts the springs after at least 55% of the animation has passed.
80     private static final float FLING_ANIMATION_THRESHOLD = 0.55f;
81     private static final int ALPHA_CHANNEL_COUNT = 2;
82 
83     protected final BaseDraggingActivity mLauncher;
84     protected final AdapterHolder[] mAH;
85     private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
86     private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
87     private final AllAppsStore mAllAppsStore = new AllAppsStore();
88 
89     private final Paint mNavBarScrimPaint;
90     private int mNavBarScrimHeight = 0;
91 
92     protected SearchUiManager mSearchUiManager;
93     private View mSearchContainer;
94     private AllAppsPagedView mViewPager;
95 
96     private FloatingHeaderView mHeader;
97     private WorkModeSwitch mWorkModeSwitch;
98 
99 
100     private SpannableStringBuilder mSearchQueryBuilder = null;
101 
102     protected boolean mUsingTabs;
103     private boolean mSearchModeWhileUsingTabs = false;
104 
105     protected RecyclerViewFastScroller mTouchHandler;
106     protected final Point mFastScrollerOffset = new Point();
107 
108     private final MultiValueAlpha mMultiValueAlpha;
109 
110     Rect mInsets = new Rect();
111 
AllAppsContainerView(Context context)112     public AllAppsContainerView(Context context) {
113         this(context, null);
114     }
115 
AllAppsContainerView(Context context, AttributeSet attrs)116     public AllAppsContainerView(Context context, AttributeSet attrs) {
117         this(context, attrs, 0);
118     }
119 
AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr)120     public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
121         super(context, attrs, defStyleAttr);
122 
123         mLauncher = BaseDraggingActivity.fromContext(context);
124         mLauncher.addOnDeviceProfileChangeListener(this);
125 
126         mSearchQueryBuilder = new SpannableStringBuilder();
127         Selection.setSelection(mSearchQueryBuilder, 0);
128 
129         mAH = new AdapterHolder[2];
130         mAH[AdapterHolder.MAIN] = new AdapterHolder(false /* isWork */);
131         mAH[AdapterHolder.WORK] = new AdapterHolder(true /* isWork */);
132 
133         mNavBarScrimPaint = new Paint();
134         mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
135 
136         mAllAppsStore.addUpdateListener(this::onAppsUpdated);
137 
138         addSpringView(R.id.all_apps_header);
139         addSpringView(R.id.apps_list_view);
140         addSpringView(R.id.all_apps_tabs_view_pager);
141 
142         mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT);
143     }
144 
145     /**
146      * Sets the long click listener for icons
147      */
setOnIconLongClickListener(OnLongClickListener listener)148     public void setOnIconLongClickListener(OnLongClickListener listener) {
149         for (AdapterHolder holder : mAH) {
150             holder.adapter.setOnIconLongClickListener(listener);
151         }
152     }
153 
getAppsStore()154     public AllAppsStore getAppsStore() {
155         return mAllAppsStore;
156     }
157 
getAlphaProperty(int index)158     public AlphaProperty getAlphaProperty(int index) {
159         return mMultiValueAlpha.getProperty(index);
160     }
161 
getWorkModeSwitch()162     public WorkModeSwitch getWorkModeSwitch() {
163         return mWorkModeSwitch;
164     }
165 
166 
167     @Override
setDampedScrollShift(float shift)168     protected void setDampedScrollShift(float shift) {
169         // Bound the shift amount to avoid content from drawing on top (Y-val) of the QSB.
170         float maxShift = getSearchView().getHeight() / 2f;
171         super.setDampedScrollShift(Utilities.boundToRange(shift, -maxShift, maxShift));
172     }
173 
174     @Override
onDeviceProfileChanged(DeviceProfile dp)175     public void onDeviceProfileChanged(DeviceProfile dp) {
176         for (AdapterHolder holder : mAH) {
177             if (holder.recyclerView != null) {
178                 // Remove all views and clear the pool, while keeping the data same. After this
179                 // call, all the viewHolders will be recreated.
180                 holder.recyclerView.swapAdapter(holder.recyclerView.getAdapter(), true);
181                 holder.recyclerView.getRecycledViewPool().clear();
182             }
183         }
184     }
185 
onAppsUpdated()186     private void onAppsUpdated() {
187         boolean hasWorkApps = false;
188         for (AppInfo app : mAllAppsStore.getApps()) {
189             if (mWorkMatcher.matches(app, null)) {
190                 hasWorkApps = true;
191                 break;
192             }
193         }
194         rebindAdapters(hasWorkApps);
195         if (hasWorkApps) {
196             resetWorkProfile();
197         }
198     }
199 
resetWorkProfile()200     private void resetWorkProfile() {
201         mWorkModeSwitch.update(!mAllAppsStore.hasModelFlag(FLAG_QUIET_MODE_ENABLED));
202         mAH[AdapterHolder.WORK].setupOverlay();
203         mAH[AdapterHolder.WORK].applyPadding();
204     }
205 
206     /**
207      * Returns whether the view itself will handle the touch event or not.
208      */
shouldContainerScroll(MotionEvent ev)209     public boolean shouldContainerScroll(MotionEvent ev) {
210         // IF the MotionEvent is inside the search box, and the container keeps on receiving
211         // touch input, container should move down.
212         if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) {
213             return true;
214         }
215         AllAppsRecyclerView rv = getActiveRecyclerView();
216         if (rv == null) {
217             return true;
218         }
219         if (rv.getScrollbar().getThumbOffsetY() >= 0 &&
220                 mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) {
221             return false;
222         }
223         return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
224     }
225 
226     @Override
onInterceptTouchEvent(MotionEvent ev)227     public boolean onInterceptTouchEvent(MotionEvent ev) {
228         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
229             AllAppsRecyclerView rv = getActiveRecyclerView();
230             if (rv != null &&
231                     rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
232                 mTouchHandler = rv.getScrollbar();
233             } else {
234                 mTouchHandler = null;
235             }
236         }
237         if (mTouchHandler != null) {
238             return mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
239         }
240         return false;
241     }
242 
243     @Override
onTouchEvent(MotionEvent ev)244     public boolean onTouchEvent(MotionEvent ev) {
245         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
246             AllAppsRecyclerView rv = getActiveRecyclerView();
247             if (rv != null && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(),
248                     mFastScrollerOffset)) {
249                 mTouchHandler = rv.getScrollbar();
250             } else {
251                 mTouchHandler = null;
252 
253             }
254         }
255         if (mTouchHandler != null) {
256             mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
257             return true;
258         }
259         return false;
260     }
261 
getDescription()262     public String getDescription() {
263         @StringRes int descriptionRes;
264         if (mUsingTabs) {
265             descriptionRes =
266                     mViewPager.getNextPage() == 0
267                             ? R.string.all_apps_button_personal_label
268                             : R.string.all_apps_button_work_label;
269         } else {
270             descriptionRes = R.string.all_apps_button_label;
271         }
272         return getContext().getString(descriptionRes);
273     }
274 
getActiveRecyclerView()275     public AllAppsRecyclerView getActiveRecyclerView() {
276         if (!mUsingTabs || mViewPager.getNextPage() == 0) {
277             return mAH[AdapterHolder.MAIN].recyclerView;
278         } else {
279             return mAH[AdapterHolder.WORK].recyclerView;
280         }
281     }
282 
getLayoutInflater()283     public LayoutInflater getLayoutInflater() {
284         return LayoutInflater.from(getContext());
285     }
286 
287     /**
288      * Resets the state of AllApps.
289      */
reset(boolean animate)290     public void reset(boolean animate) {
291         for (int i = 0; i < mAH.length; i++) {
292             if (mAH[i].recyclerView != null) {
293                 mAH[i].recyclerView.scrollToTop();
294             }
295         }
296         if (isHeaderVisible()) {
297             mHeader.reset(animate);
298         }
299         // Reset the search bar and base recycler view after transitioning home
300         mSearchUiManager.resetSearch();
301     }
302 
303     @Override
onFinishInflate()304     protected void onFinishInflate() {
305         super.onFinishInflate();
306 
307         // This is a focus listener that proxies focus from a view into the list view.  This is to
308         // work around the search box from getting first focus and showing the cursor.
309         setOnFocusChangeListener((v, hasFocus) -> {
310             if (hasFocus && getActiveRecyclerView() != null) {
311                 getActiveRecyclerView().requestFocus();
312             }
313         });
314 
315         mHeader = findViewById(R.id.all_apps_header);
316         rebindAdapters(mUsingTabs, true /* force */);
317 
318         mSearchContainer = findViewById(R.id.search_container_all_apps);
319         mSearchUiManager = (SearchUiManager) mSearchContainer;
320         mSearchUiManager.initialize(this);
321     }
322 
getSearchUiManager()323     public SearchUiManager getSearchUiManager() {
324         return mSearchUiManager;
325     }
326 
327     @Override
dispatchKeyEvent(KeyEvent event)328     public boolean dispatchKeyEvent(KeyEvent event) {
329         mSearchUiManager.preDispatchKeyEvent(event);
330         return super.dispatchKeyEvent(event);
331     }
332 
333     @Override
onDropCompleted(View target, DragObject d, boolean success)334     public void onDropCompleted(View target, DragObject d, boolean success) {
335     }
336 
337     @Override
fillInLogContainerData(ItemInfo childInfo, Target child, ArrayList<Target> parents)338     public void fillInLogContainerData(ItemInfo childInfo, Target child,
339             ArrayList<Target> parents) {
340         parents.add(newContainerTarget(
341                 getApps().hasFilter() ? ContainerType.SEARCHRESULT : ContainerType.ALLAPPS));
342     }
343 
344     @Override
setInsets(Rect insets)345     public void setInsets(Rect insets) {
346         mInsets.set(insets);
347         DeviceProfile grid = mLauncher.getDeviceProfile();
348         int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx
349                 + grid.cellLayoutPaddingLeftRightPx;
350 
351         for (int i = 0; i < mAH.length; i++) {
352             mAH[i].padding.bottom = insets.bottom;
353             mAH[i].padding.left = mAH[i].padding.right = leftRightPadding;
354             mAH[i].applyPadding();
355             mAH[i].setupOverlay();
356         }
357 
358         ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
359         mlp.leftMargin = insets.left;
360         mlp.rightMargin = insets.right;
361         setLayoutParams(mlp);
362 
363         if (grid.isVerticalBarLayout()) {
364             setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
365         } else {
366             setPadding(0, 0, 0, 0);
367         }
368 
369         InsettableFrameLayout.dispatchInsets(this, insets);
370     }
371 
372     @Override
dispatchApplyWindowInsets(WindowInsets insets)373     public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
374         if (Utilities.ATLEAST_Q) {
375             mNavBarScrimHeight = insets.getTappableElementInsets().bottom;
376         } else {
377             mNavBarScrimHeight = insets.getStableInsetBottom();
378         }
379         return super.dispatchApplyWindowInsets(insets);
380     }
381 
382     @Override
dispatchDraw(Canvas canvas)383     protected void dispatchDraw(Canvas canvas) {
384         super.dispatchDraw(canvas);
385 
386         if (mNavBarScrimHeight > 0) {
387             canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
388                     mNavBarScrimPaint);
389         }
390     }
391 
392     @Override
getCanvasClipTopForOverscroll()393     public int getCanvasClipTopForOverscroll() {
394         // Do not clip if the QSB is attached to the spring, otherwise the QSB will get clipped.
395         return mSpringViews.get(getSearchView().getId()) ? 0 : mHeader.getTop();
396     }
397 
rebindAdapters(boolean showTabs)398     private void rebindAdapters(boolean showTabs) {
399         rebindAdapters(showTabs, false /* force */);
400     }
401 
rebindAdapters(boolean showTabs, boolean force)402     protected void rebindAdapters(boolean showTabs, boolean force) {
403         if (showTabs == mUsingTabs && !force) {
404             return;
405         }
406         replaceRVContainer(showTabs);
407         mUsingTabs = showTabs;
408 
409         mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.MAIN].recyclerView);
410         mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.WORK].recyclerView);
411 
412         if (mUsingTabs) {
413             setupWorkToggle();
414             mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
415             mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
416             mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN);
417             findViewById(R.id.tab_personal)
418                     .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
419             findViewById(R.id.tab_work)
420                     .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
421             onTabChanged(mViewPager.getNextPage());
422         } else {
423             mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
424             mAH[AdapterHolder.WORK].recyclerView = null;
425             if (mWorkModeSwitch != null) {
426                 ((ViewGroup) mWorkModeSwitch.getParent()).removeView(mWorkModeSwitch);
427                 mWorkModeSwitch = null;
428             }
429         }
430         setupHeader();
431 
432         mAllAppsStore.registerIconContainer(mAH[AdapterHolder.MAIN].recyclerView);
433         mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView);
434     }
435 
setupWorkToggle()436     private void setupWorkToggle() {
437         if (Utilities.ATLEAST_P) {
438             mWorkModeSwitch = (WorkModeSwitch) mLauncher.getLayoutInflater().inflate(
439                     R.layout.work_mode_switch, this, false);
440             this.addView(mWorkModeSwitch);
441             mWorkModeSwitch.setInsets(mInsets);
442             mWorkModeSwitch.post(() -> mAH[AdapterHolder.WORK].applyPadding());
443         }
444     }
445 
446     @Override
onConfigurationChanged(Configuration newConfig)447     protected void onConfigurationChanged(Configuration newConfig) {
448         super.onConfigurationChanged(newConfig);
449         View overlay = mAH[AdapterHolder.WORK].getOverlayView();
450         int v = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ? GONE : VISIBLE;
451         overlay.findViewById(R.id.work_apps_paused_title).setVisibility(v);
452         overlay.findViewById(R.id.work_apps_paused_content).setVisibility(v);
453     }
454 
replaceRVContainer(boolean showTabs)455     private void replaceRVContainer(boolean showTabs) {
456         for (int i = 0; i < mAH.length; i++) {
457             if (mAH[i].recyclerView != null) {
458                 mAH[i].recyclerView.setLayoutManager(null);
459             }
460         }
461         View oldView = getRecyclerViewContainer();
462         int index = indexOfChild(oldView);
463         removeView(oldView);
464         int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
465         View newView = getLayoutInflater().inflate(layout, this, false);
466         addView(newView, index);
467         if (showTabs) {
468             mViewPager = (AllAppsPagedView) newView;
469             mViewPager.initParentViews(this);
470             mViewPager.getPageIndicator().setContainerView(this);
471         } else {
472             mViewPager = null;
473         }
474     }
475 
getRecyclerViewContainer()476     public View getRecyclerViewContainer() {
477         return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
478     }
479 
onTabChanged(int pos)480     public void onTabChanged(int pos) {
481         mHeader.setMainActive(pos == 0);
482         if (mAH[pos].recyclerView != null) {
483             mAH[pos].recyclerView.bindFastScrollbar();
484         }
485         reset(true /* animate */);
486         if (mWorkModeSwitch != null) {
487             mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK
488                     && mAllAppsStore.hasModelFlag(
489                             FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION));
490         }
491     }
492 
493     // Used by tests only
isDescendantViewVisible(int viewId)494     private boolean isDescendantViewVisible(int viewId) {
495         final View view = findViewById(viewId);
496         if (view == null) return false;
497 
498         if (!view.isShown()) return false;
499 
500         return view.getGlobalVisibleRect(new Rect());
501     }
502 
503     // Used by tests only
isPersonalTabVisible()504     public boolean isPersonalTabVisible() {
505         return isDescendantViewVisible(R.id.tab_personal);
506     }
507 
508     // Used by tests only
isWorkTabVisible()509     public boolean isWorkTabVisible() {
510         return isDescendantViewVisible(R.id.tab_work);
511     }
512 
getApps()513     public AlphabeticalAppsList getApps() {
514         return mAH[AdapterHolder.MAIN].appsList;
515     }
516 
getFloatingHeaderView()517     public FloatingHeaderView getFloatingHeaderView() {
518         return mHeader;
519     }
520 
getSearchView()521     public View getSearchView() {
522         return mSearchContainer;
523     }
524 
getContentView()525     public View getContentView() {
526         return mViewPager == null ? getActiveRecyclerView() : mViewPager;
527     }
528 
getScrollBar()529     public RecyclerViewFastScroller getScrollBar() {
530         AllAppsRecyclerView rv = getActiveRecyclerView();
531         return rv == null ? null : rv.getScrollbar();
532     }
533 
setupHeader()534     public void setupHeader() {
535         mHeader.setVisibility(View.VISIBLE);
536         mHeader.setup(mAH, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView == null);
537 
538         int padding = mHeader.getMaxTranslation();
539         for (int i = 0; i < mAH.length; i++) {
540             mAH[i].padding.top = padding;
541             mAH[i].applyPadding();
542         }
543     }
544 
setLastSearchQuery(String query)545     public void setLastSearchQuery(String query) {
546         for (int i = 0; i < mAH.length; i++) {
547             mAH[i].adapter.setLastSearchQuery(query);
548         }
549         if (mUsingTabs) {
550             mSearchModeWhileUsingTabs = true;
551             rebindAdapters(false); // hide tabs
552         }
553     }
554 
onClearSearchResult()555     public void onClearSearchResult() {
556         if (mSearchModeWhileUsingTabs) {
557             rebindAdapters(true); // show tabs
558             mSearchModeWhileUsingTabs = false;
559         }
560     }
561 
onSearchResultsChanged()562     public void onSearchResultsChanged() {
563         for (int i = 0; i < mAH.length; i++) {
564             if (mAH[i].recyclerView != null) {
565                 mAH[i].recyclerView.onSearchResultsChanged();
566             }
567         }
568     }
569 
setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled)570     public void setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled) {
571         for (int i = 0; i < mAH.length; i++) {
572             mAH[i].applyVerticalFadingEdgeEnabled(enabled);
573         }
574     }
575 
addElevationController(RecyclerView.OnScrollListener scrollListener)576     public void addElevationController(RecyclerView.OnScrollListener scrollListener) {
577         if (!mUsingTabs) {
578             mAH[AdapterHolder.MAIN].recyclerView.addOnScrollListener(scrollListener);
579         }
580     }
581 
isHeaderVisible()582     public boolean isHeaderVisible() {
583         return mHeader != null && mHeader.getVisibility() == View.VISIBLE;
584     }
585 
586     /**
587      * Adds an update listener to {@param animator} that adds springs to the animation.
588      */
addSpringFromFlingUpdateListener(ValueAnimator animator, float velocity)589     public void addSpringFromFlingUpdateListener(ValueAnimator animator, float velocity) {
590         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
591             boolean shouldSpring = true;
592 
593             @Override
594             public void onAnimationUpdate(ValueAnimator valueAnimator) {
595                 if (shouldSpring
596                         && valueAnimator.getAnimatedFraction() >= FLING_ANIMATION_THRESHOLD) {
597                     int searchViewId = getSearchView().getId();
598                     addSpringView(searchViewId);
599                     finishWithShiftAndVelocity(1, velocity * FLING_VELOCITY_MULTIPLIER,
600                             (anim, canceled, value, velocity) -> removeSpringView(searchViewId));
601 
602                     shouldSpring = false;
603                 }
604             }
605         });
606     }
607 
608     @Override
getDrawingRect(Rect outRect)609     public void getDrawingRect(Rect outRect) {
610         super.getDrawingRect(outRect);
611         outRect.offset(0, (int) getTranslationY());
612     }
613 
614     public class AdapterHolder {
615         public static final int MAIN = 0;
616         public static final int WORK = 1;
617 
618         private ItemInfoMatcher mInfoMatcher;
619         private final boolean mIsWork;
620         public final AllAppsGridAdapter adapter;
621         final LinearLayoutManager layoutManager;
622         final AlphabeticalAppsList appsList;
623         final Rect padding = new Rect();
624         AllAppsRecyclerView recyclerView;
625         boolean verticalFadingEdge;
626         private View mOverlay;
627 
628         boolean mWorkDisabled;
629 
AdapterHolder(boolean isWork)630         AdapterHolder(boolean isWork) {
631             mIsWork = isWork;
632             appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork);
633             adapter = new AllAppsGridAdapter(mLauncher, getLayoutInflater(), appsList);
634             appsList.setAdapter(adapter);
635             layoutManager = adapter.getLayoutManager();
636         }
637 
setup(@onNull View rv, @Nullable ItemInfoMatcher matcher)638         void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
639             mInfoMatcher = matcher;
640             appsList.updateItemFilter(matcher);
641             recyclerView = (AllAppsRecyclerView) rv;
642             recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
643             recyclerView.setApps(appsList);
644             recyclerView.setLayoutManager(layoutManager);
645             recyclerView.setAdapter(adapter);
646             recyclerView.setHasFixedSize(true);
647             // No animations will occur when changes occur to the items in this RecyclerView.
648             recyclerView.setItemAnimator(null);
649             FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(recyclerView);
650             recyclerView.addItemDecoration(focusedItemDecorator);
651             adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
652             applyVerticalFadingEdgeEnabled(verticalFadingEdge);
653             applyPadding();
654             setupOverlay();
655         }
656 
setupOverlay()657         void setupOverlay() {
658             if (!mIsWork || recyclerView == null) return;
659             boolean workDisabled = mAllAppsStore.hasModelFlag(FLAG_QUIET_MODE_ENABLED);
660             if (mWorkDisabled == workDisabled) return;
661             recyclerView.setContentDescription(workDisabled ? mLauncher.getString(
662                     R.string.work_apps_paused_content_description) : null);
663             View overlayView = getOverlayView();
664             recyclerView.setItemAnimator(new DefaultItemAnimator());
665             if (workDisabled) {
666                 overlayView.setAlpha(0);
667                 recyclerView.addAutoSizedOverlay(overlayView);
668                 overlayView.animate().alpha(1).withEndAction(
669                         () -> {
670                             appsList.updateItemFilter((info, cn) -> false);
671                             recyclerView.setItemAnimator(null);
672                         }).start();
673             } else if (mInfoMatcher != null) {
674                 appsList.updateItemFilter(mInfoMatcher);
675                 overlayView.animate().alpha(0).withEndAction(() -> {
676                     recyclerView.setItemAnimator(null);
677                     recyclerView.clearAutoSizedOverlays();
678                 }).start();
679             }
680             mWorkDisabled = workDisabled;
681         }
682 
applyPadding()683         void applyPadding() {
684             if (recyclerView != null) {
685                 Resources res = getResources();
686                 int switchH = res.getDimensionPixelSize(R.dimen.work_profile_footer_padding) * 2
687                         + mInsets.bottom + Utilities.calculateTextHeight(
688                         res.getDimension(R.dimen.work_profile_footer_text_size));
689 
690                 int bottomOffset = mWorkModeSwitch != null && mIsWork ? switchH : 0;
691                 recyclerView.setPadding(padding.left, padding.top, padding.right,
692                         padding.bottom + bottomOffset);
693             }
694         }
695 
applyVerticalFadingEdgeEnabled(boolean enabled)696         public void applyVerticalFadingEdgeEnabled(boolean enabled) {
697             verticalFadingEdge = enabled;
698             mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs
699                     && verticalFadingEdge);
700         }
701 
getOverlayView()702         private View getOverlayView() {
703             if (mOverlay == null) {
704                 mOverlay = mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null);
705             }
706             return mOverlay;
707         }
708     }
709 }
710