blob: 5d9a2e2f8857eb00899359b1ff19053492b97d8c [file] [log] [blame]
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -07001/*
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 */
Steven Ng2f5648a2021-02-08 17:18:25 +000016package com.android.launcher3.widget.picker;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070017
Sunny Goyalf3ac7032020-03-13 13:01:33 -070018import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
vadimt9f48a8e2019-12-11 11:52:37 -080019import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
20
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070021import android.animation.Animator;
22import android.animation.AnimatorListenerAdapter;
23import android.animation.PropertyValuesHolder;
24import android.content.Context;
Steven Ng391404f2021-02-17 15:58:23 +000025import android.content.pm.LauncherApps;
Alina Zaidic4f3f492021-03-19 14:22:43 +000026import android.content.res.Configuration;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070027import android.graphics.Rect;
Steven Ng391404f2021-02-17 15:58:23 +000028import android.os.Process;
29import android.os.UserHandle;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070030import android.util.AttributeSet;
Sunny Goyalde753212018-05-15 13:55:57 -070031import android.util.Pair;
Steven Ng391404f2021-02-17 15:58:23 +000032import android.util.SparseArray;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070033import android.view.LayoutInflater;
34import android.view.MotionEvent;
Sunny Goyalde753212018-05-15 13:55:57 -070035import android.view.View;
Steven Ng4235fc12021-03-19 20:13:16 +000036import android.view.ViewGroup;
Alina Zaidi76060eb2021-03-22 12:25:37 +000037import android.view.WindowInsets;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070038import android.view.animation.AnimationUtils;
Sunny Goyalf3ac7032020-03-13 13:01:33 -070039import android.view.animation.Interpolator;
Steven Ng167f81b2021-02-23 10:48:46 +000040import android.widget.TextView;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070041
Steven Ng391404f2021-02-17 15:58:23 +000042import androidx.annotation.Nullable;
Tony Wickham9791bd12019-04-05 13:52:35 -070043import androidx.annotation.VisibleForTesting;
Alina Zaidi120d6472021-04-09 09:58:53 +010044import androidx.core.view.ViewCompat;
Steven Ng391404f2021-02-17 15:58:23 +000045import androidx.recyclerview.widget.RecyclerView;
Tony Wickham9791bd12019-04-05 13:52:35 -070046
Steven Nge8d92342021-02-19 21:29:18 +000047import com.android.launcher3.DeviceProfile;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070048import com.android.launcher3.Insettable;
49import com.android.launcher3.Launcher;
50import com.android.launcher3.LauncherAppState;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070051import com.android.launcher3.R;
Sunny Goyalf3ac7032020-03-13 13:01:33 -070052import com.android.launcher3.anim.PendingAnimation;
vadimt9f48a8e2019-12-11 11:52:37 -080053import com.android.launcher3.compat.AccessibilityManagerCompat;
Steven Ng4235fc12021-03-19 20:13:16 +000054import com.android.launcher3.model.WidgetItem;
Alina Zaidi120d6472021-04-09 09:58:53 +010055import com.android.launcher3.views.ArrowTipView;
Sunny Goyal3661bfa2018-03-01 16:29:15 -080056import com.android.launcher3.views.RecyclerViewFastScroller;
57import com.android.launcher3.views.TopRoundedCornerView;
Steven Ng2f5648a2021-02-08 17:18:25 +000058import com.android.launcher3.widget.BaseWidgetSheet;
Yogisha Dixit741fae92021-02-22 14:03:44 +000059import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
Steven Ng391404f2021-02-17 15:58:23 +000060import com.android.launcher3.widget.model.WidgetsListBaseEntry;
Alina Zaidi334e6592021-03-11 16:10:27 +000061import com.android.launcher3.widget.picker.search.SearchModeListener;
62import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
Alina Zaidi2cff1642021-03-24 09:52:45 +000063import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper;
Steven Ng4235fc12021-03-19 20:13:16 +000064import com.android.launcher3.widget.util.WidgetsTableUtils;
Steven Ng391404f2021-02-17 15:58:23 +000065import com.android.launcher3.workprofile.PersonalWorkPagedView;
66import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
67
Alina Zaidi2e732e92021-03-12 17:39:38 +000068import java.util.ArrayList;
Steven Ng391404f2021-02-17 15:58:23 +000069import java.util.List;
70import java.util.function.Predicate;
Alina Zaidi120d6472021-04-09 09:58:53 +010071import java.util.stream.IntStream;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070072
73/**
74 * Popup for showing the full list of available widgets
75 */
76public class WidgetsFullSheet extends BaseWidgetSheet
Steven Ng167f81b2021-02-23 10:48:46 +000077 implements Insettable, ProviderChangedListener, OnActivePageChangedListener,
Alina Zaidi2cff1642021-03-24 09:52:45 +000078 WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener,
79 WidgetsSearchBarUIHelper {
Steven Ng4235fc12021-03-19 20:13:16 +000080 private static final String TAG = WidgetsFullSheet.class.getSimpleName();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070081
82 private static final long DEFAULT_OPEN_DURATION = 267;
83 private static final long FADE_IN_DURATION = 150;
Alina Zaidi120d6472021-04-09 09:58:53 +010084 private static final long EDUCATION_TIP_DELAY_MS = 200;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070085 private static final float VERTICAL_START_POSITION = 0.3f;
Steven Ng4235fc12021-03-19 20:13:16 +000086 // The widget recommendation table can easily take over the entire screen on devices with small
87 // resolution or landscape on phone. This ratio defines the max percentage of content area that
88 // the table can display.
89 private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
Alina Zaidi120d6472021-04-09 09:58:53 +010090 private static final String WIDGETS_EDUCATION_TIP_SEEN = "launcher.widgets_education_tip_seen";
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070091
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070092 private final Rect mInsets = new Rect();
Steven Ng391404f2021-02-17 15:58:23 +000093 private final boolean mHasWorkProfile;
94 private final SparseArray<AdapterHolder> mAdapters = new SparseArray();
95 private final UserHandle mCurrentUser = Process.myUserHandle();
96 private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter = entry ->
97 mCurrentUser.equals(entry.mPkgItem.user);
98 private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
99 mPrimaryWidgetsFilter.negate();
Alina Zaidi120d6472021-04-09 09:58:53 +0100100 private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
101 new OnLayoutChangeListener() {
102 @Override
103 public void onLayoutChange(View v, int left, int top, int right, int bottom,
104 int oldLeft, int oldTop, int oldRight, int oldBottom) {
105 if (hasSeenEducationTip()) {
106 removeOnLayoutChangeListener(this);
107 return;
108 }
109
110 // Widgets are loaded asynchronously, We are adding a delay because we only want
111 // to show the tip when the widget preview has finished loading and rendering in
112 // this view.
113 removeCallbacks(mShowEducationTipTask);
114 postDelayed(mShowEducationTipTask, EDUCATION_TIP_DELAY_MS);
115 }
116 };
117
118 private final Runnable mShowEducationTipTask = () -> {
119 if (hasSeenEducationTip()) {
120 removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
121 return;
122 }
123 View viewForTip = getViewToShowEducationTip();
124 if (viewForTip != null && ViewCompat.isLaidOut(viewForTip)) {
125 removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
126 showEducationTipOnView(viewForTip);
127 }
128 };
Steven Ng4235fc12021-03-19 20:13:16 +0000129 private final int mTabsHeight;
130 private final int mWidgetCellHorizontalPadding;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700131
Steven Ng391404f2021-02-17 15:58:23 +0000132 @Nullable private PersonalWorkPagedView mViewPager;
Alina Zaidi334e6592021-03-11 16:10:27 +0000133 private boolean mIsInSearchMode;
Steven Ng4235fc12021-03-19 20:13:16 +0000134 private int mMaxSpansPerRow = 4;
Steven Ng167f81b2021-02-23 10:48:46 +0000135 private View mTabsView;
Steven Ngd73d6e52021-03-09 22:44:04 +0000136 private TextView mNoWidgetsView;
Steven Ng167f81b2021-02-23 10:48:46 +0000137 private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
138 private SearchAndRecommendationsScrollController mSearchAndRecommendationsScrollController;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700139
140 public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
141 super(context, attrs, defStyleAttr);
Steven Ng391404f2021-02-17 15:58:23 +0000142 mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
143 mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
144 mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
Alina Zaidi334e6592021-03-11 16:10:27 +0000145 mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
Steven Ng4235fc12021-03-19 20:13:16 +0000146 mTabsHeight = mHasWorkProfile
147 ? getContext().getResources()
148 .getDimensionPixelSize(R.dimen.all_apps_header_tab_height)
149 : 0;
150 mWidgetCellHorizontalPadding = 2 * getResources().getDimensionPixelOffset(
151 R.dimen.widget_cell_horizontal_padding);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700152 }
153
154 public WidgetsFullSheet(Context context, AttributeSet attrs) {
155 this(context, attrs, 0);
156 }
157
158 @Override
159 protected void onFinishInflate() {
160 super.onFinishInflate();
161 mContent = findViewById(R.id.container);
Sunny Goyal016d7e92018-03-09 11:09:20 -0800162 TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
Steven Ng391404f2021-02-17 15:58:23 +0000163
164 LayoutInflater layoutInflater = LayoutInflater.from(getContext());
165 int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view
166 : R.layout.widgets_full_sheet_recyclerview;
Steven Ng167f81b2021-02-23 10:48:46 +0000167 layoutInflater.inflate(contentLayoutRes, springLayout, true);
Steven Ng391404f2021-02-17 15:58:23 +0000168
Steven Ng167f81b2021-02-23 10:48:46 +0000169 RecyclerViewFastScroller fastScroller = findViewById(R.id.fast_scroller);
Steven Ng391404f2021-02-17 15:58:23 +0000170 if (mHasWorkProfile) {
171 mViewPager = findViewById(R.id.widgets_view_pager);
Steven Ng391404f2021-02-17 15:58:23 +0000172 mViewPager.initParentViews(this);
173 mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
174 mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.PRIMARY);
Steven Ng167f81b2021-02-23 10:48:46 +0000175 mTabsView = findViewById(R.id.tabs);
Steven Ng391404f2021-02-17 15:58:23 +0000176 findViewById(R.id.tab_personal)
177 .setOnClickListener((View view) -> mViewPager.snapToPage(0));
178 findViewById(R.id.tab_work)
179 .setOnClickListener((View view) -> mViewPager.snapToPage(1));
Steven Ng167f81b2021-02-23 10:48:46 +0000180 fastScroller.setIsRecyclerViewFirstChildInParent(false);
Steven Ng391404f2021-02-17 15:58:23 +0000181 } else {
182 mViewPager = null;
Steven Ng391404f2021-02-17 15:58:23 +0000183 }
184
Steven Ng167f81b2021-02-23 10:48:46 +0000185 layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout,
186 true);
Steven Ng167f81b2021-02-23 10:48:46 +0000187 mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder(
188 findViewById(R.id.search_and_recommendations_container));
189 mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
190 mHasWorkProfile,
Steven Ng4235fc12021-03-19 20:13:16 +0000191 mTabsHeight,
Steven Ng167f81b2021-02-23 10:48:46 +0000192 mSearchAndRecommendationViewHolder,
193 findViewById(R.id.primary_widgets_list_view),
194 mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
Alina Zaidi334e6592021-03-11 16:10:27 +0000195 findViewById(R.id.search_widgets_list_view),
Steven Ng167f81b2021-02-23 10:48:46 +0000196 mTabsView,
197 mViewPager);
198 fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
199
Steven Ngd73d6e52021-03-09 22:44:04 +0000200 mNoWidgetsView = findViewById(R.id.no_widgets_text);
201
Steven Ng4235fc12021-03-19 20:13:16 +0000202 onRecommendedWidgetsBound();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700203 onWidgetsBound();
Alina Zaidi334e6592021-03-11 16:10:27 +0000204
205 mSearchAndRecommendationViewHolder.mSearchBar.initialize(
Sunny Goyalce6cc7e2021-03-30 16:51:08 -0700206 mLauncher.getPopupDataProvider(), /* searchModeListener= */ this);
Alina Zaidi120d6472021-04-09 09:58:53 +0100207
208 if (!hasSeenEducationTip()) {
209 addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
210 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700211 }
212
Steven Ng391404f2021-02-17 15:58:23 +0000213 @Override
214 public void onActivePageChanged(int currentActivePage) {
Steven Ngd73d6e52021-03-09 22:44:04 +0000215 AdapterHolder currentAdapterHolder = mAdapters.get(currentActivePage);
Alina Zaidi334e6592021-03-11 16:10:27 +0000216 WidgetsRecyclerView currentRecyclerView =
217 mAdapters.get(currentActivePage).mWidgetsRecyclerView;
Steven Ng167f81b2021-02-23 10:48:46 +0000218
Steven Ngd73d6e52021-03-09 22:44:04 +0000219 updateNoWidgetsView(currentAdapterHolder);
Alina Zaidi334e6592021-03-11 16:10:27 +0000220 attachScrollbarToRecyclerView(currentRecyclerView);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000221 resetExpandedHeaders();
Alina Zaidi334e6592021-03-11 16:10:27 +0000222 }
223
224 private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) {
225 recyclerView.bindFastScrollbar();
226 mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView);
Steven Ng167f81b2021-02-23 10:48:46 +0000227 reset();
228 }
229
Steven Ngd73d6e52021-03-09 22:44:04 +0000230 private void updateNoWidgetsView(AdapterHolder adapterHolder) {
231 boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.getItemCount() > 0;
232 adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
233
234 // Always resets the text in case this is updated by search.
235 mNoWidgetsView.setText(R.string.no_widgets_available);
236 mNoWidgetsView.setVisibility(isWidgetAvailable ? GONE : VISIBLE);
237 }
238
Alina Zaidi2e732e92021-03-12 17:39:38 +0000239 private void updateNoSearchResultsView(boolean isVisible) {
240 mNoWidgetsView.setVisibility(isVisible ? VISIBLE : GONE);
241 if (isVisible) {
242 mNoWidgetsView.setText(R.string.no_search_results);
243 }
244 }
245
Steven Ng167f81b2021-02-23 10:48:46 +0000246 private void reset() {
247 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
248 if (mHasWorkProfile) {
249 mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
250 }
Alina Zaidi334e6592021-03-11 16:10:27 +0000251 mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
Steven Ng7f7b2f72021-03-03 14:51:36 +0000252 mSearchAndRecommendationsScrollController.reset();
Steven Ng391404f2021-02-17 15:58:23 +0000253 }
254
Sunny Goyal8b37c572020-03-31 12:12:14 -0700255 @VisibleForTesting
256 public WidgetsRecyclerView getRecyclerView() {
Alina Zaidi334e6592021-03-11 16:10:27 +0000257 if (mIsInSearchMode) {
258 return mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView;
259 }
Steven Ng391404f2021-02-17 15:58:23 +0000260 if (!mHasWorkProfile || mViewPager.getCurrentPage() == AdapterHolder.PRIMARY) {
261 return mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
262 }
263 return mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView;
Sunny Goyal8b37c572020-03-31 12:12:14 -0700264 }
265
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700266 @Override
Sunny Goyalde753212018-05-15 13:55:57 -0700267 protected Pair<View, String> getAccessibilityTarget() {
Steven Ng391404f2021-02-17 15:58:23 +0000268 return Pair.create(getRecyclerView(), getContext().getString(
Sunny Goyalde753212018-05-15 13:55:57 -0700269 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
270 }
271
272 @Override
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700273 protected void onAttachedToWindow() {
274 super.onAttachedToWindow();
275 mLauncher.getAppWidgetHost().addProviderChangeListener(this);
276 notifyWidgetProvidersChanged();
277 }
278
279 @Override
280 protected void onDetachedFromWindow() {
281 super.onDetachedFromWindow();
282 mLauncher.getAppWidgetHost().removeProviderChangeListener(this);
283 }
284
285 @Override
286 public void setInsets(Rect insets) {
287 mInsets.set(insets);
288
Steven Ng391404f2021-02-17 15:58:23 +0000289 setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, insets.bottom);
Alina Zaidic4f3f492021-03-19 14:22:43 +0000290 setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, insets.bottom);
Steven Ng391404f2021-02-17 15:58:23 +0000291 if (mHasWorkProfile) {
292 setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
293 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700294 if (insets.bottom > 0) {
295 setupNavBarColor();
Sunny Goyalf8d56fc2018-01-31 15:18:11 -0800296 } else {
297 clearNavBarColor();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700298 }
Sunny Goyal3661bfa2018-03-01 16:29:15 -0800299
300 ((TopRoundedCornerView) mContent).setNavBarScrimHeight(mInsets.bottom);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700301 requestLayout();
302 }
303
Steven Ng391404f2021-02-17 15:58:23 +0000304 private void setBottomPadding(RecyclerView recyclerView, int bottomPadding) {
305 recyclerView.setPadding(
306 recyclerView.getPaddingLeft(),
307 recyclerView.getPaddingTop(),
308 recyclerView.getPaddingRight(),
309 bottomPadding);
310 }
311
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700312 @Override
313 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Steven Ng4235fc12021-03-19 20:13:16 +0000314 doMeasure(widthMeasureSpec, heightMeasureSpec);
315
316 if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
317 doMeasure(widthMeasureSpec, heightMeasureSpec);
318 }
319
320 if (updateMaxSpansPerRow()) {
321 doMeasure(widthMeasureSpec, heightMeasureSpec);
322
323 if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
324 doMeasure(widthMeasureSpec, heightMeasureSpec);
325 }
326 }
327 }
328
329 private void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Steven Nge8d92342021-02-19 21:29:18 +0000330 DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700331 int widthUsed;
332 if (mInsets.bottom > 0) {
Sunny Goyal786940a2020-06-02 02:31:31 -0700333 widthUsed = mInsets.left + mInsets.right;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700334 } else {
Steven Nge8d92342021-02-19 21:29:18 +0000335 Rect padding = deviceProfile.workspacePadding;
Sunny Goyal07b69292018-01-08 14:19:34 -0800336 widthUsed = Math.max(padding.left + padding.right,
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700337 2 * (mInsets.left + mInsets.right));
338 }
339
Steven Nge8d92342021-02-19 21:29:18 +0000340 int heightUsed = mInsets.top + deviceProfile.edgeMarginPx;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700341 measureChildWithMargins(mContent, widthMeasureSpec,
342 widthUsed, heightMeasureSpec, heightUsed);
Sunny Goyal6639a5d2018-02-28 15:09:36 -0800343 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
344 MeasureSpec.getSize(heightMeasureSpec));
Steven Ng4235fc12021-03-19 20:13:16 +0000345 }
Steven Nge8d92342021-02-19 21:29:18 +0000346
Steven Ng4235fc12021-03-19 20:13:16 +0000347 /** Returns {@code true} if the max spans have been updated. */
348 private boolean updateMaxSpansPerRow() {
349 if (getMeasuredWidth() == 0) return false;
350
351 int previousMaxSpansPerRow = mMaxSpansPerRow;
352 mMaxSpansPerRow = getMeasuredWidth()
353 / (mLauncher.getDeviceProfile().cellWidthPx + mWidgetCellHorizontalPadding);
354
355 if (previousMaxSpansPerRow != mMaxSpansPerRow) {
356 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
357 mMaxSpansPerRow);
358 mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
359 mMaxSpansPerRow);
360 if (mHasWorkProfile) {
361 mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
362 mMaxSpansPerRow);
363 }
364 onRecommendedWidgetsBound();
365 return true;
Steven Nge8d92342021-02-19 21:29:18 +0000366 }
Steven Ng4235fc12021-03-19 20:13:16 +0000367 return false;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700368 }
369
370 @Override
371 protected void onLayout(boolean changed, int l, int t, int r, int b) {
372 int width = r - l;
373 int height = b - t;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700374
375 // Content is laid out as center bottom aligned
376 int contentWidth = mContent.getMeasuredWidth();
Sunny Goyal786940a2020-06-02 02:31:31 -0700377 int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700378 mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
379 contentLeft + contentWidth, height);
380
381 setTranslationShift(mTranslationShift);
382 }
383
384 @Override
385 public void notifyWidgetProvidersChanged() {
386 mLauncher.refreshAndBindWidgetsForPackageUser(null);
387 }
388
389 @Override
Sunny Goyal202ae0b2019-02-04 16:26:42 -0800390 public void onWidgetsBound() {
Steven Ng391404f2021-02-17 15:58:23 +0000391 List<WidgetsListBaseEntry> allWidgets = mLauncher.getPopupDataProvider().getAllWidgets();
392
393 AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY);
394 primaryUserAdapterHolder.setup(findViewById(R.id.primary_widgets_list_view));
Alina Zaidi334e6592021-03-11 16:10:27 +0000395 AdapterHolder searchAdapterHolder = mAdapters.get(AdapterHolder.SEARCH);
396 searchAdapterHolder.setup(findViewById(R.id.search_widgets_list_view));
Steven Ng391404f2021-02-17 15:58:23 +0000397 primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
Steven Ngd73d6e52021-03-09 22:44:04 +0000398 updateNoWidgetsView(primaryUserAdapterHolder);
399
Steven Ng391404f2021-02-17 15:58:23 +0000400 if (mHasWorkProfile) {
401 AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
402 workUserAdapterHolder.setup(findViewById(R.id.work_widgets_list_view));
403 workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
404 onActivePageChanged(mViewPager.getCurrentPage());
405 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700406 }
407
Alina Zaidi334e6592021-03-11 16:10:27 +0000408 @Override
409 public void enterSearchMode() {
410 if (mIsInSearchMode) return;
411 setViewVisibilityBasedOnSearch(/*isInSearchMode= */ true);
412 attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000413 resetExpandedHeaders();
Alina Zaidi334e6592021-03-11 16:10:27 +0000414 }
415
416 @Override
417 public void exitSearchMode() {
Alina Zaidi2e732e92021-03-12 17:39:38 +0000418 onSearchResults(new ArrayList<>());
Alina Zaidi334e6592021-03-11 16:10:27 +0000419 setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false);
420 if (mHasWorkProfile) {
421 mViewPager.snapToPage(AdapterHolder.PRIMARY);
422 }
423 attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
Steven Ng4235fc12021-03-19 20:13:16 +0000424
425 mSearchAndRecommendationsScrollController.updateMarginAndPadding();
Alina Zaidi334e6592021-03-11 16:10:27 +0000426 }
427
428 @Override
429 public void onSearchResults(List<WidgetsListBaseEntry> entries) {
430 mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setWidgetsOnSearch(entries);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000431 updateNoSearchResultsView(
432 mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.getItemCount() == 0);
Alina Zaidi334e6592021-03-11 16:10:27 +0000433 }
434
435 private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
436 mIsInSearchMode = isInSearchMode;
Steven Ng4235fc12021-03-19 20:13:16 +0000437 mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
438 .setVisibility(isInSearchMode ? GONE : VISIBLE);
Alina Zaidi334e6592021-03-11 16:10:27 +0000439 if (mHasWorkProfile) {
440 mViewPager.setVisibility(isInSearchMode ? GONE : VISIBLE);
441 mTabsView.setVisibility(isInSearchMode ? GONE : VISIBLE);
442 } else {
443 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView
444 .setVisibility(isInSearchMode ? GONE : VISIBLE);
445 }
446 mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView
447 .setVisibility(mIsInSearchMode ? VISIBLE : GONE);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000448 mNoWidgetsView.setVisibility(GONE);
449 }
450
451 private void resetExpandedHeaders() {
452 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.resetExpandedHeader();
453 mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader();
Alina Zaidi334e6592021-03-11 16:10:27 +0000454 }
455
Steven Ng4235fc12021-03-19 20:13:16 +0000456 @Override
457 public void onRecommendedWidgetsBound() {
458 List<WidgetItem> recommendedWidgets =
459 mLauncher.getPopupDataProvider().getRecommendedWidgets();
460 WidgetsRecommendationTableLayout table =
461 mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable;
462 if (recommendedWidgets.size() > 0) {
463 float maxTableHeight =
464 (mLauncher.getDeviceProfile().heightPx - mTabsHeight - getHeaderViewHeight())
465 * RECOMMENDATION_TABLE_HEIGHT_RATIO;
466 List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
467 WidgetsTableUtils.groupWidgetItemsIntoTable(recommendedWidgets,
468 mMaxSpansPerRow);
469 table.setRecommendedWidgets(recommendedWidgetsInTable, maxTableHeight);
470 } else {
471 table.setVisibility(GONE);
472 }
473 }
474
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700475 private void open(boolean animate) {
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700476 if (animate) {
Sunny Goyaldb6cdb02018-07-13 11:03:04 -0700477 if (getPopupContainer().getInsets().bottom > 0) {
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700478 mContent.setAlpha(0);
479 setTranslationShift(VERTICAL_START_POSITION);
480 }
481 mOpenCloseAnimator.setValues(
482 PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
483 mOpenCloseAnimator
484 .setDuration(DEFAULT_OPEN_DURATION)
485 .setInterpolator(AnimationUtils.loadInterpolator(
486 getContext(), android.R.interpolator.linear_out_slow_in));
487 mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
488 @Override
489 public void onAnimationEnd(Animator animation) {
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700490 mOpenCloseAnimator.removeListener(this);
491 }
492 });
Sunny Goyal6639a5d2018-02-28 15:09:36 -0800493 post(() -> {
Sunny Goyal6639a5d2018-02-28 15:09:36 -0800494 mOpenCloseAnimator.start();
495 mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700496 });
497 } else {
498 setTranslationShift(TRANSLATION_SHIFT_OPENED);
Sunny Goyalde753212018-05-15 13:55:57 -0700499 post(this::announceAccessibilityChanges);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700500 }
501 }
502
503 @Override
504 protected void handleClose(boolean animate) {
505 handleClose(animate, DEFAULT_OPEN_DURATION);
506 }
507
508 @Override
509 protected boolean isOfType(int type) {
510 return (type & TYPE_WIDGETS_FULL_SHEET) != 0;
511 }
512
513 @Override
514 public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
515 // Disable swipe down when recycler view is scrolling
516 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
517 mNoIntercept = false;
Steven Ng391404f2021-02-17 15:58:23 +0000518 RecyclerViewFastScroller scroller = getRecyclerView().getScrollbar();
Steven Ng2f5648a2021-02-08 17:18:25 +0000519 if (scroller.getThumbOffsetY() >= 0
520 && getPopupContainer().isEventOverView(scroller, ev)) {
Sunny Goyal3661bfa2018-03-01 16:29:15 -0800521 mNoIntercept = true;
Sunny Goyaldb6cdb02018-07-13 11:03:04 -0700522 } else if (getPopupContainer().isEventOverView(mContent, ev)) {
Steven Ng391404f2021-02-17 15:58:23 +0000523 mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer());
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700524 }
525 }
526 return super.onControllerInterceptTouchEvent(ev);
527 }
528
Steven Ng2f5648a2021-02-08 17:18:25 +0000529 /** Shows the {@link WidgetsFullSheet} on the launcher. */
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700530 public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
531 WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
532 .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
Sunny Goyal1642f712018-09-18 11:40:35 -0700533 sheet.attachToContainer();
Sunny Goyalde753212018-05-15 13:55:57 -0700534 sheet.mIsOpen = true;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700535 sheet.open(animate);
536 return sheet;
537 }
Sunny Goyalaeb16432017-10-16 11:46:41 -0700538
Steven Ng2f5648a2021-02-08 17:18:25 +0000539 /** Gets the {@link WidgetsRecyclerView} which shows all widgets in {@link WidgetsFullSheet}. */
Vadim Tryshev2ce6a132018-06-18 19:14:44 -0700540 @VisibleForTesting
541 public static WidgetsRecyclerView getWidgetsView(Launcher launcher) {
Steven Ng391404f2021-02-17 15:58:23 +0000542 return launcher.findViewById(R.id.primary_widgets_list_view);
Vadim Tryshev2ce6a132018-06-18 19:14:44 -0700543 }
544
Sunny Goyalaeb16432017-10-16 11:46:41 -0700545 @Override
Sunny Goyalf3ac7032020-03-13 13:01:33 -0700546 public void addHintCloseAnim(
547 float distanceToMove, Interpolator interpolator, PendingAnimation target) {
Steven Ng391404f2021-02-17 15:58:23 +0000548 target.setFloat(getRecyclerView(), VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
549 target.setViewAlpha(getRecyclerView(), 0.5f, interpolator);
Tony Wickham9791bd12019-04-05 13:52:35 -0700550 }
vadimt9f48a8e2019-12-11 11:52:37 -0800551
552 @Override
553 protected void onCloseComplete() {
554 super.onCloseComplete();
555 AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
556 }
Steven Ng391404f2021-02-17 15:58:23 +0000557
Steven Ng167f81b2021-02-23 10:48:46 +0000558 @Override
559 public int getHeaderViewHeight() {
Steven Ng53d13642021-03-10 22:40:55 +0000560 return measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mCollapseHandle)
561 + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mHeaderTitle)
Alina Zaidi16327522021-03-15 07:00:49 +0000562 + measureHeightWithVerticalMargins(
563 (View) mSearchAndRecommendationViewHolder.mSearchBar);
Steven Ng7f7b2f72021-03-03 14:51:36 +0000564 }
565
566 /** private the height, in pixel, + the vertical margins of a given view. */
567 private static int measureHeightWithVerticalMargins(View view) {
Steven Ng53d13642021-03-10 22:40:55 +0000568 if (view.getVisibility() != VISIBLE) {
569 return 0;
570 }
Steven Ng7f7b2f72021-03-03 14:51:36 +0000571 MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
572 return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
573 + marginLayoutParams.topMargin;
Steven Ng167f81b2021-02-23 10:48:46 +0000574 }
575
Alina Zaidic4f3f492021-03-19 14:22:43 +0000576 @Override
577 protected void onConfigurationChanged(Configuration newConfig) {
578 super.onConfigurationChanged(newConfig);
579 if (mIsInSearchMode) {
580 mSearchAndRecommendationViewHolder.mSearchBar.reset();
581 }
582 }
583
584 @Override
585 public boolean onBackPressed() {
586 if (mIsInSearchMode) {
587 mSearchAndRecommendationViewHolder.mSearchBar.reset();
588 return true;
589 }
590 return super.onBackPressed();
591 }
592
Alina Zaidi76060eb2021-03-22 12:25:37 +0000593 @Override
594 public void onDragStart(boolean start, float startDisplacement) {
595 super.onDragStart(start, startDisplacement);
596 getWindowInsetsController().hide(WindowInsets.Type.ime());
597 }
598
Alina Zaidi2cff1642021-03-24 09:52:45 +0000599 @Override
600 public void clearSearchBarFocus() {
601 mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus();
602 }
603
Alina Zaidi120d6472021-04-09 09:58:53 +0100604 private void showEducationTipOnView(View view) {
605 mLauncher.getSharedPrefs().edit().putBoolean(WIDGETS_EDUCATION_TIP_SEEN, true).apply();
606 int[] coords = new int[2];
607 view.getLocationOnScreen(coords);
608 ArrowTipView arrowTipView = new ArrowTipView(mLauncher);
609 arrowTipView.showAtLocation(
610 getContext().getString(R.string.long_press_widget_to_add),
611 /* arrowXCoord= */coords[0] + view.getWidth() / 2,
612 /* yCoord= */coords[1]);
613 }
614
615 @Nullable private View getViewToShowEducationTip() {
616 if (mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getVisibility() == VISIBLE
617 && mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getChildCount() > 0
618 ) {
619 return ((ViewGroup) mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
620 .getChildAt(0)).getChildAt(0);
621 }
622
623 AdapterHolder adapterHolder = mAdapters.get(mIsInSearchMode
624 ? AdapterHolder.SEARCH
625 : mViewPager == null
626 ? AdapterHolder.PRIMARY
627 : mViewPager.getCurrentPage());
628 WidgetsRowViewHolder viewHolderForTip =
629 (WidgetsRowViewHolder) IntStream.range(
630 0, adapterHolder.mWidgetsListAdapter.getItemCount())
631 .mapToObj(adapterHolder.mWidgetsRecyclerView::
632 findViewHolderForAdapterPosition)
633 .filter(viewHolder -> viewHolder instanceof WidgetsRowViewHolder)
634 .findFirst()
635 .orElse(null);
636 if (viewHolderForTip != null) {
637 return ((ViewGroup) viewHolderForTip.mTableContainer.getChildAt(0)).getChildAt(0);
638 }
639
640 return null;
641 }
642
643 private boolean hasSeenEducationTip() {
644 return mLauncher.getSharedPrefs().getBoolean(WIDGETS_EDUCATION_TIP_SEEN, false);
645 }
646
Steven Ng391404f2021-02-17 15:58:23 +0000647 /** A holder class for holding adapters & their corresponding recycler view. */
648 private final class AdapterHolder {
649 static final int PRIMARY = 0;
650 static final int WORK = 1;
Alina Zaidi334e6592021-03-11 16:10:27 +0000651 static final int SEARCH = 2;
Steven Ng391404f2021-02-17 15:58:23 +0000652
653 private final int mAdapterType;
654 private final WidgetsListAdapter mWidgetsListAdapter;
655
656 private WidgetsRecyclerView mWidgetsRecyclerView;
657
658 AdapterHolder(int adapterType) {
659 mAdapterType = adapterType;
660
661 Context context = getContext();
662 LauncherAppState apps = LauncherAppState.getInstance(context);
663 mWidgetsListAdapter = new WidgetsListAdapter(
664 context,
665 LayoutInflater.from(context),
666 apps.getWidgetCache(),
667 apps.getIconCache(),
668 /* iconClickListener= */ WidgetsFullSheet.this,
Alina Zaidi2cff1642021-03-24 09:52:45 +0000669 /* iconLongClickListener= */ WidgetsFullSheet.this,
670 /* WidgetsSearchBarUIHelper= */
671 mAdapterType == SEARCH ? WidgetsFullSheet.this : null);
Steven Nge85b5562021-03-19 23:52:12 +0000672 mWidgetsListAdapter.setHasStableIds(true);
Alina Zaidi334e6592021-03-11 16:10:27 +0000673 switch (mAdapterType) {
674 case PRIMARY:
675 mWidgetsListAdapter.setFilter(mPrimaryWidgetsFilter);
676 break;
677 case WORK:
678 mWidgetsListAdapter.setFilter(mWorkWidgetsFilter);
679 break;
680 default:
681 break;
682 }
Steven Ng391404f2021-02-17 15:58:23 +0000683 }
684
685 void setup(WidgetsRecyclerView recyclerView) {
686 mWidgetsRecyclerView = recyclerView;
687 mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
Steven Nge85b5562021-03-19 23:52:12 +0000688 // Disables animation because it disrupts the item focus upon adapter item change.
689 mWidgetsRecyclerView.setItemAnimator(null);
Steven Ng167f81b2021-02-23 10:48:46 +0000690 mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
Steven Ng391404f2021-02-17 15:58:23 +0000691 mWidgetsRecyclerView.setEdgeEffectFactory(
692 ((TopRoundedCornerView) mContent).createEdgeEffectFactory());
693 mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
Steven Ng4235fc12021-03-19 20:13:16 +0000694 mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
Steven Ng391404f2021-02-17 15:58:23 +0000695 }
696 }
Steven Ng167f81b2021-02-23 10:48:46 +0000697
698 final class SearchAndRecommendationViewHolder {
Steven Ng4235fc12021-03-19 20:13:16 +0000699 final ViewGroup mContainer;
Steven Ng167f81b2021-02-23 10:48:46 +0000700 final View mCollapseHandle;
Alina Zaidi334e6592021-03-11 16:10:27 +0000701 final WidgetsSearchBar mSearchBar;
Steven Ng167f81b2021-02-23 10:48:46 +0000702 final TextView mHeaderTitle;
Steven Ng4235fc12021-03-19 20:13:16 +0000703 final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
Steven Ng167f81b2021-02-23 10:48:46 +0000704
Steven Ng4235fc12021-03-19 20:13:16 +0000705 SearchAndRecommendationViewHolder(ViewGroup searchAndRecommendationContainer) {
Steven Ng167f81b2021-02-23 10:48:46 +0000706 mContainer = searchAndRecommendationContainer;
707 mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
708 mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
709 mHeaderTitle = mContainer.findViewById(R.id.title);
Steven Ng4235fc12021-03-19 20:13:16 +0000710 mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
711 mRecommendedWidgetsTable.setWidgetCellOnTouchListener((view, event) -> {
712 getRecyclerView().onTouchEvent(event);
713 return false;
714 });
715 mRecommendedWidgetsTable.setWidgetCellLongClickListener(WidgetsFullSheet.this);
716 mRecommendedWidgetsTable.setWidgetCellOnClickListener(WidgetsFullSheet.this);
Steven Ng167f81b2021-02-23 10:48:46 +0000717 }
718 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700719}