blob: 1c34ece1156721ae61b9a5c186f778166468ef97 [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;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070037import android.view.animation.AnimationUtils;
Sunny Goyalf3ac7032020-03-13 13:01:33 -070038import android.view.animation.Interpolator;
Steven Ng167f81b2021-02-23 10:48:46 +000039import android.widget.TextView;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070040
Steven Ng391404f2021-02-17 15:58:23 +000041import androidx.annotation.Nullable;
Tony Wickham9791bd12019-04-05 13:52:35 -070042import androidx.annotation.VisibleForTesting;
Steven Ng391404f2021-02-17 15:58:23 +000043import androidx.recyclerview.widget.RecyclerView;
Tony Wickham9791bd12019-04-05 13:52:35 -070044
Steven Nge8d92342021-02-19 21:29:18 +000045import com.android.launcher3.DeviceProfile;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070046import com.android.launcher3.Insettable;
47import com.android.launcher3.Launcher;
48import com.android.launcher3.LauncherAppState;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070049import com.android.launcher3.R;
Sunny Goyalf3ac7032020-03-13 13:01:33 -070050import com.android.launcher3.anim.PendingAnimation;
vadimt9f48a8e2019-12-11 11:52:37 -080051import com.android.launcher3.compat.AccessibilityManagerCompat;
Steven Ng4235fc12021-03-19 20:13:16 +000052import com.android.launcher3.model.WidgetItem;
Sunny Goyal3661bfa2018-03-01 16:29:15 -080053import com.android.launcher3.views.RecyclerViewFastScroller;
54import com.android.launcher3.views.TopRoundedCornerView;
Steven Ng2f5648a2021-02-08 17:18:25 +000055import com.android.launcher3.widget.BaseWidgetSheet;
Yogisha Dixit741fae92021-02-22 14:03:44 +000056import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
Steven Ng391404f2021-02-17 15:58:23 +000057import com.android.launcher3.widget.model.WidgetsListBaseEntry;
Alina Zaidi334e6592021-03-11 16:10:27 +000058import com.android.launcher3.widget.picker.search.SearchModeListener;
59import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
Steven Ng4235fc12021-03-19 20:13:16 +000060import com.android.launcher3.widget.util.WidgetsTableUtils;
Steven Ng391404f2021-02-17 15:58:23 +000061import com.android.launcher3.workprofile.PersonalWorkPagedView;
62import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
63
Alina Zaidi2e732e92021-03-12 17:39:38 +000064import java.util.ArrayList;
Steven Ng391404f2021-02-17 15:58:23 +000065import java.util.List;
66import java.util.function.Predicate;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070067
68/**
69 * Popup for showing the full list of available widgets
70 */
71public class WidgetsFullSheet extends BaseWidgetSheet
Steven Ng167f81b2021-02-23 10:48:46 +000072 implements Insettable, ProviderChangedListener, OnActivePageChangedListener,
Alina Zaidi334e6592021-03-11 16:10:27 +000073 WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener {
Steven Ng4235fc12021-03-19 20:13:16 +000074 private static final String TAG = WidgetsFullSheet.class.getSimpleName();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070075
76 private static final long DEFAULT_OPEN_DURATION = 267;
77 private static final long FADE_IN_DURATION = 150;
78 private static final float VERTICAL_START_POSITION = 0.3f;
Steven Ng4235fc12021-03-19 20:13:16 +000079 // The widget recommendation table can easily take over the entire screen on devices with small
80 // resolution or landscape on phone. This ratio defines the max percentage of content area that
81 // the table can display.
82 private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070083
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070084 private final Rect mInsets = new Rect();
Steven Ng391404f2021-02-17 15:58:23 +000085 private final boolean mHasWorkProfile;
86 private final SparseArray<AdapterHolder> mAdapters = new SparseArray();
87 private final UserHandle mCurrentUser = Process.myUserHandle();
88 private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter = entry ->
89 mCurrentUser.equals(entry.mPkgItem.user);
90 private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
91 mPrimaryWidgetsFilter.negate();
Steven Ng4235fc12021-03-19 20:13:16 +000092 private final int mTabsHeight;
93 private final int mWidgetCellHorizontalPadding;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070094
Steven Ng391404f2021-02-17 15:58:23 +000095 @Nullable private PersonalWorkPagedView mViewPager;
Alina Zaidi334e6592021-03-11 16:10:27 +000096 private boolean mIsInSearchMode;
Steven Ng4235fc12021-03-19 20:13:16 +000097 private int mMaxSpansPerRow = 4;
Steven Ng167f81b2021-02-23 10:48:46 +000098 private View mTabsView;
Steven Ngd73d6e52021-03-09 22:44:04 +000099 private TextView mNoWidgetsView;
Steven Ng167f81b2021-02-23 10:48:46 +0000100 private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
101 private SearchAndRecommendationsScrollController mSearchAndRecommendationsScrollController;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700102
103 public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
104 super(context, attrs, defStyleAttr);
Steven Ng391404f2021-02-17 15:58:23 +0000105 mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
106 mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
107 mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
Alina Zaidi334e6592021-03-11 16:10:27 +0000108 mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
Steven Ng4235fc12021-03-19 20:13:16 +0000109 mTabsHeight = mHasWorkProfile
110 ? getContext().getResources()
111 .getDimensionPixelSize(R.dimen.all_apps_header_tab_height)
112 : 0;
113 mWidgetCellHorizontalPadding = 2 * getResources().getDimensionPixelOffset(
114 R.dimen.widget_cell_horizontal_padding);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700115 }
116
117 public WidgetsFullSheet(Context context, AttributeSet attrs) {
118 this(context, attrs, 0);
119 }
120
121 @Override
122 protected void onFinishInflate() {
123 super.onFinishInflate();
124 mContent = findViewById(R.id.container);
Sunny Goyal016d7e92018-03-09 11:09:20 -0800125 TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
Steven Ng391404f2021-02-17 15:58:23 +0000126
127 LayoutInflater layoutInflater = LayoutInflater.from(getContext());
128 int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view
129 : R.layout.widgets_full_sheet_recyclerview;
Steven Ng167f81b2021-02-23 10:48:46 +0000130 layoutInflater.inflate(contentLayoutRes, springLayout, true);
Steven Ng391404f2021-02-17 15:58:23 +0000131
Steven Ng167f81b2021-02-23 10:48:46 +0000132 RecyclerViewFastScroller fastScroller = findViewById(R.id.fast_scroller);
Steven Ng391404f2021-02-17 15:58:23 +0000133 if (mHasWorkProfile) {
134 mViewPager = findViewById(R.id.widgets_view_pager);
Steven Ng391404f2021-02-17 15:58:23 +0000135 mViewPager.initParentViews(this);
136 mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
137 mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.PRIMARY);
Steven Ng167f81b2021-02-23 10:48:46 +0000138 mTabsView = findViewById(R.id.tabs);
Steven Ng391404f2021-02-17 15:58:23 +0000139 findViewById(R.id.tab_personal)
140 .setOnClickListener((View view) -> mViewPager.snapToPage(0));
141 findViewById(R.id.tab_work)
142 .setOnClickListener((View view) -> mViewPager.snapToPage(1));
Steven Ng167f81b2021-02-23 10:48:46 +0000143 fastScroller.setIsRecyclerViewFirstChildInParent(false);
Steven Ng391404f2021-02-17 15:58:23 +0000144 springLayout.addSpringView(R.id.primary_widgets_list_view);
145 springLayout.addSpringView(R.id.work_widgets_list_view);
146 } else {
147 mViewPager = null;
148 springLayout.addSpringView(R.id.primary_widgets_list_view);
149 }
150
Steven Ng167f81b2021-02-23 10:48:46 +0000151 layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout,
152 true);
153 springLayout.addSpringView(R.id.search_and_recommendations_container);
154
155 mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder(
156 findViewById(R.id.search_and_recommendations_container));
157 mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
158 mHasWorkProfile,
Steven Ng4235fc12021-03-19 20:13:16 +0000159 mTabsHeight,
Steven Ng167f81b2021-02-23 10:48:46 +0000160 mSearchAndRecommendationViewHolder,
161 findViewById(R.id.primary_widgets_list_view),
162 mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
Alina Zaidi334e6592021-03-11 16:10:27 +0000163 findViewById(R.id.search_widgets_list_view),
Steven Ng167f81b2021-02-23 10:48:46 +0000164 mTabsView,
165 mViewPager);
166 fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
167
Steven Ngd73d6e52021-03-09 22:44:04 +0000168 mNoWidgetsView = findViewById(R.id.no_widgets_text);
169
Steven Ng4235fc12021-03-19 20:13:16 +0000170 onRecommendedWidgetsBound();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700171 onWidgetsBound();
Alina Zaidi334e6592021-03-11 16:10:27 +0000172
173 mSearchAndRecommendationViewHolder.mSearchBar.initialize(
174 mLauncher.getPopupDataProvider().getAllWidgets(), /* searchModeListener= */ this);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700175 }
176
Steven Ng391404f2021-02-17 15:58:23 +0000177 @Override
178 public void onActivePageChanged(int currentActivePage) {
Steven Ngd73d6e52021-03-09 22:44:04 +0000179 AdapterHolder currentAdapterHolder = mAdapters.get(currentActivePage);
Alina Zaidi334e6592021-03-11 16:10:27 +0000180 WidgetsRecyclerView currentRecyclerView =
181 mAdapters.get(currentActivePage).mWidgetsRecyclerView;
Steven Ng167f81b2021-02-23 10:48:46 +0000182
Steven Ngd73d6e52021-03-09 22:44:04 +0000183 updateNoWidgetsView(currentAdapterHolder);
Alina Zaidi334e6592021-03-11 16:10:27 +0000184 attachScrollbarToRecyclerView(currentRecyclerView);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000185 resetExpandedHeaders();
Alina Zaidi334e6592021-03-11 16:10:27 +0000186 }
187
188 private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) {
189 recyclerView.bindFastScrollbar();
190 mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView);
Steven Ng167f81b2021-02-23 10:48:46 +0000191 reset();
192 }
193
Steven Ngd73d6e52021-03-09 22:44:04 +0000194 private void updateNoWidgetsView(AdapterHolder adapterHolder) {
195 boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.getItemCount() > 0;
196 adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
197
198 // Always resets the text in case this is updated by search.
199 mNoWidgetsView.setText(R.string.no_widgets_available);
200 mNoWidgetsView.setVisibility(isWidgetAvailable ? GONE : VISIBLE);
201 }
202
Alina Zaidi2e732e92021-03-12 17:39:38 +0000203 private void updateNoSearchResultsView(boolean isVisible) {
204 mNoWidgetsView.setVisibility(isVisible ? VISIBLE : GONE);
205 if (isVisible) {
206 mNoWidgetsView.setText(R.string.no_search_results);
207 }
208 }
209
Steven Ng167f81b2021-02-23 10:48:46 +0000210 private void reset() {
211 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
212 if (mHasWorkProfile) {
213 mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
214 }
Alina Zaidi334e6592021-03-11 16:10:27 +0000215 mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
Steven Ng7f7b2f72021-03-03 14:51:36 +0000216 mSearchAndRecommendationsScrollController.reset();
Steven Ng391404f2021-02-17 15:58:23 +0000217 }
218
Sunny Goyal8b37c572020-03-31 12:12:14 -0700219 @VisibleForTesting
220 public WidgetsRecyclerView getRecyclerView() {
Alina Zaidi334e6592021-03-11 16:10:27 +0000221 if (mIsInSearchMode) {
222 return mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView;
223 }
Steven Ng391404f2021-02-17 15:58:23 +0000224 if (!mHasWorkProfile || mViewPager.getCurrentPage() == AdapterHolder.PRIMARY) {
225 return mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
226 }
227 return mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView;
Sunny Goyal8b37c572020-03-31 12:12:14 -0700228 }
229
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700230 @Override
Sunny Goyalde753212018-05-15 13:55:57 -0700231 protected Pair<View, String> getAccessibilityTarget() {
Steven Ng391404f2021-02-17 15:58:23 +0000232 return Pair.create(getRecyclerView(), getContext().getString(
Sunny Goyalde753212018-05-15 13:55:57 -0700233 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
234 }
235
236 @Override
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700237 protected void onAttachedToWindow() {
238 super.onAttachedToWindow();
239 mLauncher.getAppWidgetHost().addProviderChangeListener(this);
240 notifyWidgetProvidersChanged();
241 }
242
243 @Override
244 protected void onDetachedFromWindow() {
245 super.onDetachedFromWindow();
246 mLauncher.getAppWidgetHost().removeProviderChangeListener(this);
247 }
248
249 @Override
250 public void setInsets(Rect insets) {
251 mInsets.set(insets);
252
Steven Ng391404f2021-02-17 15:58:23 +0000253 setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, insets.bottom);
Alina Zaidic4f3f492021-03-19 14:22:43 +0000254 setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, insets.bottom);
Steven Ng391404f2021-02-17 15:58:23 +0000255 if (mHasWorkProfile) {
256 setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
257 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700258 if (insets.bottom > 0) {
259 setupNavBarColor();
Sunny Goyalf8d56fc2018-01-31 15:18:11 -0800260 } else {
261 clearNavBarColor();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700262 }
Sunny Goyal3661bfa2018-03-01 16:29:15 -0800263
264 ((TopRoundedCornerView) mContent).setNavBarScrimHeight(mInsets.bottom);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700265 requestLayout();
266 }
267
Steven Ng391404f2021-02-17 15:58:23 +0000268 private void setBottomPadding(RecyclerView recyclerView, int bottomPadding) {
269 recyclerView.setPadding(
270 recyclerView.getPaddingLeft(),
271 recyclerView.getPaddingTop(),
272 recyclerView.getPaddingRight(),
273 bottomPadding);
274 }
275
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700276 @Override
277 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Steven Ng4235fc12021-03-19 20:13:16 +0000278 doMeasure(widthMeasureSpec, heightMeasureSpec);
279
280 if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
281 doMeasure(widthMeasureSpec, heightMeasureSpec);
282 }
283
284 if (updateMaxSpansPerRow()) {
285 doMeasure(widthMeasureSpec, heightMeasureSpec);
286
287 if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
288 doMeasure(widthMeasureSpec, heightMeasureSpec);
289 }
290 }
291 }
292
293 private void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Steven Nge8d92342021-02-19 21:29:18 +0000294 DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700295 int widthUsed;
296 if (mInsets.bottom > 0) {
Sunny Goyal786940a2020-06-02 02:31:31 -0700297 widthUsed = mInsets.left + mInsets.right;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700298 } else {
Steven Nge8d92342021-02-19 21:29:18 +0000299 Rect padding = deviceProfile.workspacePadding;
Sunny Goyal07b69292018-01-08 14:19:34 -0800300 widthUsed = Math.max(padding.left + padding.right,
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700301 2 * (mInsets.left + mInsets.right));
302 }
303
Steven Nge8d92342021-02-19 21:29:18 +0000304 int heightUsed = mInsets.top + deviceProfile.edgeMarginPx;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700305 measureChildWithMargins(mContent, widthMeasureSpec,
306 widthUsed, heightMeasureSpec, heightUsed);
Sunny Goyal6639a5d2018-02-28 15:09:36 -0800307 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
308 MeasureSpec.getSize(heightMeasureSpec));
Steven Ng4235fc12021-03-19 20:13:16 +0000309 }
Steven Nge8d92342021-02-19 21:29:18 +0000310
Steven Ng4235fc12021-03-19 20:13:16 +0000311 /** Returns {@code true} if the max spans have been updated. */
312 private boolean updateMaxSpansPerRow() {
313 if (getMeasuredWidth() == 0) return false;
314
315 int previousMaxSpansPerRow = mMaxSpansPerRow;
316 mMaxSpansPerRow = getMeasuredWidth()
317 / (mLauncher.getDeviceProfile().cellWidthPx + mWidgetCellHorizontalPadding);
318
319 if (previousMaxSpansPerRow != mMaxSpansPerRow) {
320 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
321 mMaxSpansPerRow);
322 mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
323 mMaxSpansPerRow);
324 if (mHasWorkProfile) {
325 mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
326 mMaxSpansPerRow);
327 }
328 onRecommendedWidgetsBound();
329 return true;
Steven Nge8d92342021-02-19 21:29:18 +0000330 }
Steven Ng4235fc12021-03-19 20:13:16 +0000331 return false;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700332 }
333
334 @Override
335 protected void onLayout(boolean changed, int l, int t, int r, int b) {
336 int width = r - l;
337 int height = b - t;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700338
339 // Content is laid out as center bottom aligned
340 int contentWidth = mContent.getMeasuredWidth();
Sunny Goyal786940a2020-06-02 02:31:31 -0700341 int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700342 mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
343 contentLeft + contentWidth, height);
344
345 setTranslationShift(mTranslationShift);
346 }
347
348 @Override
349 public void notifyWidgetProvidersChanged() {
350 mLauncher.refreshAndBindWidgetsForPackageUser(null);
351 }
352
353 @Override
Sunny Goyal202ae0b2019-02-04 16:26:42 -0800354 public void onWidgetsBound() {
Steven Ng391404f2021-02-17 15:58:23 +0000355 List<WidgetsListBaseEntry> allWidgets = mLauncher.getPopupDataProvider().getAllWidgets();
356
357 AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY);
358 primaryUserAdapterHolder.setup(findViewById(R.id.primary_widgets_list_view));
Alina Zaidi334e6592021-03-11 16:10:27 +0000359 AdapterHolder searchAdapterHolder = mAdapters.get(AdapterHolder.SEARCH);
360 searchAdapterHolder.setup(findViewById(R.id.search_widgets_list_view));
Steven Ng391404f2021-02-17 15:58:23 +0000361 primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
Steven Ngd73d6e52021-03-09 22:44:04 +0000362 updateNoWidgetsView(primaryUserAdapterHolder);
363
Steven Ng391404f2021-02-17 15:58:23 +0000364 if (mHasWorkProfile) {
365 AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
366 workUserAdapterHolder.setup(findViewById(R.id.work_widgets_list_view));
367 workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
368 onActivePageChanged(mViewPager.getCurrentPage());
369 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700370 }
371
Alina Zaidi334e6592021-03-11 16:10:27 +0000372 @Override
373 public void enterSearchMode() {
374 if (mIsInSearchMode) return;
375 setViewVisibilityBasedOnSearch(/*isInSearchMode= */ true);
376 attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000377 resetExpandedHeaders();
Alina Zaidi334e6592021-03-11 16:10:27 +0000378 }
379
380 @Override
381 public void exitSearchMode() {
Alina Zaidi2e732e92021-03-12 17:39:38 +0000382 onSearchResults(new ArrayList<>());
Alina Zaidi334e6592021-03-11 16:10:27 +0000383 setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false);
384 if (mHasWorkProfile) {
385 mViewPager.snapToPage(AdapterHolder.PRIMARY);
386 }
387 attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
Steven Ng4235fc12021-03-19 20:13:16 +0000388
389 mSearchAndRecommendationsScrollController.updateMarginAndPadding();
Alina Zaidi334e6592021-03-11 16:10:27 +0000390 }
391
392 @Override
393 public void onSearchResults(List<WidgetsListBaseEntry> entries) {
394 mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setWidgetsOnSearch(entries);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000395 updateNoSearchResultsView(
396 mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.getItemCount() == 0);
Alina Zaidi334e6592021-03-11 16:10:27 +0000397 }
398
399 private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
400 mIsInSearchMode = isInSearchMode;
Steven Ng4235fc12021-03-19 20:13:16 +0000401 mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
402 .setVisibility(isInSearchMode ? GONE : VISIBLE);
Alina Zaidi334e6592021-03-11 16:10:27 +0000403 if (mHasWorkProfile) {
404 mViewPager.setVisibility(isInSearchMode ? GONE : VISIBLE);
405 mTabsView.setVisibility(isInSearchMode ? GONE : VISIBLE);
406 } else {
407 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView
408 .setVisibility(isInSearchMode ? GONE : VISIBLE);
409 }
410 mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView
411 .setVisibility(mIsInSearchMode ? VISIBLE : GONE);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000412 mNoWidgetsView.setVisibility(GONE);
413 }
414
415 private void resetExpandedHeaders() {
416 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.resetExpandedHeader();
417 mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader();
Alina Zaidi334e6592021-03-11 16:10:27 +0000418 }
419
Steven Ng4235fc12021-03-19 20:13:16 +0000420 @Override
421 public void onRecommendedWidgetsBound() {
422 List<WidgetItem> recommendedWidgets =
423 mLauncher.getPopupDataProvider().getRecommendedWidgets();
424 WidgetsRecommendationTableLayout table =
425 mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable;
426 if (recommendedWidgets.size() > 0) {
427 float maxTableHeight =
428 (mLauncher.getDeviceProfile().heightPx - mTabsHeight - getHeaderViewHeight())
429 * RECOMMENDATION_TABLE_HEIGHT_RATIO;
430 List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
431 WidgetsTableUtils.groupWidgetItemsIntoTable(recommendedWidgets,
432 mMaxSpansPerRow);
433 table.setRecommendedWidgets(recommendedWidgetsInTable, maxTableHeight);
434 } else {
435 table.setVisibility(GONE);
436 }
437 }
438
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700439 private void open(boolean animate) {
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700440 if (animate) {
Sunny Goyaldb6cdb02018-07-13 11:03:04 -0700441 if (getPopupContainer().getInsets().bottom > 0) {
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700442 mContent.setAlpha(0);
443 setTranslationShift(VERTICAL_START_POSITION);
444 }
445 mOpenCloseAnimator.setValues(
446 PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
447 mOpenCloseAnimator
448 .setDuration(DEFAULT_OPEN_DURATION)
449 .setInterpolator(AnimationUtils.loadInterpolator(
450 getContext(), android.R.interpolator.linear_out_slow_in));
451 mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
452 @Override
453 public void onAnimationEnd(Animator animation) {
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700454 mOpenCloseAnimator.removeListener(this);
455 }
456 });
Sunny Goyal6639a5d2018-02-28 15:09:36 -0800457 post(() -> {
Sunny Goyal6639a5d2018-02-28 15:09:36 -0800458 mOpenCloseAnimator.start();
459 mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700460 });
461 } else {
462 setTranslationShift(TRANSLATION_SHIFT_OPENED);
Sunny Goyalde753212018-05-15 13:55:57 -0700463 post(this::announceAccessibilityChanges);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700464 }
465 }
466
467 @Override
468 protected void handleClose(boolean animate) {
469 handleClose(animate, DEFAULT_OPEN_DURATION);
470 }
471
472 @Override
473 protected boolean isOfType(int type) {
474 return (type & TYPE_WIDGETS_FULL_SHEET) != 0;
475 }
476
477 @Override
478 public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
479 // Disable swipe down when recycler view is scrolling
480 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
481 mNoIntercept = false;
Steven Ng391404f2021-02-17 15:58:23 +0000482 RecyclerViewFastScroller scroller = getRecyclerView().getScrollbar();
Steven Ng2f5648a2021-02-08 17:18:25 +0000483 if (scroller.getThumbOffsetY() >= 0
484 && getPopupContainer().isEventOverView(scroller, ev)) {
Sunny Goyal3661bfa2018-03-01 16:29:15 -0800485 mNoIntercept = true;
Sunny Goyaldb6cdb02018-07-13 11:03:04 -0700486 } else if (getPopupContainer().isEventOverView(mContent, ev)) {
Steven Ng391404f2021-02-17 15:58:23 +0000487 mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer());
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700488 }
489 }
490 return super.onControllerInterceptTouchEvent(ev);
491 }
492
Steven Ng2f5648a2021-02-08 17:18:25 +0000493 /** Shows the {@link WidgetsFullSheet} on the launcher. */
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700494 public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
495 WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
496 .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
Sunny Goyal1642f712018-09-18 11:40:35 -0700497 sheet.attachToContainer();
Sunny Goyalde753212018-05-15 13:55:57 -0700498 sheet.mIsOpen = true;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700499 sheet.open(animate);
500 return sheet;
501 }
Sunny Goyalaeb16432017-10-16 11:46:41 -0700502
Steven Ng2f5648a2021-02-08 17:18:25 +0000503 /** Gets the {@link WidgetsRecyclerView} which shows all widgets in {@link WidgetsFullSheet}. */
Vadim Tryshev2ce6a132018-06-18 19:14:44 -0700504 @VisibleForTesting
505 public static WidgetsRecyclerView getWidgetsView(Launcher launcher) {
Steven Ng391404f2021-02-17 15:58:23 +0000506 return launcher.findViewById(R.id.primary_widgets_list_view);
Vadim Tryshev2ce6a132018-06-18 19:14:44 -0700507 }
508
Sunny Goyalaeb16432017-10-16 11:46:41 -0700509 @Override
Sunny Goyalf3ac7032020-03-13 13:01:33 -0700510 public void addHintCloseAnim(
511 float distanceToMove, Interpolator interpolator, PendingAnimation target) {
Steven Ng391404f2021-02-17 15:58:23 +0000512 target.setFloat(getRecyclerView(), VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
513 target.setViewAlpha(getRecyclerView(), 0.5f, interpolator);
Tony Wickham9791bd12019-04-05 13:52:35 -0700514 }
vadimt9f48a8e2019-12-11 11:52:37 -0800515
516 @Override
517 protected void onCloseComplete() {
518 super.onCloseComplete();
519 AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
520 }
Steven Ng391404f2021-02-17 15:58:23 +0000521
Steven Ng167f81b2021-02-23 10:48:46 +0000522 @Override
523 public int getHeaderViewHeight() {
Steven Ng53d13642021-03-10 22:40:55 +0000524 return measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mCollapseHandle)
525 + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mHeaderTitle)
Alina Zaidi16327522021-03-15 07:00:49 +0000526 + measureHeightWithVerticalMargins(
527 (View) mSearchAndRecommendationViewHolder.mSearchBar);
Steven Ng7f7b2f72021-03-03 14:51:36 +0000528 }
529
530 /** private the height, in pixel, + the vertical margins of a given view. */
531 private static int measureHeightWithVerticalMargins(View view) {
Steven Ng53d13642021-03-10 22:40:55 +0000532 if (view.getVisibility() != VISIBLE) {
533 return 0;
534 }
Steven Ng7f7b2f72021-03-03 14:51:36 +0000535 MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
536 return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
537 + marginLayoutParams.topMargin;
Steven Ng167f81b2021-02-23 10:48:46 +0000538 }
539
Alina Zaidic4f3f492021-03-19 14:22:43 +0000540 @Override
541 protected void onConfigurationChanged(Configuration newConfig) {
542 super.onConfigurationChanged(newConfig);
543 if (mIsInSearchMode) {
544 mSearchAndRecommendationViewHolder.mSearchBar.reset();
545 }
546 }
547
548 @Override
549 public boolean onBackPressed() {
550 if (mIsInSearchMode) {
551 mSearchAndRecommendationViewHolder.mSearchBar.reset();
552 return true;
553 }
554 return super.onBackPressed();
555 }
556
Steven Ng391404f2021-02-17 15:58:23 +0000557 /** A holder class for holding adapters & their corresponding recycler view. */
558 private final class AdapterHolder {
559 static final int PRIMARY = 0;
560 static final int WORK = 1;
Alina Zaidi334e6592021-03-11 16:10:27 +0000561 static final int SEARCH = 2;
Steven Ng391404f2021-02-17 15:58:23 +0000562
563 private final int mAdapterType;
564 private final WidgetsListAdapter mWidgetsListAdapter;
565
566 private WidgetsRecyclerView mWidgetsRecyclerView;
567
568 AdapterHolder(int adapterType) {
569 mAdapterType = adapterType;
570
571 Context context = getContext();
572 LauncherAppState apps = LauncherAppState.getInstance(context);
573 mWidgetsListAdapter = new WidgetsListAdapter(
574 context,
575 LayoutInflater.from(context),
576 apps.getWidgetCache(),
577 apps.getIconCache(),
578 /* iconClickListener= */ WidgetsFullSheet.this,
579 /* iconLongClickListener= */ WidgetsFullSheet.this);
Alina Zaidi334e6592021-03-11 16:10:27 +0000580 switch (mAdapterType) {
581 case PRIMARY:
582 mWidgetsListAdapter.setFilter(mPrimaryWidgetsFilter);
583 break;
584 case WORK:
585 mWidgetsListAdapter.setFilter(mWorkWidgetsFilter);
586 break;
587 default:
588 break;
589 }
Steven Ng391404f2021-02-17 15:58:23 +0000590 }
591
592 void setup(WidgetsRecyclerView recyclerView) {
593 mWidgetsRecyclerView = recyclerView;
594 mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
Steven Ng167f81b2021-02-23 10:48:46 +0000595 mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
Steven Ng391404f2021-02-17 15:58:23 +0000596 mWidgetsRecyclerView.setEdgeEffectFactory(
597 ((TopRoundedCornerView) mContent).createEdgeEffectFactory());
598 mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
Steven Ng4235fc12021-03-19 20:13:16 +0000599 mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
Steven Ng391404f2021-02-17 15:58:23 +0000600 }
601 }
Steven Ng167f81b2021-02-23 10:48:46 +0000602
603 final class SearchAndRecommendationViewHolder {
Steven Ng4235fc12021-03-19 20:13:16 +0000604 final ViewGroup mContainer;
Steven Ng167f81b2021-02-23 10:48:46 +0000605 final View mCollapseHandle;
Alina Zaidi334e6592021-03-11 16:10:27 +0000606 final WidgetsSearchBar mSearchBar;
Steven Ng167f81b2021-02-23 10:48:46 +0000607 final TextView mHeaderTitle;
Steven Ng4235fc12021-03-19 20:13:16 +0000608 final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
Steven Ng167f81b2021-02-23 10:48:46 +0000609
Steven Ng4235fc12021-03-19 20:13:16 +0000610 SearchAndRecommendationViewHolder(ViewGroup searchAndRecommendationContainer) {
Steven Ng167f81b2021-02-23 10:48:46 +0000611 mContainer = searchAndRecommendationContainer;
612 mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
613 mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
614 mHeaderTitle = mContainer.findViewById(R.id.title);
Steven Ng4235fc12021-03-19 20:13:16 +0000615 mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
616 mRecommendedWidgetsTable.setWidgetCellOnTouchListener((view, event) -> {
617 getRecyclerView().onTouchEvent(event);
618 return false;
619 });
620 mRecommendedWidgetsTable.setWidgetCellLongClickListener(WidgetsFullSheet.this);
621 mRecommendedWidgetsTable.setWidgetCellOnClickListener(WidgetsFullSheet.this);
Steven Ng167f81b2021-02-23 10:48:46 +0000622 }
623 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700624}