• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.allapps;
17 
18 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB;
19 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB;
20 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
21 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
22 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
23 
24 import android.animation.Animator;
25 import android.animation.AnimatorListenerAdapter;
26 import android.animation.ValueAnimator;
27 import android.content.Context;
28 import android.content.res.Resources;
29 import android.graphics.Canvas;
30 import android.graphics.Color;
31 import android.graphics.Paint;
32 import android.graphics.Point;
33 import android.graphics.Rect;
34 import android.os.Bundle;
35 import android.os.Parcelable;
36 import android.os.Process;
37 import android.text.Selection;
38 import android.text.SpannableStringBuilder;
39 import android.util.AttributeSet;
40 import android.util.Log;
41 import android.util.SparseArray;
42 import android.view.KeyEvent;
43 import android.view.LayoutInflater;
44 import android.view.MotionEvent;
45 import android.view.View;
46 import android.view.ViewGroup;
47 import android.view.WindowInsets;
48 
49 import androidx.annotation.NonNull;
50 import androidx.annotation.Nullable;
51 import androidx.annotation.StringRes;
52 import androidx.annotation.VisibleForTesting;
53 import androidx.core.graphics.ColorUtils;
54 import androidx.recyclerview.widget.LinearLayoutManager;
55 import androidx.recyclerview.widget.RecyclerView;
56 
57 import com.android.launcher3.BaseDraggingActivity;
58 import com.android.launcher3.DeviceProfile;
59 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
60 import com.android.launcher3.DragSource;
61 import com.android.launcher3.DropTarget.DragObject;
62 import com.android.launcher3.Insettable;
63 import com.android.launcher3.InsettableFrameLayout;
64 import com.android.launcher3.R;
65 import com.android.launcher3.Utilities;
66 import com.android.launcher3.allapps.search.SearchAdapterProvider;
67 import com.android.launcher3.config.FeatureFlags;
68 import com.android.launcher3.keyboard.FocusedItemDecorator;
69 import com.android.launcher3.model.data.AppInfo;
70 import com.android.launcher3.testing.TestProtocol;
71 import com.android.launcher3.util.ItemInfoMatcher;
72 import com.android.launcher3.util.Themes;
73 import com.android.launcher3.views.RecyclerViewFastScroller;
74 import com.android.launcher3.views.ScrimView;
75 import com.android.launcher3.views.SpringRelativeLayout;
76 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
77 
78 /**
79  * The all apps view container.
80  */
81 public class AllAppsContainerView extends SpringRelativeLayout implements DragSource,
82         Insettable, OnDeviceProfileChangeListener, OnActivePageChangedListener,
83         ScrimView.ScrimDrawingController {
84 
85     private static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page";
86 
87     public static final float PULL_MULTIPLIER = .02f;
88     public static final float FLING_VELOCITY_MULTIPLIER = 1200f;
89 
90     private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
91 
92     protected final BaseDraggingActivity mLauncher;
93     protected final AdapterHolder[] mAH;
94     private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
95     private final ItemInfoMatcher mWorkMatcher = mPersonalMatcher.negate();
96     private final AllAppsStore mAllAppsStore = new AllAppsStore();
97 
98     private final RecyclerView.OnScrollListener mScrollListener =
99             new RecyclerView.OnScrollListener() {
100                 @Override
101                 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
102                     updateHeaderScroll(((AllAppsRecyclerView) recyclerView).getCurrentScrollY());
103                 }
104             };
105 
106     private final Paint mNavBarScrimPaint;
107     private int mNavBarScrimHeight = 0;
108 
109     protected SearchUiManager mSearchUiManager;
110     private View mSearchContainer;
111     private AllAppsPagedView mViewPager;
112 
113     protected FloatingHeaderView mHeader;
114     private float mHeaderTop;
115     private WorkModeSwitch mWorkModeSwitch;
116 
117 
118     private SpannableStringBuilder mSearchQueryBuilder = null;
119 
120     protected boolean mUsingTabs;
121     private boolean mSearchModeWhileUsingTabs = false;
122 
123     protected RecyclerViewFastScroller mTouchHandler;
124     protected final Point mFastScrollerOffset = new Point();
125 
126     private Rect mInsets = new Rect();
127 
128     private SearchAdapterProvider mSearchAdapterProvider;
129     private WorkAdapterProvider mWorkAdapterProvider;
130     private final int mScrimColor;
131     private final int mHeaderProtectionColor;
132     private final float mHeaderThreshold;
133     private ScrimView mScrimView;
134     private int mHeaderColor;
135 
AllAppsContainerView(Context context)136     public AllAppsContainerView(Context context) {
137         this(context, null);
138     }
139 
AllAppsContainerView(Context context, AttributeSet attrs)140     public AllAppsContainerView(Context context, AttributeSet attrs) {
141         this(context, attrs, 0);
142     }
143 
AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr)144     public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
145         super(context, attrs, defStyleAttr);
146 
147         mLauncher = BaseDraggingActivity.fromContext(context);
148 
149         mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
150         mHeaderThreshold = getResources().getDimensionPixelSize(
151                 R.dimen.dynamic_grid_cell_border_spacing);
152         mHeaderProtectionColor = Themes.getAttrColor(context, R.attr.allappsHeaderProtectionColor);
153 
154         mLauncher.addOnDeviceProfileChangeListener(this);
155 
156         mSearchAdapterProvider = mLauncher.createSearchAdapterProvider(this);
157         mSearchQueryBuilder = new SpannableStringBuilder();
158         Selection.setSelection(mSearchQueryBuilder, 0);
159 
160         mAH = new AdapterHolder[2];
161         mWorkAdapterProvider = new WorkAdapterProvider(mLauncher, () -> {
162             if (mAH[AdapterHolder.WORK] != null) {
163                 mAH[AdapterHolder.WORK].appsList.updateAdapterItems();
164             }
165         });
166         mAH[AdapterHolder.MAIN] = new AdapterHolder(false /* isWork */);
167         mAH[AdapterHolder.WORK] = new AdapterHolder(true /* isWork */);
168 
169         mNavBarScrimPaint = new Paint();
170         mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
171 
172         mAllAppsStore.addUpdateListener(this::onAppsUpdated);
173     }
174 
175     @Override
dispatchRestoreInstanceState(SparseArray<Parcelable> sparseArray)176     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> sparseArray) {
177         try {
178             // Many slice view id is not properly assigned, and hence throws null
179             // pointer exception in the underneath method. Catching the exception
180             // simply doesn't restore these slice views. This doesn't have any
181             // user visible effect because because we query them again.
182             super.dispatchRestoreInstanceState(sparseArray);
183         } catch (Exception e) {
184             Log.e("AllAppsContainerView", "restoreInstanceState viewId = 0", e);
185         }
186 
187         Bundle state = (Bundle) sparseArray.get(R.id.work_tab_state_id, null);
188         if (state != null) {
189             int currentPage = state.getInt(BUNDLE_KEY_CURRENT_PAGE, 0);
190             if (currentPage != 0 && mViewPager != null) {
191                 mViewPager.setCurrentPage(currentPage);
192                 rebindAdapters(true);
193             } else {
194                 reset(true);
195             }
196         }
197 
198     }
199 
200     @Override
dispatchSaveInstanceState(SparseArray<Parcelable> container)201     protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
202         super.dispatchSaveInstanceState(container);
203         Bundle state = new Bundle();
204         state.putInt(BUNDLE_KEY_CURRENT_PAGE, getCurrentPage());
205         container.put(R.id.work_tab_state_id, state);
206     }
207 
208     /**
209      * Sets the long click listener for icons
210      */
setOnIconLongClickListener(OnLongClickListener listener)211     public void setOnIconLongClickListener(OnLongClickListener listener) {
212         for (AdapterHolder holder : mAH) {
213             holder.adapter.setOnIconLongClickListener(listener);
214         }
215     }
216 
getAppsStore()217     public AllAppsStore getAppsStore() {
218         return mAllAppsStore;
219     }
220 
getWorkModeSwitch()221     public WorkModeSwitch getWorkModeSwitch() {
222         return mWorkModeSwitch;
223     }
224 
225     @Override
onDeviceProfileChanged(DeviceProfile dp)226     public void onDeviceProfileChanged(DeviceProfile dp) {
227         for (AdapterHolder holder : mAH) {
228             holder.adapter.setAppsPerRow(dp.numShownAllAppsColumns);
229             if (holder.recyclerView != null) {
230                 // Remove all views and clear the pool, while keeping the data same. After this
231                 // call, all the viewHolders will be recreated.
232                 holder.recyclerView.swapAdapter(holder.recyclerView.getAdapter(), true);
233                 holder.recyclerView.getRecycledViewPool().clear();
234             }
235         }
236     }
237 
onAppsUpdated()238     private void onAppsUpdated() {
239         boolean hasWorkApps = false;
240         for (AppInfo app : mAllAppsStore.getApps()) {
241             if (mWorkMatcher.matches(app, null)) {
242                 hasWorkApps = true;
243                 break;
244             }
245         }
246         if (!mAH[AdapterHolder.MAIN].appsList.hasFilter()) {
247             rebindAdapters(hasWorkApps);
248             if (hasWorkApps) {
249                 resetWorkProfile();
250             }
251         }
252     }
253 
resetWorkProfile()254     private void resetWorkProfile() {
255         boolean isEnabled = !mAllAppsStore.hasModelFlag(FLAG_QUIET_MODE_ENABLED);
256         if (mWorkModeSwitch != null) {
257             mWorkModeSwitch.updateCurrentState(isEnabled);
258         }
259         mWorkAdapterProvider.updateCurrentState(isEnabled);
260     }
261 
262     /**
263      * Returns whether the view itself will handle the touch event or not.
264      */
shouldContainerScroll(MotionEvent ev)265     public boolean shouldContainerScroll(MotionEvent ev) {
266         // IF the MotionEvent is inside the search box, and the container keeps on receiving
267         // touch input, container should move down.
268         if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) {
269             return true;
270         }
271         AllAppsRecyclerView rv = getActiveRecyclerView();
272         if (rv == null) {
273             return true;
274         }
275         if (rv.getScrollbar().getThumbOffsetY() >= 0 &&
276                 mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) {
277             return false;
278         }
279         return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
280     }
281 
282     @Override
onInterceptTouchEvent(MotionEvent ev)283     public boolean onInterceptTouchEvent(MotionEvent ev) {
284         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
285             AllAppsRecyclerView rv = getActiveRecyclerView();
286             if (rv != null &&
287                     rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
288                 mTouchHandler = rv.getScrollbar();
289             } else {
290                 mTouchHandler = null;
291             }
292         }
293         if (mTouchHandler != null) {
294             return mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
295         }
296         return false;
297     }
298 
299     @Override
onTouchEvent(MotionEvent ev)300     public boolean onTouchEvent(MotionEvent ev) {
301         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
302             AllAppsRecyclerView rv = getActiveRecyclerView();
303             if (rv != null && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(),
304                     mFastScrollerOffset)) {
305                 mTouchHandler = rv.getScrollbar();
306             } else {
307                 mTouchHandler = null;
308 
309             }
310         }
311         if (mTouchHandler != null) {
312             mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
313             return true;
314         }
315         return false;
316     }
317 
getDescription()318     public String getDescription() {
319         @StringRes int descriptionRes;
320         if (mUsingTabs) {
321             descriptionRes =
322                     mViewPager.getNextPage() == 0
323                             ? R.string.all_apps_button_personal_label
324                             : R.string.all_apps_button_work_label;
325         } else {
326             descriptionRes = R.string.all_apps_button_label;
327         }
328         return getContext().getString(descriptionRes);
329     }
330 
getActiveRecyclerView()331     public AllAppsRecyclerView getActiveRecyclerView() {
332         if (!mUsingTabs || mViewPager.getNextPage() == 0) {
333             return mAH[AdapterHolder.MAIN].recyclerView;
334         } else {
335             return mAH[AdapterHolder.WORK].recyclerView;
336         }
337     }
338 
getLayoutInflater()339     public LayoutInflater getLayoutInflater() {
340         return LayoutInflater.from(getContext());
341     }
342 
343     /**
344      * Resets the state of AllApps.
345      */
reset(boolean animate)346     public void reset(boolean animate) {
347         for (int i = 0; i < mAH.length; i++) {
348             if (mAH[i].recyclerView != null) {
349                 mAH[i].recyclerView.scrollToTop();
350             }
351         }
352         if (isHeaderVisible()) {
353             mHeader.reset(animate);
354         }
355         // Reset the search bar and base recycler view after transitioning home
356         mSearchUiManager.resetSearch();
357         updateHeaderScroll(0);
358     }
359 
360     @Override
onFinishInflate()361     protected void onFinishInflate() {
362         super.onFinishInflate();
363 
364         // This is a focus listener that proxies focus from a view into the list view.  This is to
365         // work around the search box from getting first focus and showing the cursor.
366         setOnFocusChangeListener((v, hasFocus) -> {
367             if (hasFocus && getActiveRecyclerView() != null) {
368                 getActiveRecyclerView().requestFocus();
369             }
370         });
371 
372         mHeader = findViewById(R.id.all_apps_header);
373         rebindAdapters(mUsingTabs, true /* force */);
374 
375         mSearchContainer = findViewById(R.id.search_container_all_apps);
376         mSearchUiManager = (SearchUiManager) mSearchContainer;
377         mSearchUiManager.initializeSearch(this);
378     }
379 
getSearchUiManager()380     public SearchUiManager getSearchUiManager() {
381         return mSearchUiManager;
382     }
383 
384     @Override
dispatchKeyEvent(KeyEvent event)385     public boolean dispatchKeyEvent(KeyEvent event) {
386         mSearchUiManager.preDispatchKeyEvent(event);
387         return super.dispatchKeyEvent(event);
388     }
389 
390     @Override
onDropCompleted(View target, DragObject d, boolean success)391     public void onDropCompleted(View target, DragObject d, boolean success) {
392     }
393 
394     @Override
setInsets(Rect insets)395     public void setInsets(Rect insets) {
396         mInsets.set(insets);
397         DeviceProfile grid = mLauncher.getDeviceProfile();
398         int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx
399                 + grid.cellLayoutPaddingLeftRightPx;
400 
401         for (int i = 0; i < mAH.length; i++) {
402             mAH[i].padding.bottom = insets.bottom;
403             mAH[i].padding.left = mAH[i].padding.right = leftRightPadding;
404             mAH[i].applyPadding();
405         }
406 
407         ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
408         mlp.leftMargin = insets.left;
409         mlp.rightMargin = insets.right;
410         setLayoutParams(mlp);
411 
412         if (grid.isVerticalBarLayout()) {
413             setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
414         } else {
415             setPadding(0, 0, 0, 0);
416         }
417 
418         InsettableFrameLayout.dispatchInsets(this, insets);
419     }
420 
421     @Override
dispatchApplyWindowInsets(WindowInsets insets)422     public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
423         if (Utilities.ATLEAST_Q) {
424             mNavBarScrimHeight = insets.getTappableElementInsets().bottom
425                     - mLauncher.getDeviceProfile().nonOverlappingTaskbarInset;
426         } else {
427             mNavBarScrimHeight = insets.getStableInsetBottom();
428         }
429         return super.dispatchApplyWindowInsets(insets);
430     }
431 
432     @Override
dispatchDraw(Canvas canvas)433     protected void dispatchDraw(Canvas canvas) {
434         super.dispatchDraw(canvas);
435 
436         if (mNavBarScrimHeight > 0) {
437             canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
438                     mNavBarScrimPaint);
439         }
440     }
441 
rebindAdapters(boolean showTabs)442     private void rebindAdapters(boolean showTabs) {
443         rebindAdapters(showTabs, false /* force */);
444     }
445 
rebindAdapters(boolean showTabs, boolean force)446     protected void rebindAdapters(boolean showTabs, boolean force) {
447         if (showTabs == mUsingTabs && !force) {
448             return;
449         }
450         replaceRVContainer(showTabs);
451         mUsingTabs = showTabs;
452 
453         mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.MAIN].recyclerView);
454         mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.WORK].recyclerView);
455 
456         if (mUsingTabs) {
457             setupWorkToggle();
458             mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
459             mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
460             mAH[AdapterHolder.WORK].recyclerView.setId(R.id.apps_list_view_work);
461             mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN);
462             findViewById(R.id.tab_personal)
463                     .setOnClickListener((View view) -> {
464                         if (mViewPager.snapToPage(AdapterHolder.MAIN)) {
465                             mLauncher.getStatsLogManager().logger()
466                                     .log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB);
467                         }
468                     });
469             findViewById(R.id.tab_work)
470                     .setOnClickListener((View view) -> {
471                         if (mViewPager.snapToPage(AdapterHolder.WORK)) {
472                             mLauncher.getStatsLogManager().logger()
473                                     .log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB);
474                         }
475                     });
476             onActivePageChanged(mViewPager.getNextPage());
477         } else {
478             mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
479             mAH[AdapterHolder.WORK].recyclerView = null;
480         }
481         setupHeader();
482 
483         mAllAppsStore.registerIconContainer(mAH[AdapterHolder.MAIN].recyclerView);
484         mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView);
485     }
486 
setupWorkToggle()487     private void setupWorkToggle() {
488         if (Utilities.ATLEAST_P) {
489             mWorkModeSwitch = (WorkModeSwitch) mLauncher.getLayoutInflater().inflate(
490                     R.layout.work_mode_fab, this, false);
491             this.addView(mWorkModeSwitch);
492             mWorkModeSwitch.setInsets(mInsets);
493             mWorkModeSwitch.post(() -> {
494                 mAH[AdapterHolder.WORK].applyPadding();
495                 resetWorkProfile();
496             });
497         }
498     }
499 
replaceRVContainer(boolean showTabs)500     private void replaceRVContainer(boolean showTabs) {
501         for (int i = 0; i < mAH.length; i++) {
502             if (mAH[i].recyclerView != null) {
503                 mAH[i].recyclerView.setLayoutManager(null);
504             }
505         }
506         View oldView = getRecyclerViewContainer();
507         int index = indexOfChild(oldView);
508         removeView(oldView);
509         int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
510         View newView = getLayoutInflater().inflate(layout, this, false);
511         addView(newView, index);
512         if (TestProtocol.sDebugTracing) {
513             Log.d(TestProtocol.WORK_PROFILE_REMOVED, "should show tabs:" + showTabs,
514                     new Exception());
515         }
516         if (showTabs) {
517             mViewPager = (AllAppsPagedView) newView;
518             mViewPager.initParentViews(this);
519             mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
520         } else {
521             mViewPager = null;
522         }
523     }
524 
getRecyclerViewContainer()525     public View getRecyclerViewContainer() {
526         return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
527     }
528 
529     @Override
onActivePageChanged(int currentActivePage)530     public void onActivePageChanged(int currentActivePage) {
531         mHeader.setMainActive(currentActivePage == AdapterHolder.MAIN);
532         if (mAH[currentActivePage].recyclerView != null) {
533             mAH[currentActivePage].recyclerView.bindFastScrollbar();
534         }
535         reset(true /* animate */);
536         if (mWorkModeSwitch != null) {
537             mWorkModeSwitch.setWorkTabVisible(currentActivePage == AdapterHolder.WORK
538                     && mAllAppsStore.hasModelFlag(
539                     FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION));
540 
541             if (currentActivePage == AdapterHolder.WORK) {
542                 if (mWorkModeSwitch.getParent() == null) {
543                     addView(mWorkModeSwitch);
544                 }
545             } else {
546                 removeView(mWorkModeSwitch);
547             }
548         }
549     }
550 
551     // Used by tests only
isDescendantViewVisible(int viewId)552     private boolean isDescendantViewVisible(int viewId) {
553         final View view = findViewById(viewId);
554         if (view == null) return false;
555 
556         if (!view.isShown()) return false;
557 
558         return view.getGlobalVisibleRect(new Rect());
559     }
560 
561     @VisibleForTesting
isPersonalTabVisible()562     public boolean isPersonalTabVisible() {
563         return isDescendantViewVisible(R.id.tab_personal);
564     }
565 
566     // Used by tests only
isWorkTabVisible()567     public boolean isWorkTabVisible() {
568         return isDescendantViewVisible(R.id.tab_work);
569     }
570 
getApps()571     public AlphabeticalAppsList getApps() {
572         return mAH[AdapterHolder.MAIN].appsList;
573     }
574 
getFloatingHeaderView()575     public FloatingHeaderView getFloatingHeaderView() {
576         return mHeader;
577     }
578 
getSearchView()579     public View getSearchView() {
580         return mSearchContainer;
581     }
582 
getContentView()583     public View getContentView() {
584         return mViewPager == null ? getActiveRecyclerView() : mViewPager;
585     }
586 
getCurrentPage()587     public int getCurrentPage() {
588         return mViewPager != null ? mViewPager.getCurrentPage() : AdapterHolder.MAIN;
589     }
590 
591     /**
592      * Handles selection on focused view and returns success
593      */
launchHighlightedItem()594     public boolean launchHighlightedItem() {
595         if (mSearchAdapterProvider == null) return false;
596         return mSearchAdapterProvider.launchHighlightedItem();
597     }
598 
getSearchAdapterProvider()599     public SearchAdapterProvider getSearchAdapterProvider() {
600         return mSearchAdapterProvider;
601     }
602 
getScrollBar()603     public RecyclerViewFastScroller getScrollBar() {
604         AllAppsRecyclerView rv = getActiveRecyclerView();
605         return rv == null ? null : rv.getScrollbar();
606     }
607 
setupHeader()608     public void setupHeader() {
609         mHeader.setVisibility(View.VISIBLE);
610         mHeader.setup(mAH, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView == null);
611 
612         int padding = mHeader.getMaxTranslation();
613         for (int i = 0; i < mAH.length; i++) {
614             mAH[i].padding.top = padding;
615             mAH[i].applyPadding();
616         }
617         mHeaderTop = mHeader.getTop();
618     }
619 
setLastSearchQuery(String query)620     public void setLastSearchQuery(String query) {
621         for (int i = 0; i < mAH.length; i++) {
622             mAH[i].adapter.setLastSearchQuery(query);
623         }
624         if (mUsingTabs) {
625             mSearchModeWhileUsingTabs = true;
626             rebindAdapters(false); // hide tabs
627         }
628         mHeader.setCollapsed(true);
629     }
630 
onClearSearchResult()631     public void onClearSearchResult() {
632         if (mSearchModeWhileUsingTabs) {
633             rebindAdapters(true); // show tabs
634             mSearchModeWhileUsingTabs = false;
635         }
636     }
637 
onSearchResultsChanged()638     public void onSearchResultsChanged() {
639         for (int i = 0; i < mAH.length; i++) {
640             if (mAH[i].recyclerView != null) {
641                 mAH[i].recyclerView.onSearchResultsChanged();
642             }
643         }
644     }
645 
setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled)646     public void setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled) {
647         for (int i = 0; i < mAH.length; i++) {
648             mAH[i].applyVerticalFadingEdgeEnabled(enabled);
649         }
650     }
651 
addElevationController(RecyclerView.OnScrollListener scrollListener)652     public void addElevationController(RecyclerView.OnScrollListener scrollListener) {
653         if (!mUsingTabs) {
654             mAH[AdapterHolder.MAIN].recyclerView.addOnScrollListener(scrollListener);
655         }
656     }
657 
isHeaderVisible()658     public boolean isHeaderVisible() {
659         return mHeader != null && mHeader.getVisibility() == View.VISIBLE;
660     }
661 
662     /**
663      * Adds an update listener to {@param animator} that adds springs to the animation.
664      */
addSpringFromFlingUpdateListener(ValueAnimator animator, float velocity , float progress )665     public void addSpringFromFlingUpdateListener(ValueAnimator animator,
666             float velocity /* release velocity */,
667             float progress /* portion of the distance to travel*/) {
668         animator.addListener(new AnimatorListenerAdapter() {
669             @Override
670             public void onAnimationStart(Animator animator) {
671                 float distance = (float) ((1 - progress) * getHeight()); // px
672                 float settleVelocity = Math.min(0, distance
673                         / (AllAppsTransitionController.INTERP_COEFF * animator.getDuration())
674                         + velocity);
675                 absorbSwipeUpVelocity(Math.max(1000, Math.abs(
676                         Math.round(settleVelocity * FLING_VELOCITY_MULTIPLIER))));
677             }
678         });
679     }
680 
onPull(float deltaDistance, float displacement)681     public void onPull(float deltaDistance, float displacement) {
682         absorbPullDeltaDistance(PULL_MULTIPLIER * deltaDistance, PULL_MULTIPLIER * displacement);
683         // Current motion spec is to actually push and not pull
684         // on this surface. However, until EdgeEffect.onPush (b/190612804) is
685         // implemented at view level, we will simply pull
686     }
687 
688     @Override
getDrawingRect(Rect outRect)689     public void getDrawingRect(Rect outRect) {
690         super.getDrawingRect(outRect);
691         outRect.offset(0, (int) getTranslationY());
692     }
693 
694     @Override
setTranslationY(float translationY)695     public void setTranslationY(float translationY) {
696         super.setTranslationY(translationY);
697         invalidateHeader();
698     }
699 
setScrimView(ScrimView scrimView)700     public void setScrimView(ScrimView scrimView) {
701         mScrimView = scrimView;
702     }
703 
704     @Override
drawOnScrim(Canvas canvas)705     public void drawOnScrim(Canvas canvas) {
706         mHeaderPaint.setColor(mHeaderColor);
707         mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor)));
708         if (mHeaderPaint.getColor() != mScrimColor && mHeaderPaint.getColor() != 0) {
709             int bottom = mUsingTabs && mHeader.mHeaderCollapsed ? mHeader.getVisibleBottomBound()
710                     : mSearchContainer.getBottom();
711             canvas.drawRect(0, 0, canvas.getWidth(), bottom + getTranslationY(),
712                     mHeaderPaint);
713 
714             if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && getTranslationY() == 0) {
715                 mSearchUiManager.getEditText().setBackground(null);
716             }
717         }
718     }
719 
720     public class AdapterHolder {
721         public static final int MAIN = 0;
722         public static final int WORK = 1;
723 
724         private ItemInfoMatcher mInfoMatcher;
725         private final boolean mIsWork;
726         public final AllAppsGridAdapter adapter;
727         final LinearLayoutManager layoutManager;
728         final AlphabeticalAppsList appsList;
729         final Rect padding = new Rect();
730         AllAppsRecyclerView recyclerView;
731         boolean verticalFadingEdge;
732         private View mOverlay;
733 
734         boolean mWorkDisabled;
735 
AdapterHolder(boolean isWork)736         AdapterHolder(boolean isWork) {
737             mIsWork = isWork;
738             appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore,
739                     isWork ? mWorkAdapterProvider : null);
740 
741             BaseAdapterProvider[] adapterProviders =
742                     isWork ? new BaseAdapterProvider[]{mSearchAdapterProvider, mWorkAdapterProvider}
743                             : new BaseAdapterProvider[]{mSearchAdapterProvider};
744 
745             adapter = new AllAppsGridAdapter(mLauncher, getLayoutInflater(), appsList,
746                     adapterProviders);
747             appsList.setAdapter(adapter);
748             layoutManager = adapter.getLayoutManager();
749         }
750 
setup(@onNull View rv, @Nullable ItemInfoMatcher matcher)751         void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
752             mInfoMatcher = matcher;
753             appsList.updateItemFilter(matcher);
754             recyclerView = (AllAppsRecyclerView) rv;
755             recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
756             recyclerView.setApps(appsList);
757             recyclerView.setLayoutManager(layoutManager);
758             recyclerView.setAdapter(adapter);
759             recyclerView.setHasFixedSize(true);
760             // No animations will occur when changes occur to the items in this RecyclerView.
761             recyclerView.setItemAnimator(null);
762             recyclerView.addOnScrollListener(mScrollListener);
763             FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(recyclerView);
764             recyclerView.addItemDecoration(focusedItemDecorator);
765             adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
766             applyVerticalFadingEdgeEnabled(verticalFadingEdge);
767             applyPadding();
768             if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
769                 recyclerView.addItemDecoration(mSearchAdapterProvider.getDecorator());
770             }
771         }
772 
applyPadding()773         void applyPadding() {
774             if (recyclerView != null) {
775                 Resources res = getResources();
776                 int switchH = res.getDimensionPixelSize(R.dimen.work_profile_footer_padding) * 2
777                         + mInsets.bottom + Utilities.calculateTextHeight(
778                         res.getDimension(R.dimen.work_profile_footer_text_size));
779 
780                 int bottomOffset = mWorkModeSwitch != null && mIsWork ? switchH : 0;
781                 recyclerView.setPadding(padding.left, padding.top, padding.right,
782                         padding.bottom + bottomOffset);
783             }
784         }
785 
applyVerticalFadingEdgeEnabled(boolean enabled)786         public void applyVerticalFadingEdgeEnabled(boolean enabled) {
787             verticalFadingEdge = enabled;
788             mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs
789                     && verticalFadingEdge);
790         }
791     }
792 
793 
updateHeaderScroll(int scrolledOffset)794     protected void updateHeaderScroll(int scrolledOffset) {
795         float prog = Math.max(0, Math.min(1, (float) scrolledOffset / mHeaderThreshold));
796         int viewBG = ColorUtils.blendARGB(mScrimColor, mHeaderProtectionColor, prog);
797         int headerColor = ColorUtils.setAlphaComponent(viewBG,
798                 (int) (getSearchView().getAlpha() * 255));
799         if (headerColor != mHeaderColor) {
800             mHeaderColor = headerColor;
801             getSearchView().setBackgroundColor(viewBG);
802             getFloatingHeaderView().setHeaderColor(viewBG);
803             invalidateHeader();
804             if (scrolledOffset == 0 && mSearchUiManager.getEditText() != null) {
805                 mSearchUiManager.getEditText().show();
806             }
807         }
808     }
809 
810     /**
811      * redraws header protection
812      */
invalidateHeader()813     public void invalidateHeader() {
814         if (mScrimView != null && FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
815             mScrimView.invalidate();
816         }
817     }
818 }
819