• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.widget.picker;
17 
18 import static android.view.View.MeasureSpec.makeMeasureSpec;
19 
20 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
21 import static com.android.launcher3.config.FeatureFlags.LARGE_SCREEN_WIDGET_PICKER;
22 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
23 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
24 
25 import android.animation.Animator;
26 import android.animation.AnimatorListenerAdapter;
27 import android.animation.PropertyValuesHolder;
28 import android.content.Context;
29 import android.content.pm.LauncherApps;
30 import android.content.res.Configuration;
31 import android.content.res.Resources;
32 import android.graphics.Outline;
33 import android.graphics.Rect;
34 import android.os.Process;
35 import android.os.UserHandle;
36 import android.os.UserManager;
37 import android.util.AttributeSet;
38 import android.util.Pair;
39 import android.util.SparseArray;
40 import android.view.LayoutInflater;
41 import android.view.MotionEvent;
42 import android.view.View;
43 import android.view.ViewGroup;
44 import android.view.ViewOutlineProvider;
45 import android.view.WindowInsets;
46 import android.view.animation.AnimationUtils;
47 import android.view.animation.Interpolator;
48 import android.widget.Button;
49 import android.widget.FrameLayout;
50 import android.widget.LinearLayout;
51 import android.widget.TextView;
52 
53 import androidx.annotation.FloatRange;
54 import androidx.annotation.NonNull;
55 import androidx.annotation.Nullable;
56 import androidx.annotation.Px;
57 import androidx.annotation.VisibleForTesting;
58 import androidx.recyclerview.widget.DefaultItemAnimator;
59 import androidx.recyclerview.widget.RecyclerView;
60 
61 import com.android.launcher3.DeviceProfile;
62 import com.android.launcher3.Launcher;
63 import com.android.launcher3.R;
64 import com.android.launcher3.Utilities;
65 import com.android.launcher3.anim.PendingAnimation;
66 import com.android.launcher3.compat.AccessibilityManagerCompat;
67 import com.android.launcher3.model.UserManagerState;
68 import com.android.launcher3.model.WidgetItem;
69 import com.android.launcher3.model.data.PackageItemInfo;
70 import com.android.launcher3.pm.UserCache;
71 import com.android.launcher3.recyclerview.ViewHolderBinder;
72 import com.android.launcher3.util.PackageUserKey;
73 import com.android.launcher3.views.ArrowTipView;
74 import com.android.launcher3.views.RecyclerViewFastScroller;
75 import com.android.launcher3.views.SpringRelativeLayout;
76 import com.android.launcher3.views.StickyHeaderLayout;
77 import com.android.launcher3.views.WidgetsEduView;
78 import com.android.launcher3.widget.BaseWidgetSheet;
79 import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
80 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
81 import com.android.launcher3.widget.model.WidgetsListContentEntry;
82 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
83 import com.android.launcher3.widget.picker.search.SearchModeListener;
84 import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
85 import com.android.launcher3.widget.util.WidgetsTableUtils;
86 import com.android.launcher3.workprofile.PersonalWorkPagedView;
87 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
88 
89 import java.util.ArrayList;
90 import java.util.Collections;
91 import java.util.List;
92 import java.util.function.Predicate;
93 import java.util.stream.IntStream;
94 
95 /**
96  * Popup for showing the full list of available widgets
97  */
98 public class WidgetsFullSheet extends BaseWidgetSheet
99         implements ProviderChangedListener, OnActivePageChangedListener,
100         WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener {
101 
102     private static final long FADE_IN_DURATION = 150;
103     private static final long EDUCATION_TIP_DELAY_MS = 200;
104     private static final long EDUCATION_DIALOG_DELAY_MS = 500;
105     private static final float VERTICAL_START_POSITION = 0.3f;
106     private static final int PERSONAL_TAB = 0;
107     private static final int WORK_TAB = 1;
108     private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry";
109     // The widget recommendation table can easily take over the entire screen on devices with small
110     // resolution or landscape on phone. This ratio defines the max percentage of content area that
111     // the table can display.
112     private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
113 
114     private static final String KEY_WIDGETS_EDUCATION_DIALOG_SEEN =
115             "launcher.widgets_education_dialog_seen";
116 
117     private final UserManagerState mUserManagerState = new UserManagerState();
118 
119     private final boolean mHasWorkProfile;
120     private final SparseArray<AdapterHolder> mAdapters = new SparseArray();
121     private final UserHandle mCurrentUser = Process.myUserHandle();
122     private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter =
123             entry -> mCurrentUser.equals(entry.mPkgItem.user);
124     private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
125             entry -> !mCurrentUser.equals(entry.mPkgItem.user)
126                     && !mUserManagerState.isUserQuiet(entry.mPkgItem.user);
127     @Nullable private ArrowTipView mLatestEducationalTip;
128     private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
129             new OnLayoutChangeListener() {
130                 @Override
131                 public void onLayoutChange(View v, int left, int top, int right, int bottom,
132                         int oldLeft, int oldTop, int oldRight, int oldBottom) {
133                     if (hasSeenEducationTip()) {
134                         removeOnLayoutChangeListener(this);
135                         return;
136                     }
137 
138                     // Widgets are loaded asynchronously, We are adding a delay because we only want
139                     // to show the tip when the widget preview has finished loading and rendering in
140                     // this view.
141                     removeCallbacks(mShowEducationTipTask);
142                     postDelayed(mShowEducationTipTask, EDUCATION_TIP_DELAY_MS);
143                 }
144             };
145 
146     private final Runnable mShowEducationTipTask = () -> {
147         if (hasSeenEducationTip()) {
148             removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
149             return;
150         }
151         mLatestEducationalTip = showEducationTipOnViewIfPossible(getViewToShowEducationTip());
152         if (mLatestEducationalTip != null) {
153             removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
154         }
155     };
156 
157     private final OnAttachStateChangeListener mBindScrollbarInSearchMode =
158             new OnAttachStateChangeListener() {
159                 @Override
160                 public void onViewAttachedToWindow(View view) {
161                     WidgetsRecyclerView searchRecyclerView =
162                             mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView;
163                     if (mIsInSearchMode && searchRecyclerView != null) {
164                         searchRecyclerView.bindFastScrollbar(mFastScroller);
165                     }
166                 }
167 
168                 @Override
169                 public void onViewDetachedFromWindow(View view) {
170                 }
171             };
172 
173     private final ViewOutlineProvider mViewOutlineProvider = new ViewOutlineProvider() {
174         @Override
175         public void getOutline(View view, Outline outline) {
176             outline.setRect(
177                     0,
178                     0,
179                     view.getMeasuredWidth(),
180                     view.getMeasuredHeight() + getBottomOffsetPx()
181             );
182         }
183     };
184 
185     @Px private final int mTabsHeight;
186 
187     @Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView;
188     @Nullable private PersonalWorkPagedView mViewPager;
189     private boolean mIsInSearchMode;
190     private boolean mIsNoWidgetsViewNeeded;
191     @Px private int mMaxSpanPerRow;
192     private TextView mNoWidgetsView;
193 
194     private StickyHeaderLayout mSearchScrollView;
195     private WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
196     private LinearLayout mSuggestedWidgetsContainer;
197     private WidgetsListHeader mSuggestedWidgetsHeader;
198     private View mTabBar;
199     private View mSearchBarContainer;
200     private WidgetsSearchBar mSearchBar;
201     private TextView mHeaderTitle;
202     private FrameLayout mRightPane;
203     private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
204     private DeviceProfile mDeviceProfile;
205     private final boolean mIsTwoPane;
206 
207     private int mOrientation;
208     private @Nullable WidgetsRecyclerView mCurrentTouchEventRecyclerView;
209 
210     private RecyclerViewFastScroller mFastScroller;
211 
WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr)212     public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
213         super(context, attrs, defStyleAttr);
214         mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile();
215         mIsTwoPane = mDeviceProfile.isTablet
216                 && mDeviceProfile.isLandscape
217                 && LARGE_SCREEN_WIDGET_PICKER.get();
218         mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
219         mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
220         mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
221         mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
222 
223         Resources resources = getResources();
224         mTabsHeight = mHasWorkProfile
225                 ? resources.getDimensionPixelSize(R.dimen.all_apps_header_pill_height)
226                 : 0;
227 
228         mUserManagerState.init(UserCache.INSTANCE.get(context),
229                 context.getSystemService(UserManager.class));
230         setContentBackground(getContext().getDrawable(R.drawable.bg_widgets_full_sheet));
231     }
232 
WidgetsFullSheet(Context context, AttributeSet attrs)233     public WidgetsFullSheet(Context context, AttributeSet attrs) {
234         this(context, attrs, 0);
235     }
236 
237     @Override
onFinishInflate()238     protected void onFinishInflate() {
239         super.onFinishInflate();
240         mContent = findViewById(R.id.container);
241 
242         mContent.setOutlineProvider(mViewOutlineProvider);
243         mContent.setClipToOutline(true);
244 
245         LayoutInflater layoutInflater = LayoutInflater.from(getContext());
246         int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view
247                 : R.layout.widgets_full_sheet_recyclerview;
248         if (mIsTwoPane) {
249             contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view_large_screen
250                     : R.layout.widgets_full_sheet_recyclerview_large_screen;
251         }
252         layoutInflater.inflate(contentLayoutRes, mContent, true);
253 
254         mFastScroller = findViewById(R.id.fast_scroller);
255         if (mIsTwoPane) {
256             mFastScroller.setVisibility(GONE);
257         }
258         mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup));
259 
260         mAdapters.get(AdapterHolder.PRIMARY).setup(findViewById(R.id.primary_widgets_list_view));
261         mAdapters.get(AdapterHolder.SEARCH).setup(findViewById(R.id.search_widgets_list_view));
262         if (mHasWorkProfile) {
263             mViewPager = findViewById(R.id.widgets_view_pager);
264             mViewPager.setOutlineProvider(mViewOutlineProvider);
265             mViewPager.setClipToOutline(true);
266             mViewPager.setClipChildren(false);
267             mViewPager.initParentViews(this);
268             mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
269             mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.PRIMARY);
270             findViewById(R.id.tab_personal)
271                     .setOnClickListener((View view) -> mViewPager.snapToPage(0));
272             findViewById(R.id.tab_work)
273                     .setOnClickListener((View view) -> mViewPager.snapToPage(1));
274             mAdapters.get(AdapterHolder.WORK).setup(findViewById(R.id.work_widgets_list_view));
275             setDeviceManagementResources();
276         } else {
277             mViewPager = null;
278         }
279 
280         mNoWidgetsView = findViewById(R.id.no_widgets_text);
281 
282         mSearchScrollView = findViewById(R.id.search_and_recommendations_container);
283         mSearchScrollView.setCurrentRecyclerView(findViewById(R.id.primary_widgets_list_view));
284 
285         mRecommendedWidgetsTable = mIsTwoPane
286                 ? mContent.findViewById(R.id.recommended_widget_table)
287                 : mSearchScrollView.findViewById(R.id.recommended_widget_table);
288 
289         mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
290         mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
291 
292         // Add suggested widgets.
293         if (mIsTwoPane) {
294             mSuggestedWidgetsContainer = mSearchScrollView.findViewById(R.id.suggestions_header);
295 
296             // Inflate the suggestions header.
297             mSuggestedWidgetsHeader = (WidgetsListHeader) layoutInflater.inflate(
298                     R.layout.widgets_list_row_header_two_pane,
299                     mSuggestedWidgetsContainer,
300                     false);
301             mSuggestedWidgetsHeader.setExpanded(true);
302 
303             PackageItemInfo packageItemInfo =  new PackageItemInfo(
304                     /* packageName= */ SUGGESTIONS_PACKAGE_NAME,
305                     Process.myUserHandle()) {
306                 @Override
307                 public boolean usingLowResIcon() {
308                     return false;
309                 }
310             };
311             packageItemInfo.title = getContext().getString(R.string.suggested_widgets_header_title);
312             WidgetsListHeaderEntry widgetsListHeaderEntry = WidgetsListHeaderEntry.create(
313                     packageItemInfo,
314                     getContext().getString(R.string.suggested_widgets_header_title),
315                     mActivityContext.getPopupDataProvider().getRecommendedWidgets())
316                     .withWidgetListShown();
317 
318             mSuggestedWidgetsHeader.applyFromItemInfoWithIcon(widgetsListHeaderEntry);
319             mSuggestedWidgetsHeader.setIcon(
320                     getContext().getDrawable(R.drawable.widget_suggestions_icon));
321             mSuggestedWidgetsHeader.setOnClickListener(view -> {
322                 mSuggestedWidgetsHeader.setExpanded(true);
323                 resetExpandedHeaders();
324                 mRightPane.removeAllViews();
325                 mRightPane.addView(mRecommendedWidgetsTable);
326             });
327             mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader);
328         }
329 
330         mTabBar = mSearchScrollView.findViewById(R.id.tabs);
331         mSearchBarContainer = mSearchScrollView.findViewById(R.id.search_bar_container);
332         mSearchBar = mSearchScrollView.findViewById(R.id.widgets_search_bar);
333         mHeaderTitle = mIsTwoPane
334                 ? mContent.findViewById(R.id.title)
335                 : mSearchScrollView.findViewById(R.id.title);
336         mRightPane = mIsTwoPane ? mContent.findViewById(R.id.right_pane) : null;
337         mWidgetsListTableViewHolderBinder =
338                 new WidgetsListTableViewHolderBinder(mActivityContext, layoutInflater, this, this);
339         onRecommendedWidgetsBound();
340         onWidgetsBound();
341 
342         mSearchBar.initialize(
343                 mActivityContext.getPopupDataProvider(), /* searchModeListener= */ this);
344 
345         setUpEducationViewsIfNeeded();
346     }
347 
setDeviceManagementResources()348     private void setDeviceManagementResources() {
349         if (mActivityContext.getStringCache() != null) {
350             Button personalTab = findViewById(R.id.tab_personal);
351             personalTab.setText(mActivityContext.getStringCache().widgetsPersonalTab);
352 
353             Button workTab = findViewById(R.id.tab_work);
354             workTab.setText(mActivityContext.getStringCache().widgetsWorkTab);
355         }
356     }
357 
358     @Override
onActivePageChanged(int currentActivePage)359     public void onActivePageChanged(int currentActivePage) {
360 
361         // if the current active page changes to personal or work we set suggestions
362         // to be the selected widget
363         if (mIsTwoPane && (currentActivePage == PERSONAL_TAB || currentActivePage == WORK_TAB)) {
364             mSuggestedWidgetsHeader.callOnClick();
365         }
366 
367         AdapterHolder currentAdapterHolder = mAdapters.get(currentActivePage);
368         WidgetsRecyclerView currentRecyclerView =
369                 mAdapters.get(currentActivePage).mWidgetsRecyclerView;
370 
371         updateRecyclerViewVisibility(currentAdapterHolder);
372         attachScrollbarToRecyclerView(currentRecyclerView);
373     }
374 
375     @Override
onBackProgressed(@loatRangefrom = 0.0, to = 1.0) float progress)376     public void onBackProgressed(@FloatRange(from = 0.0, to = 1.0) float progress) {
377         super.onBackProgressed(progress);
378         mFastScroller.setVisibility(progress > 0 ? View.INVISIBLE : View.VISIBLE);
379     }
380 
attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView)381     private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) {
382         recyclerView.bindFastScrollbar(mFastScroller);
383         if (mCurrentWidgetsRecyclerView != recyclerView) {
384             // Only reset the scroll position & expanded apps if the currently shown recycler view
385             // has been updated.
386             reset();
387             resetExpandedHeaders();
388             mCurrentWidgetsRecyclerView = recyclerView;
389             mSearchScrollView.setCurrentRecyclerView(recyclerView);
390         }
391     }
392 
updateRecyclerViewVisibility(AdapterHolder adapterHolder)393     private void updateRecyclerViewVisibility(AdapterHolder adapterHolder) {
394         // The first item is always an empty space entry. Look for any more items.
395         boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries();
396 
397         if (mIsTwoPane) {
398             mRightPane.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
399         }
400 
401         adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
402 
403         if (adapterHolder.mAdapterType == AdapterHolder.SEARCH) {
404             mNoWidgetsView.setText(R.string.no_search_results);
405         } else if (adapterHolder.mAdapterType == AdapterHolder.WORK
406                 && mUserManagerState.isAnyProfileQuietModeEnabled()
407                 && mActivityContext.getStringCache() != null) {
408             mNoWidgetsView.setText(mActivityContext.getStringCache().workProfilePausedTitle);
409         } else {
410             mNoWidgetsView.setText(R.string.no_widgets_available);
411         }
412         mNoWidgetsView.setVisibility(isWidgetAvailable ? GONE : VISIBLE);
413     }
414 
reset()415     private void reset() {
416         mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
417         if (mHasWorkProfile) {
418             mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
419         }
420         mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
421         mSearchScrollView.reset(/* animate= */ true);
422     }
423 
424     @VisibleForTesting
getRecyclerView()425     public WidgetsRecyclerView getRecyclerView() {
426         if (mIsInSearchMode) {
427             return mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView;
428         }
429         if (!mHasWorkProfile || mViewPager.getCurrentPage() == AdapterHolder.PRIMARY) {
430             return mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
431         }
432         return mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView;
433     }
434 
435     @Override
getAccessibilityTarget()436     protected Pair<View, String> getAccessibilityTarget() {
437         return Pair.create(getRecyclerView(), getContext().getString(
438                 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
439     }
440 
441     @Override
onAttachedToWindow()442     protected void onAttachedToWindow() {
443         super.onAttachedToWindow();
444         mActivityContext.getAppWidgetHolder().addProviderChangeListener(this);
445         notifyWidgetProvidersChanged();
446         onRecommendedWidgetsBound();
447     }
448 
449     @Override
onDetachedFromWindow()450     protected void onDetachedFromWindow() {
451         super.onDetachedFromWindow();
452         mActivityContext.getAppWidgetHolder().removeProviderChangeListener(this);
453         mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView
454                 .removeOnAttachStateChangeListener(mBindScrollbarInSearchMode);
455         if (mHasWorkProfile) {
456             mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView
457                     .removeOnAttachStateChangeListener(mBindScrollbarInSearchMode);
458         }
459     }
460 
461     @Override
setInsets(Rect insets)462     public void setInsets(Rect insets) {
463         super.setInsets(insets);
464         int bottomPadding = Math.max(insets.bottom, mNavBarScrimHeight);
465         setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, bottomPadding);
466         setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, bottomPadding);
467         if (mHasWorkProfile) {
468             setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, bottomPadding);
469         }
470         ((MarginLayoutParams) mNoWidgetsView.getLayoutParams()).bottomMargin = bottomPadding;
471 
472         if (bottomPadding > 0) {
473             setupNavBarColor();
474         } else {
475             clearNavBarColor();
476         }
477 
478         requestLayout();
479     }
480 
setBottomPadding(RecyclerView recyclerView, int bottomPadding)481     private void setBottomPadding(RecyclerView recyclerView, int bottomPadding) {
482         recyclerView.setPadding(
483                 recyclerView.getPaddingLeft(),
484                 recyclerView.getPaddingTop(),
485                 recyclerView.getPaddingRight(),
486                 bottomPadding);
487     }
488 
489     @Override
onContentHorizontalMarginChanged(int contentHorizontalMarginInPx)490     protected void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx) {
491         setContentViewChildHorizontalMargin(mSearchScrollView, contentHorizontalMarginInPx);
492         if (mViewPager == null) {
493             setContentViewChildHorizontalPadding(
494                     mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView,
495                     contentHorizontalMarginInPx);
496         } else {
497             setContentViewChildHorizontalPadding(
498                     mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView,
499                     contentHorizontalMarginInPx);
500             setContentViewChildHorizontalPadding(
501                     mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView,
502                     contentHorizontalMarginInPx);
503         }
504         setContentViewChildHorizontalPadding(
505                 mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView,
506                 contentHorizontalMarginInPx);
507     }
508 
setContentViewChildHorizontalMargin(View view, int horizontalMarginInPx)509     private static void setContentViewChildHorizontalMargin(View view, int horizontalMarginInPx) {
510         ViewGroup.MarginLayoutParams layoutParams =
511                 (ViewGroup.MarginLayoutParams) view.getLayoutParams();
512         layoutParams.setMarginStart(horizontalMarginInPx);
513         layoutParams.setMarginEnd(horizontalMarginInPx);
514     }
515 
setContentViewChildHorizontalPadding(View view, int horizontalPaddingInPx)516     private static void setContentViewChildHorizontalPadding(View view, int horizontalPaddingInPx) {
517         view.setPadding(horizontalPaddingInPx, view.getPaddingTop(), horizontalPaddingInPx,
518                 view.getPaddingBottom());
519     }
520 
521     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)522     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
523         doMeasure(widthMeasureSpec, heightMeasureSpec);
524 
525         if (updateMaxSpansPerRow()) {
526             doMeasure(widthMeasureSpec, heightMeasureSpec);
527         }
528     }
529 
530     /** Returns {@code true} if the max spans have been updated. */
updateMaxSpansPerRow()531     private boolean updateMaxSpansPerRow() {
532         if (getMeasuredWidth() == 0) return false;
533 
534         View content = mHasWorkProfile
535                 ? mViewPager
536                 : mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
537         if (mIsTwoPane && mRightPane != null) {
538             content = mRightPane;
539         }
540 
541         @Px int maxHorizontalSpan = content.getMeasuredWidth() - (2 * mContentHorizontalMargin);
542         if (mMaxSpanPerRow != maxHorizontalSpan) {
543             mMaxSpanPerRow = maxHorizontalSpan;
544             mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(
545                     maxHorizontalSpan);
546             mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(
547                     maxHorizontalSpan);
548             if (mHasWorkProfile) {
549                 mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(
550                         maxHorizontalSpan);
551             }
552             onRecommendedWidgetsBound();
553             return true;
554         }
555         return false;
556     }
557 
558     @Override
onLayout(boolean changed, int l, int t, int r, int b)559     protected void onLayout(boolean changed, int l, int t, int r, int b) {
560         int width = r - l;
561         int height = b - t;
562 
563         // Content is laid out as center bottom aligned
564         int contentWidth = mContent.getMeasuredWidth();
565         int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
566         mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
567                 contentLeft + contentWidth, height);
568 
569         setTranslationShift(mTranslationShift);
570     }
571 
572     @Override
notifyWidgetProvidersChanged()573     public void notifyWidgetProvidersChanged() {
574         mActivityContext.refreshAndBindWidgetsForPackageUser(null);
575     }
576 
577     @Override
onWidgetsBound()578     public void onWidgetsBound() {
579         if (mIsInSearchMode) {
580             return;
581         }
582         List<WidgetsListBaseEntry> allWidgets =
583                 mActivityContext.getPopupDataProvider().getAllWidgets();
584 
585         AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY);
586         primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
587 
588         if (mHasWorkProfile) {
589             mViewPager.setVisibility(VISIBLE);
590             mTabBar.setVisibility(VISIBLE);
591             AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
592             workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
593             onActivePageChanged(mViewPager.getCurrentPage());
594         } else {
595             onActivePageChanged(0);
596         }
597         // Update recommended widgets section so that it occupies appropriate space on screen to
598         // leave enough space for presence/absence of mNoWidgetsView.
599         boolean isNoWidgetsViewNeeded =
600                 !mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.hasVisibleEntries()
601                         || (mHasWorkProfile && mAdapters.get(AdapterHolder.WORK)
602                         .mWidgetsListAdapter.hasVisibleEntries());
603         if (mIsNoWidgetsViewNeeded != isNoWidgetsViewNeeded) {
604             mIsNoWidgetsViewNeeded = isNoWidgetsViewNeeded;
605             onRecommendedWidgetsBound();
606         }
607     }
608 
609     @Override
enterSearchMode()610     public void enterSearchMode() {
611         if (mIsInSearchMode) return;
612         setViewVisibilityBasedOnSearch(/*isInSearchMode= */ true);
613         attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView);
614         mActivityContext.getStatsLogManager().logger().log(LAUNCHER_WIDGETSTRAY_SEARCHED);
615     }
616 
617     @Override
exitSearchMode()618     public void exitSearchMode() {
619         if (!mIsInSearchMode) return;
620         onSearchResults(new ArrayList<>());
621         setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false);
622         if (mHasWorkProfile) {
623             mViewPager.snapToPage(AdapterHolder.PRIMARY);
624         }
625         attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
626     }
627 
628     @Override
onSearchResults(List<WidgetsListBaseEntry> entries)629     public void onSearchResults(List<WidgetsListBaseEntry> entries) {
630         mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setWidgetsOnSearch(entries);
631         updateRecyclerViewVisibility(mAdapters.get(AdapterHolder.SEARCH));
632         if (mIsTwoPane) {
633             mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.selectFirstHeaderEntry();
634         }
635         mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
636     }
637 
setViewVisibilityBasedOnSearch(boolean isInSearchMode)638     private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
639         mIsInSearchMode = isInSearchMode;
640         if (isInSearchMode) {
641             mRecommendedWidgetsTable.setVisibility(GONE);
642             if (mIsTwoPane) {
643                 mSuggestedWidgetsContainer.setVisibility(GONE);
644             }
645             if (mHasWorkProfile) {
646                 mViewPager.setVisibility(GONE);
647                 mTabBar.setVisibility(GONE);
648             } else {
649                 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.setVisibility(GONE);
650             }
651             updateRecyclerViewVisibility(mAdapters.get(AdapterHolder.SEARCH));
652             // Hide no search results view to prevent it from flashing on enter search.
653             mNoWidgetsView.setVisibility(GONE);
654         } else {
655             mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE);
656             if (mIsTwoPane) {
657                 mSuggestedWidgetsContainer.setVisibility(VISIBLE);
658                 mSuggestedWidgetsHeader.callOnClick();
659             }
660             // Visibility of recommended widgets, recycler views and headers are handled in methods
661             // below.
662             onRecommendedWidgetsBound();
663             onWidgetsBound();
664         }
665     }
666 
resetExpandedHeaders()667     private void resetExpandedHeaders() {
668         mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.resetExpandedHeader();
669         mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader();
670     }
671 
672     @Override
onRecommendedWidgetsBound()673     public void onRecommendedWidgetsBound() {
674         if (mIsInSearchMode) {
675             return;
676         }
677         List<WidgetItem> recommendedWidgets =
678                 mActivityContext.getPopupDataProvider().getRecommendedWidgets();
679         if (recommendedWidgets.size() > 0) {
680             float noWidgetsViewHeight = 0;
681             if (mIsNoWidgetsViewNeeded) {
682                 // Make sure recommended section leaves enough space for noWidgetsView.
683                 Rect noWidgetsViewTextBounds = new Rect();
684                 mNoWidgetsView.getPaint()
685                         .getTextBounds(mNoWidgetsView.getText().toString(), /* start= */ 0,
686                                 mNoWidgetsView.getText().length(), noWidgetsViewTextBounds);
687                 noWidgetsViewHeight = noWidgetsViewTextBounds.height();
688             }
689             doMeasure(
690                     makeMeasureSpec(mActivityContext.getDeviceProfile().availableWidthPx,
691                             MeasureSpec.EXACTLY),
692                     makeMeasureSpec(mActivityContext.getDeviceProfile().availableHeightPx,
693                             MeasureSpec.EXACTLY));
694             float maxTableHeight = mIsTwoPane ? Float.MAX_VALUE : (mContent.getMeasuredHeight()
695                     - mTabsHeight - getHeaderViewHeight()
696                     - noWidgetsViewHeight) * RECOMMENDATION_TABLE_HEIGHT_RATIO;
697 
698             List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
699                     WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering(
700                             recommendedWidgets,
701                             mActivityContext,
702                             mActivityContext.getDeviceProfile(),
703                             mMaxSpanPerRow,
704                             mWidgetCellHorizontalPadding);
705             mRecommendedWidgetsTable.setRecommendedWidgets(
706                     recommendedWidgetsInTable, maxTableHeight);
707         } else {
708             mRecommendedWidgetsTable.setVisibility(GONE);
709         }
710     }
711 
open(boolean animate)712     private void open(boolean animate) {
713         if (animate) {
714             if (getPopupContainer().getInsets().bottom > 0) {
715                 mContent.setAlpha(0);
716                 setTranslationShift(VERTICAL_START_POSITION);
717             }
718             mOpenCloseAnimator.setValues(
719                     PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
720             mOpenCloseAnimator
721                     .setDuration(mActivityContext.getDeviceProfile().bottomSheetOpenDuration)
722                     .setInterpolator(AnimationUtils.loadInterpolator(
723                             getContext(), android.R.interpolator.linear_out_slow_in));
724             mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
725                 @Override
726                 public void onAnimationEnd(Animator animation) {
727                     mOpenCloseAnimator.removeListener(this);
728                 }
729             });
730             post(() -> {
731                 mOpenCloseAnimator.start();
732                 mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
733             });
734         } else {
735             setTranslationShift(TRANSLATION_SHIFT_OPENED);
736             post(this::announceAccessibilityChanges);
737         }
738     }
739 
740     @Override
handleClose(boolean animate)741     protected void handleClose(boolean animate) {
742         handleClose(animate, mActivityContext.getDeviceProfile().bottomSheetCloseDuration);
743     }
744 
745     @Override
isOfType(int type)746     protected boolean isOfType(int type) {
747         return (type & TYPE_WIDGETS_FULL_SHEET) != 0;
748     }
749 
750     @Override
onControllerInterceptTouchEvent(MotionEvent ev)751     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
752         // Disable swipe down when recycler view is scrolling or scroll view is scrolling
753         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
754             mNoIntercept = false;
755             WidgetsRecyclerView recyclerView = getRecyclerView();
756             RecyclerViewFastScroller scroller = recyclerView.getScrollbar();
757             if (scroller.getThumbOffsetY() >= 0
758                     && getPopupContainer().isEventOverView(scroller, ev)) {
759                 mNoIntercept = true;
760             } else if (getPopupContainer().isEventOverView(recyclerView, ev)) {
761                 mNoIntercept = !recyclerView.shouldContainerScroll(ev, getPopupContainer());
762             } else if (mIsTwoPane && getPopupContainer().isEventOverView(mRightPane, ev)) {
763                 mNoIntercept = mRightPane.getScrollY() > 0;
764             }
765 
766             if (mSearchBar.isSearchBarFocused()
767                     && !getPopupContainer().isEventOverView(mSearchBarContainer, ev)) {
768                 mSearchBar.clearSearchBarFocus();
769             }
770         }
771         return super.onControllerInterceptTouchEvent(ev);
772     }
773 
774     /** Shows the {@link WidgetsFullSheet} on the launcher. */
show(Launcher launcher, boolean animate)775     public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
776         WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
777                 .inflate(LARGE_SCREEN_WIDGET_PICKER.get()
778                         && launcher.getDeviceProfile().isTablet
779                         && launcher.getDeviceProfile().isLandscape
780                         ? R.layout.widgets_full_sheet_large_screen
781                         : R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
782         sheet.attachToContainer();
783         sheet.mIsOpen = true;
784         sheet.open(animate);
785         return sheet;
786     }
787 
788     @Override
onInterceptTouchEvent(MotionEvent ev)789     public boolean onInterceptTouchEvent(MotionEvent ev) {
790         return isTouchOnScrollbar(ev) || super.onInterceptTouchEvent(ev);
791     }
792 
793     @Override
onTouchEvent(MotionEvent ev)794     public boolean onTouchEvent(MotionEvent ev) {
795         return maybeHandleTouchEvent(ev) || super.onTouchEvent(ev);
796     }
797 
maybeHandleTouchEvent(MotionEvent ev)798     private boolean maybeHandleTouchEvent(MotionEvent ev) {
799         boolean isEventHandled = false;
800 
801         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
802             mCurrentTouchEventRecyclerView = isTouchOnScrollbar(ev) ? getRecyclerView() : null;
803         }
804 
805         if (mCurrentTouchEventRecyclerView != null) {
806             final float offsetX = mContent.getX();
807             final float offsetY = mContent.getY();
808             ev.offsetLocation(-offsetX, -offsetY);
809             isEventHandled = mCurrentTouchEventRecyclerView.dispatchTouchEvent(ev);
810             ev.offsetLocation(offsetX, offsetY);
811         }
812 
813         if (ev.getAction() == MotionEvent.ACTION_UP
814                 || ev.getAction() == MotionEvent.ACTION_CANCEL) {
815             mCurrentTouchEventRecyclerView = null;
816         }
817 
818         return isEventHandled;
819     }
820 
isTouchOnScrollbar(MotionEvent ev)821     private boolean isTouchOnScrollbar(MotionEvent ev) {
822         final float offsetX = mContent.getX();
823         final float offsetY = mContent.getY();
824         WidgetsRecyclerView rv = getRecyclerView();
825 
826         ev.offsetLocation(-offsetX, -offsetY);
827         boolean isOnScrollBar = rv != null && rv.getScrollbar() != null && rv.isHitOnScrollBar(ev);
828         ev.offsetLocation(offsetX, offsetY);
829 
830         return isOnScrollBar;
831     }
832 
833     /** Gets the {@link WidgetsRecyclerView} which shows all widgets in {@link WidgetsFullSheet}. */
834     @VisibleForTesting
getWidgetsView(Launcher launcher)835     public static WidgetsRecyclerView getWidgetsView(Launcher launcher) {
836         return launcher.findViewById(R.id.primary_widgets_list_view);
837     }
838 
839     @Override
addHintCloseAnim( float distanceToMove, Interpolator interpolator, PendingAnimation target)840     public void addHintCloseAnim(
841             float distanceToMove, Interpolator interpolator, PendingAnimation target) {
842         target.setFloat(getRecyclerView(), VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
843         target.setViewAlpha(getRecyclerView(), 0.5f, interpolator);
844     }
845 
846     @Override
onCloseComplete()847     protected void onCloseComplete() {
848         super.onCloseComplete();
849         removeCallbacks(mShowEducationTipTask);
850         if (mLatestEducationalTip != null) {
851             mLatestEducationalTip.close(false);
852         }
853         AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
854     }
855 
856     @Override
getHeaderViewHeight()857     public int getHeaderViewHeight() {
858         return measureHeightWithVerticalMargins(mHeaderTitle)
859                 + measureHeightWithVerticalMargins(mSearchBarContainer);
860     }
861 
862     /** private the height, in pixel, + the vertical margins of a given view. */
measureHeightWithVerticalMargins(View view)863     private static int measureHeightWithVerticalMargins(View view) {
864         if (view.getVisibility() != VISIBLE) {
865             return 0;
866         }
867         MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
868         return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
869                 + marginLayoutParams.topMargin;
870     }
871 
872     @Override
onConfigurationChanged(Configuration newConfig)873     protected void onConfigurationChanged(Configuration newConfig) {
874         super.onConfigurationChanged(newConfig);
875         if (mIsInSearchMode) {
876             mSearchBar.reset();
877         }
878 
879         // Checks the orientation of the screen
880         if (LARGE_SCREEN_WIDGET_PICKER.get()
881                 && mOrientation != newConfig.orientation
882                 && mDeviceProfile.isTablet) {
883             mOrientation = newConfig.orientation;
884             handleClose(false);
885             show(Launcher.getLauncher(getContext()), false);
886         }
887     }
888 
889     @Override
onBackInvoked()890     public void onBackInvoked() {
891         if (mIsInSearchMode) {
892             mSearchBar.reset();
893             animateSlideInViewToNoScale();
894         } else {
895             super.onBackInvoked();
896         }
897     }
898 
899     @Override
onDragStart(boolean start, float startDisplacement)900     public void onDragStart(boolean start, float startDisplacement) {
901         super.onDragStart(start, startDisplacement);
902         getWindowInsetsController().hide(WindowInsets.Type.ime());
903     }
904 
getViewToShowEducationTip()905     @Nullable private View getViewToShowEducationTip() {
906         if (mRecommendedWidgetsTable.getVisibility() == VISIBLE
907                 && mRecommendedWidgetsTable.getChildCount() > 0) {
908             return ((ViewGroup) mRecommendedWidgetsTable.getChildAt(0)).getChildAt(0);
909         }
910 
911         AdapterHolder adapterHolder = mAdapters.get(mIsInSearchMode
912                 ? AdapterHolder.SEARCH
913                 : mViewPager == null
914                         ? AdapterHolder.PRIMARY
915                         : mViewPager.getCurrentPage());
916         WidgetsRowViewHolder viewHolderForTip =
917                 (WidgetsRowViewHolder) IntStream.range(
918                                 0, adapterHolder.mWidgetsListAdapter.getItemCount())
919                         .mapToObj(adapterHolder.mWidgetsRecyclerView::
920                                 findViewHolderForAdapterPosition)
921                         .filter(viewHolder -> viewHolder instanceof WidgetsRowViewHolder)
922                         .findFirst()
923                         .orElse(null);
924         if (viewHolderForTip != null) {
925             return ((ViewGroup) viewHolderForTip.tableContainer.getChildAt(0)).getChildAt(0);
926         }
927 
928         return null;
929     }
930 
931     /** Shows education dialog for widgets. */
showEducationDialog()932     private WidgetsEduView showEducationDialog() {
933         mActivityContext.getSharedPrefs().edit()
934                 .putBoolean(KEY_WIDGETS_EDUCATION_DIALOG_SEEN, true).apply();
935         return WidgetsEduView.showEducationDialog(mActivityContext);
936     }
937 
938     /** Returns {@code true} if education dialog has previously been shown. */
hasSeenEducationDialog()939     protected boolean hasSeenEducationDialog() {
940         return mActivityContext.getSharedPrefs()
941                 .getBoolean(KEY_WIDGETS_EDUCATION_DIALOG_SEEN, false)
942                 || Utilities.isRunningInTestHarness();
943     }
944 
setUpEducationViewsIfNeeded()945     private void setUpEducationViewsIfNeeded() {
946         if (!hasSeenEducationDialog()) {
947             postDelayed(() -> {
948                 WidgetsEduView eduDialog = showEducationDialog();
949                 eduDialog.addOnCloseListener(() -> {
950                     if (!hasSeenEducationTip()) {
951                         addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
952                         // Call #requestLayout() to trigger layout change listener in order to show
953                         // arrow tip immediately if there is a widget to show it on.
954                         requestLayout();
955                     }
956                 });
957             }, EDUCATION_DIALOG_DELAY_MS);
958         } else if (!hasSeenEducationTip()) {
959             addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
960         }
961     }
962 
963     /** A holder class for holding adapters & their corresponding recycler view. */
964     private final class AdapterHolder {
965         static final int PRIMARY = 0;
966         static final int WORK = 1;
967         static final int SEARCH = 2;
968 
969         private final int mAdapterType;
970         private final WidgetsListAdapter mWidgetsListAdapter;
971         private final DefaultItemAnimator mWidgetsListItemAnimator;
972 
973         private WidgetsRecyclerView mWidgetsRecyclerView;
974 
AdapterHolder(int adapterType)975         AdapterHolder(int adapterType) {
976             mAdapterType = adapterType;
977             Context context = getContext();
978             HeaderChangeListener headerChangeListener = new HeaderChangeListener() {
979                 @Override
980                 public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) {
981                     WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider()
982                             .getSelectedAppWidgets(selectedHeader);
983 
984                     if (contentEntry == null || mRightPane == null) {
985                         return;
986                     }
987 
988                     if (mSuggestedWidgetsHeader != null) {
989                         mSuggestedWidgetsHeader.setExpanded(false);
990                     }
991                     WidgetsRowViewHolder widgetsRowViewHolder =
992                             mWidgetsListTableViewHolderBinder.newViewHolder(mRightPane);
993                     mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
994                             contentEntry,
995                             ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
996                             Collections.EMPTY_LIST);
997                     widgetsRowViewHolder.mDataCallback = data -> {
998                         mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
999                                 contentEntry,
1000                                 ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
1001                                 Collections.singletonList(data));
1002                     };
1003                     mRightPane.removeAllViews();
1004                     mRightPane.addView(widgetsRowViewHolder.itemView);
1005                 }
1006             };
1007             mWidgetsListAdapter = new WidgetsListAdapter(
1008                     context,
1009                     LayoutInflater.from(context),
1010                     this::getEmptySpaceHeight,
1011                     /* iconClickListener= */ WidgetsFullSheet.this,
1012                     /* iconLongClickListener= */ WidgetsFullSheet.this,
1013                     mIsTwoPane ? headerChangeListener : null);
1014             mWidgetsListAdapter.setHasStableIds(true);
1015             switch (mAdapterType) {
1016                 case PRIMARY:
1017                     mWidgetsListAdapter.setFilter(mPrimaryWidgetsFilter);
1018                     break;
1019                 case WORK:
1020                     mWidgetsListAdapter.setFilter(mWorkWidgetsFilter);
1021                     break;
1022                 default:
1023                     break;
1024             }
1025             mWidgetsListItemAnimator = new DefaultItemAnimator();
1026             // Disable change animations because it disrupts the item focus upon adapter item
1027             // change.
1028             mWidgetsListItemAnimator.setSupportsChangeAnimations(false);
1029         }
1030 
getEmptySpaceHeight()1031         private int getEmptySpaceHeight() {
1032             return mSearchScrollView.getHeaderHeight();
1033         }
1034 
setup(WidgetsRecyclerView recyclerView)1035         void setup(WidgetsRecyclerView recyclerView) {
1036             mWidgetsRecyclerView = recyclerView;
1037             mWidgetsRecyclerView.setOutlineProvider(mViewOutlineProvider);
1038             mWidgetsRecyclerView.setClipToOutline(true);
1039             mWidgetsRecyclerView.setClipChildren(false);
1040             mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
1041             mWidgetsRecyclerView.bindFastScrollbar(mFastScroller);
1042             mWidgetsRecyclerView.setItemAnimator(mWidgetsListItemAnimator);
1043             mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
1044             if (!mIsTwoPane) {
1045                 mWidgetsRecyclerView.setEdgeEffectFactory(
1046                         ((SpringRelativeLayout) mContent).createEdgeEffectFactory());
1047             }
1048             // Recycler view binds to fast scroller when it is attached to screen. Make sure
1049             // search recycler view is bound to fast scroller if user is in search mode at the time
1050             // of attachment.
1051             if (mAdapterType == PRIMARY || mAdapterType == WORK) {
1052                 mWidgetsRecyclerView.addOnAttachStateChangeListener(mBindScrollbarInSearchMode);
1053             }
1054             mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(mMaxSpanPerRow);
1055         }
1056     }
1057 
1058     /**
1059      * This is a listener for when the selected header gets changed in the left pane.
1060      */
1061     public interface HeaderChangeListener {
1062         /**
1063          * Sets the right pane to have the widgets for the currently selected header from
1064          * the left pane.
1065          */
onHeaderChanged(@onNull PackageUserKey selectedHeader)1066         void onHeaderChanged(@NonNull PackageUserKey selectedHeader);
1067     }
1068 }
1069