blob: f6ff87b5046160d79a88a25258856936b5be81a8 [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;
Steven Ng391404f2021-02-17 15:58:23 +000044import androidx.recyclerview.widget.RecyclerView;
Tony Wickham9791bd12019-04-05 13:52:35 -070045
Steven Nge8d92342021-02-19 21:29:18 +000046import com.android.launcher3.DeviceProfile;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070047import com.android.launcher3.Insettable;
48import com.android.launcher3.Launcher;
49import com.android.launcher3.LauncherAppState;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070050import com.android.launcher3.R;
Sunny Goyalf3ac7032020-03-13 13:01:33 -070051import com.android.launcher3.anim.PendingAnimation;
vadimt9f48a8e2019-12-11 11:52:37 -080052import com.android.launcher3.compat.AccessibilityManagerCompat;
Steven Ng4235fc12021-03-19 20:13:16 +000053import com.android.launcher3.model.WidgetItem;
Sunny Goyal3661bfa2018-03-01 16:29:15 -080054import com.android.launcher3.views.RecyclerViewFastScroller;
55import com.android.launcher3.views.TopRoundedCornerView;
Steven Ng2f5648a2021-02-08 17:18:25 +000056import com.android.launcher3.widget.BaseWidgetSheet;
Yogisha Dixit741fae92021-02-22 14:03:44 +000057import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
Steven Ng391404f2021-02-17 15:58:23 +000058import com.android.launcher3.widget.model.WidgetsListBaseEntry;
Alina Zaidi334e6592021-03-11 16:10:27 +000059import com.android.launcher3.widget.picker.search.SearchModeListener;
60import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
Alina Zaidi2cff1642021-03-24 09:52:45 +000061import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper;
Steven Ng4235fc12021-03-19 20:13:16 +000062import com.android.launcher3.widget.util.WidgetsTableUtils;
Steven Ng391404f2021-02-17 15:58:23 +000063import com.android.launcher3.workprofile.PersonalWorkPagedView;
64import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
65
Alina Zaidi2e732e92021-03-12 17:39:38 +000066import java.util.ArrayList;
Steven Ng391404f2021-02-17 15:58:23 +000067import java.util.List;
68import java.util.function.Predicate;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070069
70/**
71 * Popup for showing the full list of available widgets
72 */
73public class WidgetsFullSheet extends BaseWidgetSheet
Steven Ng167f81b2021-02-23 10:48:46 +000074 implements Insettable, ProviderChangedListener, OnActivePageChangedListener,
Alina Zaidi2cff1642021-03-24 09:52:45 +000075 WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener,
76 WidgetsSearchBarUIHelper {
Steven Ng4235fc12021-03-19 20:13:16 +000077 private static final String TAG = WidgetsFullSheet.class.getSimpleName();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070078
79 private static final long DEFAULT_OPEN_DURATION = 267;
80 private static final long FADE_IN_DURATION = 150;
81 private static final float VERTICAL_START_POSITION = 0.3f;
Steven Ng4235fc12021-03-19 20:13:16 +000082 // The widget recommendation table can easily take over the entire screen on devices with small
83 // resolution or landscape on phone. This ratio defines the max percentage of content area that
84 // the table can display.
85 private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070086
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070087 private final Rect mInsets = new Rect();
Steven Ng391404f2021-02-17 15:58:23 +000088 private final boolean mHasWorkProfile;
89 private final SparseArray<AdapterHolder> mAdapters = new SparseArray();
90 private final UserHandle mCurrentUser = Process.myUserHandle();
91 private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter = entry ->
92 mCurrentUser.equals(entry.mPkgItem.user);
93 private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
94 mPrimaryWidgetsFilter.negate();
Steven Ng4235fc12021-03-19 20:13:16 +000095 private final int mTabsHeight;
96 private final int mWidgetCellHorizontalPadding;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070097
Steven Ng391404f2021-02-17 15:58:23 +000098 @Nullable private PersonalWorkPagedView mViewPager;
Alina Zaidi334e6592021-03-11 16:10:27 +000099 private boolean mIsInSearchMode;
Steven Ng4235fc12021-03-19 20:13:16 +0000100 private int mMaxSpansPerRow = 4;
Steven Ng167f81b2021-02-23 10:48:46 +0000101 private View mTabsView;
Steven Ngd73d6e52021-03-09 22:44:04 +0000102 private TextView mNoWidgetsView;
Steven Ng167f81b2021-02-23 10:48:46 +0000103 private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
104 private SearchAndRecommendationsScrollController mSearchAndRecommendationsScrollController;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700105
106 public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
107 super(context, attrs, defStyleAttr);
Steven Ng391404f2021-02-17 15:58:23 +0000108 mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
109 mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
110 mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
Alina Zaidi334e6592021-03-11 16:10:27 +0000111 mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
Steven Ng4235fc12021-03-19 20:13:16 +0000112 mTabsHeight = mHasWorkProfile
113 ? getContext().getResources()
114 .getDimensionPixelSize(R.dimen.all_apps_header_tab_height)
115 : 0;
116 mWidgetCellHorizontalPadding = 2 * getResources().getDimensionPixelOffset(
117 R.dimen.widget_cell_horizontal_padding);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700118 }
119
120 public WidgetsFullSheet(Context context, AttributeSet attrs) {
121 this(context, attrs, 0);
122 }
123
124 @Override
125 protected void onFinishInflate() {
126 super.onFinishInflate();
127 mContent = findViewById(R.id.container);
Sunny Goyal016d7e92018-03-09 11:09:20 -0800128 TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
Steven Ng391404f2021-02-17 15:58:23 +0000129
130 LayoutInflater layoutInflater = LayoutInflater.from(getContext());
131 int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view
132 : R.layout.widgets_full_sheet_recyclerview;
Steven Ng167f81b2021-02-23 10:48:46 +0000133 layoutInflater.inflate(contentLayoutRes, springLayout, true);
Steven Ng391404f2021-02-17 15:58:23 +0000134
Steven Ng167f81b2021-02-23 10:48:46 +0000135 RecyclerViewFastScroller fastScroller = findViewById(R.id.fast_scroller);
Steven Ng391404f2021-02-17 15:58:23 +0000136 if (mHasWorkProfile) {
137 mViewPager = findViewById(R.id.widgets_view_pager);
Steven Ng391404f2021-02-17 15:58:23 +0000138 mViewPager.initParentViews(this);
139 mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
140 mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.PRIMARY);
Steven Ng167f81b2021-02-23 10:48:46 +0000141 mTabsView = findViewById(R.id.tabs);
Steven Ng391404f2021-02-17 15:58:23 +0000142 findViewById(R.id.tab_personal)
143 .setOnClickListener((View view) -> mViewPager.snapToPage(0));
144 findViewById(R.id.tab_work)
145 .setOnClickListener((View view) -> mViewPager.snapToPage(1));
Steven Ng167f81b2021-02-23 10:48:46 +0000146 fastScroller.setIsRecyclerViewFirstChildInParent(false);
Steven Ng391404f2021-02-17 15:58:23 +0000147 } else {
148 mViewPager = null;
Steven Ng391404f2021-02-17 15:58:23 +0000149 }
150
Steven Ng167f81b2021-02-23 10:48:46 +0000151 layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout,
152 true);
Steven Ng167f81b2021-02-23 10:48:46 +0000153 mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder(
154 findViewById(R.id.search_and_recommendations_container));
155 mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
156 mHasWorkProfile,
Steven Ng4235fc12021-03-19 20:13:16 +0000157 mTabsHeight,
Steven Ng167f81b2021-02-23 10:48:46 +0000158 mSearchAndRecommendationViewHolder,
159 findViewById(R.id.primary_widgets_list_view),
160 mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
Alina Zaidi334e6592021-03-11 16:10:27 +0000161 findViewById(R.id.search_widgets_list_view),
Steven Ng167f81b2021-02-23 10:48:46 +0000162 mTabsView,
163 mViewPager);
164 fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
165
Steven Ngd73d6e52021-03-09 22:44:04 +0000166 mNoWidgetsView = findViewById(R.id.no_widgets_text);
167
Steven Ng4235fc12021-03-19 20:13:16 +0000168 onRecommendedWidgetsBound();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700169 onWidgetsBound();
Alina Zaidi334e6592021-03-11 16:10:27 +0000170
171 mSearchAndRecommendationViewHolder.mSearchBar.initialize(
Sunny Goyalce6cc7e2021-03-30 16:51:08 -0700172 mLauncher.getPopupDataProvider(), /* searchModeListener= */ this);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700173 }
174
Steven Ng391404f2021-02-17 15:58:23 +0000175 @Override
176 public void onActivePageChanged(int currentActivePage) {
Steven Ngd73d6e52021-03-09 22:44:04 +0000177 AdapterHolder currentAdapterHolder = mAdapters.get(currentActivePage);
Alina Zaidi334e6592021-03-11 16:10:27 +0000178 WidgetsRecyclerView currentRecyclerView =
179 mAdapters.get(currentActivePage).mWidgetsRecyclerView;
Steven Ng167f81b2021-02-23 10:48:46 +0000180
Steven Ngd73d6e52021-03-09 22:44:04 +0000181 updateNoWidgetsView(currentAdapterHolder);
Alina Zaidi334e6592021-03-11 16:10:27 +0000182 attachScrollbarToRecyclerView(currentRecyclerView);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000183 resetExpandedHeaders();
Alina Zaidi334e6592021-03-11 16:10:27 +0000184 }
185
186 private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) {
187 recyclerView.bindFastScrollbar();
188 mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView);
Steven Ng167f81b2021-02-23 10:48:46 +0000189 reset();
190 }
191
Steven Ngd73d6e52021-03-09 22:44:04 +0000192 private void updateNoWidgetsView(AdapterHolder adapterHolder) {
193 boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.getItemCount() > 0;
194 adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
195
196 // Always resets the text in case this is updated by search.
197 mNoWidgetsView.setText(R.string.no_widgets_available);
198 mNoWidgetsView.setVisibility(isWidgetAvailable ? GONE : VISIBLE);
199 }
200
Alina Zaidi2e732e92021-03-12 17:39:38 +0000201 private void updateNoSearchResultsView(boolean isVisible) {
202 mNoWidgetsView.setVisibility(isVisible ? VISIBLE : GONE);
203 if (isVisible) {
204 mNoWidgetsView.setText(R.string.no_search_results);
205 }
206 }
207
Steven Ng167f81b2021-02-23 10:48:46 +0000208 private void reset() {
209 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
210 if (mHasWorkProfile) {
211 mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
212 }
Alina Zaidi334e6592021-03-11 16:10:27 +0000213 mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
Steven Ng7f7b2f72021-03-03 14:51:36 +0000214 mSearchAndRecommendationsScrollController.reset();
Steven Ng391404f2021-02-17 15:58:23 +0000215 }
216
Sunny Goyal8b37c572020-03-31 12:12:14 -0700217 @VisibleForTesting
218 public WidgetsRecyclerView getRecyclerView() {
Alina Zaidi334e6592021-03-11 16:10:27 +0000219 if (mIsInSearchMode) {
220 return mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView;
221 }
Steven Ng391404f2021-02-17 15:58:23 +0000222 if (!mHasWorkProfile || mViewPager.getCurrentPage() == AdapterHolder.PRIMARY) {
223 return mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
224 }
225 return mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView;
Sunny Goyal8b37c572020-03-31 12:12:14 -0700226 }
227
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700228 @Override
Sunny Goyalde753212018-05-15 13:55:57 -0700229 protected Pair<View, String> getAccessibilityTarget() {
Steven Ng391404f2021-02-17 15:58:23 +0000230 return Pair.create(getRecyclerView(), getContext().getString(
Sunny Goyalde753212018-05-15 13:55:57 -0700231 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
232 }
233
234 @Override
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700235 protected void onAttachedToWindow() {
236 super.onAttachedToWindow();
237 mLauncher.getAppWidgetHost().addProviderChangeListener(this);
238 notifyWidgetProvidersChanged();
239 }
240
241 @Override
242 protected void onDetachedFromWindow() {
243 super.onDetachedFromWindow();
244 mLauncher.getAppWidgetHost().removeProviderChangeListener(this);
245 }
246
247 @Override
248 public void setInsets(Rect insets) {
249 mInsets.set(insets);
250
Steven Ng391404f2021-02-17 15:58:23 +0000251 setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, insets.bottom);
Alina Zaidic4f3f492021-03-19 14:22:43 +0000252 setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, insets.bottom);
Steven Ng391404f2021-02-17 15:58:23 +0000253 if (mHasWorkProfile) {
254 setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
255 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700256 if (insets.bottom > 0) {
257 setupNavBarColor();
Sunny Goyalf8d56fc2018-01-31 15:18:11 -0800258 } else {
259 clearNavBarColor();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700260 }
Sunny Goyal3661bfa2018-03-01 16:29:15 -0800261
262 ((TopRoundedCornerView) mContent).setNavBarScrimHeight(mInsets.bottom);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700263 requestLayout();
264 }
265
Steven Ng391404f2021-02-17 15:58:23 +0000266 private void setBottomPadding(RecyclerView recyclerView, int bottomPadding) {
267 recyclerView.setPadding(
268 recyclerView.getPaddingLeft(),
269 recyclerView.getPaddingTop(),
270 recyclerView.getPaddingRight(),
271 bottomPadding);
272 }
273
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700274 @Override
275 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Steven Ng4235fc12021-03-19 20:13:16 +0000276 doMeasure(widthMeasureSpec, heightMeasureSpec);
277
278 if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
279 doMeasure(widthMeasureSpec, heightMeasureSpec);
280 }
281
282 if (updateMaxSpansPerRow()) {
283 doMeasure(widthMeasureSpec, heightMeasureSpec);
284
285 if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
286 doMeasure(widthMeasureSpec, heightMeasureSpec);
287 }
288 }
289 }
290
291 private void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Steven Nge8d92342021-02-19 21:29:18 +0000292 DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700293 int widthUsed;
294 if (mInsets.bottom > 0) {
Sunny Goyal786940a2020-06-02 02:31:31 -0700295 widthUsed = mInsets.left + mInsets.right;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700296 } else {
Steven Nge8d92342021-02-19 21:29:18 +0000297 Rect padding = deviceProfile.workspacePadding;
Sunny Goyal07b69292018-01-08 14:19:34 -0800298 widthUsed = Math.max(padding.left + padding.right,
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700299 2 * (mInsets.left + mInsets.right));
300 }
301
Steven Nge8d92342021-02-19 21:29:18 +0000302 int heightUsed = mInsets.top + deviceProfile.edgeMarginPx;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700303 measureChildWithMargins(mContent, widthMeasureSpec,
304 widthUsed, heightMeasureSpec, heightUsed);
Sunny Goyal6639a5d2018-02-28 15:09:36 -0800305 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
306 MeasureSpec.getSize(heightMeasureSpec));
Steven Ng4235fc12021-03-19 20:13:16 +0000307 }
Steven Nge8d92342021-02-19 21:29:18 +0000308
Steven Ng4235fc12021-03-19 20:13:16 +0000309 /** Returns {@code true} if the max spans have been updated. */
310 private boolean updateMaxSpansPerRow() {
311 if (getMeasuredWidth() == 0) return false;
312
313 int previousMaxSpansPerRow = mMaxSpansPerRow;
314 mMaxSpansPerRow = getMeasuredWidth()
315 / (mLauncher.getDeviceProfile().cellWidthPx + mWidgetCellHorizontalPadding);
316
317 if (previousMaxSpansPerRow != mMaxSpansPerRow) {
318 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
319 mMaxSpansPerRow);
320 mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
321 mMaxSpansPerRow);
322 if (mHasWorkProfile) {
323 mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
324 mMaxSpansPerRow);
325 }
326 onRecommendedWidgetsBound();
327 return true;
Steven Nge8d92342021-02-19 21:29:18 +0000328 }
Steven Ng4235fc12021-03-19 20:13:16 +0000329 return false;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700330 }
331
332 @Override
333 protected void onLayout(boolean changed, int l, int t, int r, int b) {
334 int width = r - l;
335 int height = b - t;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700336
337 // Content is laid out as center bottom aligned
338 int contentWidth = mContent.getMeasuredWidth();
Sunny Goyal786940a2020-06-02 02:31:31 -0700339 int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700340 mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
341 contentLeft + contentWidth, height);
342
343 setTranslationShift(mTranslationShift);
344 }
345
346 @Override
347 public void notifyWidgetProvidersChanged() {
348 mLauncher.refreshAndBindWidgetsForPackageUser(null);
349 }
350
351 @Override
Sunny Goyal202ae0b2019-02-04 16:26:42 -0800352 public void onWidgetsBound() {
Steven Ng391404f2021-02-17 15:58:23 +0000353 List<WidgetsListBaseEntry> allWidgets = mLauncher.getPopupDataProvider().getAllWidgets();
354
355 AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY);
356 primaryUserAdapterHolder.setup(findViewById(R.id.primary_widgets_list_view));
Alina Zaidi334e6592021-03-11 16:10:27 +0000357 AdapterHolder searchAdapterHolder = mAdapters.get(AdapterHolder.SEARCH);
358 searchAdapterHolder.setup(findViewById(R.id.search_widgets_list_view));
Steven Ng391404f2021-02-17 15:58:23 +0000359 primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
Steven Ngd73d6e52021-03-09 22:44:04 +0000360 updateNoWidgetsView(primaryUserAdapterHolder);
361
Steven Ng391404f2021-02-17 15:58:23 +0000362 if (mHasWorkProfile) {
363 AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
364 workUserAdapterHolder.setup(findViewById(R.id.work_widgets_list_view));
365 workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
366 onActivePageChanged(mViewPager.getCurrentPage());
367 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700368 }
369
Alina Zaidi334e6592021-03-11 16:10:27 +0000370 @Override
371 public void enterSearchMode() {
372 if (mIsInSearchMode) return;
373 setViewVisibilityBasedOnSearch(/*isInSearchMode= */ true);
374 attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000375 resetExpandedHeaders();
Alina Zaidi334e6592021-03-11 16:10:27 +0000376 }
377
378 @Override
379 public void exitSearchMode() {
Alina Zaidi2e732e92021-03-12 17:39:38 +0000380 onSearchResults(new ArrayList<>());
Alina Zaidi334e6592021-03-11 16:10:27 +0000381 setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false);
382 if (mHasWorkProfile) {
383 mViewPager.snapToPage(AdapterHolder.PRIMARY);
384 }
385 attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
Steven Ng4235fc12021-03-19 20:13:16 +0000386
387 mSearchAndRecommendationsScrollController.updateMarginAndPadding();
Alina Zaidi334e6592021-03-11 16:10:27 +0000388 }
389
390 @Override
391 public void onSearchResults(List<WidgetsListBaseEntry> entries) {
392 mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setWidgetsOnSearch(entries);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000393 updateNoSearchResultsView(
394 mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.getItemCount() == 0);
Alina Zaidi1ccd7ef2021-04-13 10:41:18 +0100395 mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
Alina Zaidi334e6592021-03-11 16:10:27 +0000396 }
397
398 private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
399 mIsInSearchMode = isInSearchMode;
Steven Ng4235fc12021-03-19 20:13:16 +0000400 mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
401 .setVisibility(isInSearchMode ? GONE : VISIBLE);
Alina Zaidi334e6592021-03-11 16:10:27 +0000402 if (mHasWorkProfile) {
403 mViewPager.setVisibility(isInSearchMode ? GONE : VISIBLE);
404 mTabsView.setVisibility(isInSearchMode ? GONE : VISIBLE);
405 } else {
406 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView
407 .setVisibility(isInSearchMode ? GONE : VISIBLE);
408 }
409 mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView
410 .setVisibility(mIsInSearchMode ? VISIBLE : GONE);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000411 mNoWidgetsView.setVisibility(GONE);
412 }
413
414 private void resetExpandedHeaders() {
415 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.resetExpandedHeader();
416 mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader();
Alina Zaidi334e6592021-03-11 16:10:27 +0000417 }
418
Steven Ng4235fc12021-03-19 20:13:16 +0000419 @Override
420 public void onRecommendedWidgetsBound() {
421 List<WidgetItem> recommendedWidgets =
422 mLauncher.getPopupDataProvider().getRecommendedWidgets();
423 WidgetsRecommendationTableLayout table =
424 mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable;
425 if (recommendedWidgets.size() > 0) {
426 float maxTableHeight =
427 (mLauncher.getDeviceProfile().heightPx - mTabsHeight - getHeaderViewHeight())
428 * RECOMMENDATION_TABLE_HEIGHT_RATIO;
429 List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
430 WidgetsTableUtils.groupWidgetItemsIntoTable(recommendedWidgets,
431 mMaxSpansPerRow);
432 table.setRecommendedWidgets(recommendedWidgetsInTable, maxTableHeight);
433 } else {
434 table.setVisibility(GONE);
435 }
436 }
437
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700438 private void open(boolean animate) {
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700439 if (animate) {
Sunny Goyaldb6cdb02018-07-13 11:03:04 -0700440 if (getPopupContainer().getInsets().bottom > 0) {
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700441 mContent.setAlpha(0);
442 setTranslationShift(VERTICAL_START_POSITION);
443 }
444 mOpenCloseAnimator.setValues(
445 PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
446 mOpenCloseAnimator
447 .setDuration(DEFAULT_OPEN_DURATION)
448 .setInterpolator(AnimationUtils.loadInterpolator(
449 getContext(), android.R.interpolator.linear_out_slow_in));
450 mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
451 @Override
452 public void onAnimationEnd(Animator animation) {
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700453 mOpenCloseAnimator.removeListener(this);
454 }
455 });
Sunny Goyal6639a5d2018-02-28 15:09:36 -0800456 post(() -> {
Sunny Goyal6639a5d2018-02-28 15:09:36 -0800457 mOpenCloseAnimator.start();
458 mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700459 });
460 } else {
461 setTranslationShift(TRANSLATION_SHIFT_OPENED);
Sunny Goyalde753212018-05-15 13:55:57 -0700462 post(this::announceAccessibilityChanges);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700463 }
464 }
465
466 @Override
467 protected void handleClose(boolean animate) {
468 handleClose(animate, DEFAULT_OPEN_DURATION);
469 }
470
471 @Override
472 protected boolean isOfType(int type) {
473 return (type & TYPE_WIDGETS_FULL_SHEET) != 0;
474 }
475
476 @Override
477 public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
478 // Disable swipe down when recycler view is scrolling
479 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
480 mNoIntercept = false;
Steven Ng391404f2021-02-17 15:58:23 +0000481 RecyclerViewFastScroller scroller = getRecyclerView().getScrollbar();
Steven Ng2f5648a2021-02-08 17:18:25 +0000482 if (scroller.getThumbOffsetY() >= 0
483 && getPopupContainer().isEventOverView(scroller, ev)) {
Sunny Goyal3661bfa2018-03-01 16:29:15 -0800484 mNoIntercept = true;
Sunny Goyaldb6cdb02018-07-13 11:03:04 -0700485 } else if (getPopupContainer().isEventOverView(mContent, ev)) {
Steven Ng391404f2021-02-17 15:58:23 +0000486 mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer());
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700487 }
488 }
489 return super.onControllerInterceptTouchEvent(ev);
490 }
491
Steven Ng2f5648a2021-02-08 17:18:25 +0000492 /** Shows the {@link WidgetsFullSheet} on the launcher. */
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700493 public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
494 WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
495 .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
Sunny Goyal1642f712018-09-18 11:40:35 -0700496 sheet.attachToContainer();
Sunny Goyalde753212018-05-15 13:55:57 -0700497 sheet.mIsOpen = true;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700498 sheet.open(animate);
499 return sheet;
500 }
Sunny Goyalaeb16432017-10-16 11:46:41 -0700501
Steven Ng2f5648a2021-02-08 17:18:25 +0000502 /** Gets the {@link WidgetsRecyclerView} which shows all widgets in {@link WidgetsFullSheet}. */
Vadim Tryshev2ce6a132018-06-18 19:14:44 -0700503 @VisibleForTesting
504 public static WidgetsRecyclerView getWidgetsView(Launcher launcher) {
Steven Ng391404f2021-02-17 15:58:23 +0000505 return launcher.findViewById(R.id.primary_widgets_list_view);
Vadim Tryshev2ce6a132018-06-18 19:14:44 -0700506 }
507
Sunny Goyalaeb16432017-10-16 11:46:41 -0700508 @Override
Sunny Goyalf3ac7032020-03-13 13:01:33 -0700509 public void addHintCloseAnim(
510 float distanceToMove, Interpolator interpolator, PendingAnimation target) {
Steven Ng391404f2021-02-17 15:58:23 +0000511 target.setFloat(getRecyclerView(), VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
512 target.setViewAlpha(getRecyclerView(), 0.5f, interpolator);
Tony Wickham9791bd12019-04-05 13:52:35 -0700513 }
vadimt9f48a8e2019-12-11 11:52:37 -0800514
515 @Override
516 protected void onCloseComplete() {
517 super.onCloseComplete();
518 AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
519 }
Steven Ng391404f2021-02-17 15:58:23 +0000520
Steven Ng167f81b2021-02-23 10:48:46 +0000521 @Override
522 public int getHeaderViewHeight() {
Steven Ng53d13642021-03-10 22:40:55 +0000523 return measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mCollapseHandle)
524 + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mHeaderTitle)
Alina Zaidi16327522021-03-15 07:00:49 +0000525 + measureHeightWithVerticalMargins(
526 (View) mSearchAndRecommendationViewHolder.mSearchBar);
Steven Ng7f7b2f72021-03-03 14:51:36 +0000527 }
528
529 /** private the height, in pixel, + the vertical margins of a given view. */
530 private static int measureHeightWithVerticalMargins(View view) {
Steven Ng53d13642021-03-10 22:40:55 +0000531 if (view.getVisibility() != VISIBLE) {
532 return 0;
533 }
Steven Ng7f7b2f72021-03-03 14:51:36 +0000534 MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
535 return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
536 + marginLayoutParams.topMargin;
Steven Ng167f81b2021-02-23 10:48:46 +0000537 }
538
Alina Zaidic4f3f492021-03-19 14:22:43 +0000539 @Override
540 protected void onConfigurationChanged(Configuration newConfig) {
541 super.onConfigurationChanged(newConfig);
542 if (mIsInSearchMode) {
543 mSearchAndRecommendationViewHolder.mSearchBar.reset();
544 }
545 }
546
547 @Override
548 public boolean onBackPressed() {
549 if (mIsInSearchMode) {
550 mSearchAndRecommendationViewHolder.mSearchBar.reset();
551 return true;
552 }
553 return super.onBackPressed();
554 }
555
Alina Zaidi76060eb2021-03-22 12:25:37 +0000556 @Override
557 public void onDragStart(boolean start, float startDisplacement) {
558 super.onDragStart(start, startDisplacement);
559 getWindowInsetsController().hide(WindowInsets.Type.ime());
560 }
561
Alina Zaidi2cff1642021-03-24 09:52:45 +0000562 @Override
563 public void clearSearchBarFocus() {
564 mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus();
565 }
566
Steven Ng391404f2021-02-17 15:58:23 +0000567 /** A holder class for holding adapters & their corresponding recycler view. */
568 private final class AdapterHolder {
569 static final int PRIMARY = 0;
570 static final int WORK = 1;
Alina Zaidi334e6592021-03-11 16:10:27 +0000571 static final int SEARCH = 2;
Steven Ng391404f2021-02-17 15:58:23 +0000572
573 private final int mAdapterType;
574 private final WidgetsListAdapter mWidgetsListAdapter;
575
576 private WidgetsRecyclerView mWidgetsRecyclerView;
577
578 AdapterHolder(int adapterType) {
579 mAdapterType = adapterType;
580
581 Context context = getContext();
582 LauncherAppState apps = LauncherAppState.getInstance(context);
583 mWidgetsListAdapter = new WidgetsListAdapter(
584 context,
585 LayoutInflater.from(context),
586 apps.getWidgetCache(),
587 apps.getIconCache(),
588 /* iconClickListener= */ WidgetsFullSheet.this,
Alina Zaidi2cff1642021-03-24 09:52:45 +0000589 /* iconLongClickListener= */ WidgetsFullSheet.this,
590 /* WidgetsSearchBarUIHelper= */
591 mAdapterType == SEARCH ? WidgetsFullSheet.this : null);
Steven Nge85b5562021-03-19 23:52:12 +0000592 mWidgetsListAdapter.setHasStableIds(true);
Alina Zaidi334e6592021-03-11 16:10:27 +0000593 switch (mAdapterType) {
594 case PRIMARY:
595 mWidgetsListAdapter.setFilter(mPrimaryWidgetsFilter);
596 break;
597 case WORK:
598 mWidgetsListAdapter.setFilter(mWorkWidgetsFilter);
599 break;
600 default:
601 break;
602 }
Steven Ng391404f2021-02-17 15:58:23 +0000603 }
604
605 void setup(WidgetsRecyclerView recyclerView) {
606 mWidgetsRecyclerView = recyclerView;
607 mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
Steven Nge85b5562021-03-19 23:52:12 +0000608 // Disables animation because it disrupts the item focus upon adapter item change.
609 mWidgetsRecyclerView.setItemAnimator(null);
Steven Ng167f81b2021-02-23 10:48:46 +0000610 mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
Steven Ng391404f2021-02-17 15:58:23 +0000611 mWidgetsRecyclerView.setEdgeEffectFactory(
612 ((TopRoundedCornerView) mContent).createEdgeEffectFactory());
613 mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
Steven Ng4235fc12021-03-19 20:13:16 +0000614 mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
Steven Ng391404f2021-02-17 15:58:23 +0000615 }
616 }
Steven Ng167f81b2021-02-23 10:48:46 +0000617
618 final class SearchAndRecommendationViewHolder {
Steven Ng4235fc12021-03-19 20:13:16 +0000619 final ViewGroup mContainer;
Steven Ng167f81b2021-02-23 10:48:46 +0000620 final View mCollapseHandle;
Alina Zaidi334e6592021-03-11 16:10:27 +0000621 final WidgetsSearchBar mSearchBar;
Steven Ng167f81b2021-02-23 10:48:46 +0000622 final TextView mHeaderTitle;
Steven Ng4235fc12021-03-19 20:13:16 +0000623 final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
Steven Ng167f81b2021-02-23 10:48:46 +0000624
Steven Ng4235fc12021-03-19 20:13:16 +0000625 SearchAndRecommendationViewHolder(ViewGroup searchAndRecommendationContainer) {
Steven Ng167f81b2021-02-23 10:48:46 +0000626 mContainer = searchAndRecommendationContainer;
627 mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
628 mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
629 mHeaderTitle = mContainer.findViewById(R.id.title);
Steven Ng4235fc12021-03-19 20:13:16 +0000630 mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
631 mRecommendedWidgetsTable.setWidgetCellOnTouchListener((view, event) -> {
632 getRecyclerView().onTouchEvent(event);
633 return false;
634 });
635 mRecommendedWidgetsTable.setWidgetCellLongClickListener(WidgetsFullSheet.this);
636 mRecommendedWidgetsTable.setWidgetCellOnClickListener(WidgetsFullSheet.this);
Steven Ng167f81b2021-02-23 10:48:46 +0000637 }
638 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700639}