blob: 55432568971b133bad1de5d17af90b7a870fb681 [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;
Steven Ng29f6b0b2021-04-16 17:11:47 +010031import android.util.Log;
Sunny Goyalde753212018-05-15 13:55:57 -070032import android.util.Pair;
Steven Ng391404f2021-02-17 15:58:23 +000033import android.util.SparseArray;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070034import android.view.LayoutInflater;
35import android.view.MotionEvent;
Sunny Goyalde753212018-05-15 13:55:57 -070036import android.view.View;
Steven Ng4235fc12021-03-19 20:13:16 +000037import android.view.ViewGroup;
Alina Zaidi76060eb2021-03-22 12:25:37 +000038import android.view.WindowInsets;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070039import android.view.animation.AnimationUtils;
Sunny Goyalf3ac7032020-03-13 13:01:33 -070040import android.view.animation.Interpolator;
Steven Ng167f81b2021-02-23 10:48:46 +000041import android.widget.TextView;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070042
Steven Ng391404f2021-02-17 15:58:23 +000043import androidx.annotation.Nullable;
Tony Wickham9791bd12019-04-05 13:52:35 -070044import androidx.annotation.VisibleForTesting;
Alina Zaidi120d6472021-04-09 09:58:53 +010045import androidx.core.view.ViewCompat;
Steven Ng391404f2021-02-17 15:58:23 +000046import androidx.recyclerview.widget.RecyclerView;
Tony Wickham9791bd12019-04-05 13:52:35 -070047
Steven Nge8d92342021-02-19 21:29:18 +000048import com.android.launcher3.DeviceProfile;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070049import com.android.launcher3.Insettable;
50import com.android.launcher3.Launcher;
51import com.android.launcher3.LauncherAppState;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070052import com.android.launcher3.R;
Sunny Goyalf3ac7032020-03-13 13:01:33 -070053import com.android.launcher3.anim.PendingAnimation;
vadimt9f48a8e2019-12-11 11:52:37 -080054import com.android.launcher3.compat.AccessibilityManagerCompat;
Steven Ng4235fc12021-03-19 20:13:16 +000055import com.android.launcher3.model.WidgetItem;
Alina Zaidi120d6472021-04-09 09:58:53 +010056import com.android.launcher3.views.ArrowTipView;
Sunny Goyal3661bfa2018-03-01 16:29:15 -080057import com.android.launcher3.views.RecyclerViewFastScroller;
58import com.android.launcher3.views.TopRoundedCornerView;
Steven Ng2f5648a2021-02-08 17:18:25 +000059import com.android.launcher3.widget.BaseWidgetSheet;
Yogisha Dixit741fae92021-02-22 14:03:44 +000060import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
Steven Ng391404f2021-02-17 15:58:23 +000061import com.android.launcher3.widget.model.WidgetsListBaseEntry;
Alina Zaidi334e6592021-03-11 16:10:27 +000062import com.android.launcher3.widget.picker.search.SearchModeListener;
63import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
Alina Zaidi2cff1642021-03-24 09:52:45 +000064import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper;
Steven Ng4235fc12021-03-19 20:13:16 +000065import com.android.launcher3.widget.util.WidgetsTableUtils;
Steven Ng391404f2021-02-17 15:58:23 +000066import com.android.launcher3.workprofile.PersonalWorkPagedView;
67import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
68
Alina Zaidi2e732e92021-03-12 17:39:38 +000069import java.util.ArrayList;
Steven Ng391404f2021-02-17 15:58:23 +000070import java.util.List;
71import java.util.function.Predicate;
Alina Zaidi120d6472021-04-09 09:58:53 +010072import java.util.stream.IntStream;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070073
74/**
75 * Popup for showing the full list of available widgets
76 */
77public class WidgetsFullSheet extends BaseWidgetSheet
Steven Ng167f81b2021-02-23 10:48:46 +000078 implements Insettable, ProviderChangedListener, OnActivePageChangedListener,
Alina Zaidi2cff1642021-03-24 09:52:45 +000079 WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener,
80 WidgetsSearchBarUIHelper {
Steven Ng4235fc12021-03-19 20:13:16 +000081 private static final String TAG = WidgetsFullSheet.class.getSimpleName();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070082
83 private static final long DEFAULT_OPEN_DURATION = 267;
84 private static final long FADE_IN_DURATION = 150;
Alina Zaidi120d6472021-04-09 09:58:53 +010085 private static final long EDUCATION_TIP_DELAY_MS = 200;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070086 private static final float VERTICAL_START_POSITION = 0.3f;
Steven Ng4235fc12021-03-19 20:13:16 +000087 // The widget recommendation table can easily take over the entire screen on devices with small
88 // resolution or landscape on phone. This ratio defines the max percentage of content area that
89 // the table can display.
90 private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
Alina Zaidi120d6472021-04-09 09:58:53 +010091 private static final String WIDGETS_EDUCATION_TIP_SEEN = "launcher.widgets_education_tip_seen";
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070092
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -070093 private final Rect mInsets = new Rect();
Steven Ng391404f2021-02-17 15:58:23 +000094 private final boolean mHasWorkProfile;
95 private final SparseArray<AdapterHolder> mAdapters = new SparseArray();
96 private final UserHandle mCurrentUser = Process.myUserHandle();
97 private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter = entry ->
98 mCurrentUser.equals(entry.mPkgItem.user);
99 private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
100 mPrimaryWidgetsFilter.negate();
Alina Zaidi120d6472021-04-09 09:58:53 +0100101 private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
102 new OnLayoutChangeListener() {
103 @Override
104 public void onLayoutChange(View v, int left, int top, int right, int bottom,
105 int oldLeft, int oldTop, int oldRight, int oldBottom) {
106 if (hasSeenEducationTip()) {
107 removeOnLayoutChangeListener(this);
108 return;
109 }
110
111 // Widgets are loaded asynchronously, We are adding a delay because we only want
112 // to show the tip when the widget preview has finished loading and rendering in
113 // this view.
114 removeCallbacks(mShowEducationTipTask);
115 postDelayed(mShowEducationTipTask, EDUCATION_TIP_DELAY_MS);
116 }
117 };
118
119 private final Runnable mShowEducationTipTask = () -> {
120 if (hasSeenEducationTip()) {
121 removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
122 return;
123 }
124 View viewForTip = getViewToShowEducationTip();
125 if (viewForTip != null && ViewCompat.isLaidOut(viewForTip)) {
126 removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
127 showEducationTipOnView(viewForTip);
128 }
129 };
Steven Ng4235fc12021-03-19 20:13:16 +0000130 private final int mTabsHeight;
131 private final int mWidgetCellHorizontalPadding;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700132
Steven Ng391404f2021-02-17 15:58:23 +0000133 @Nullable private PersonalWorkPagedView mViewPager;
Alina Zaidi334e6592021-03-11 16:10:27 +0000134 private boolean mIsInSearchMode;
Steven Ng4235fc12021-03-19 20:13:16 +0000135 private int mMaxSpansPerRow = 4;
Steven Ng167f81b2021-02-23 10:48:46 +0000136 private View mTabsView;
Steven Ngd73d6e52021-03-09 22:44:04 +0000137 private TextView mNoWidgetsView;
Steven Ng167f81b2021-02-23 10:48:46 +0000138 private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
139 private SearchAndRecommendationsScrollController mSearchAndRecommendationsScrollController;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700140
141 public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
142 super(context, attrs, defStyleAttr);
Steven Ng391404f2021-02-17 15:58:23 +0000143 mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
144 mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
145 mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
Alina Zaidi334e6592021-03-11 16:10:27 +0000146 mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
Steven Ng4235fc12021-03-19 20:13:16 +0000147 mTabsHeight = mHasWorkProfile
148 ? getContext().getResources()
149 .getDimensionPixelSize(R.dimen.all_apps_header_tab_height)
150 : 0;
151 mWidgetCellHorizontalPadding = 2 * getResources().getDimensionPixelOffset(
152 R.dimen.widget_cell_horizontal_padding);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700153 }
154
155 public WidgetsFullSheet(Context context, AttributeSet attrs) {
156 this(context, attrs, 0);
157 }
158
159 @Override
160 protected void onFinishInflate() {
161 super.onFinishInflate();
162 mContent = findViewById(R.id.container);
Sunny Goyal016d7e92018-03-09 11:09:20 -0800163 TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
Steven Ng391404f2021-02-17 15:58:23 +0000164
165 LayoutInflater layoutInflater = LayoutInflater.from(getContext());
166 int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view
167 : R.layout.widgets_full_sheet_recyclerview;
Steven Ng167f81b2021-02-23 10:48:46 +0000168 layoutInflater.inflate(contentLayoutRes, springLayout, true);
Steven Ng391404f2021-02-17 15:58:23 +0000169
Steven Ng167f81b2021-02-23 10:48:46 +0000170 RecyclerViewFastScroller fastScroller = findViewById(R.id.fast_scroller);
Steven Ngffde1472021-04-16 18:02:23 +0100171 mAdapters.get(AdapterHolder.PRIMARY).setup(findViewById(R.id.primary_widgets_list_view));
172 mAdapters.get(AdapterHolder.SEARCH).setup(findViewById(R.id.search_widgets_list_view));
Steven Ng391404f2021-02-17 15:58:23 +0000173 if (mHasWorkProfile) {
174 mViewPager = findViewById(R.id.widgets_view_pager);
Steven Ng391404f2021-02-17 15:58:23 +0000175 mViewPager.initParentViews(this);
176 mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
177 mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.PRIMARY);
Steven Ng167f81b2021-02-23 10:48:46 +0000178 mTabsView = findViewById(R.id.tabs);
Steven Ng391404f2021-02-17 15:58:23 +0000179 findViewById(R.id.tab_personal)
180 .setOnClickListener((View view) -> mViewPager.snapToPage(0));
181 findViewById(R.id.tab_work)
182 .setOnClickListener((View view) -> mViewPager.snapToPage(1));
Steven Ng167f81b2021-02-23 10:48:46 +0000183 fastScroller.setIsRecyclerViewFirstChildInParent(false);
Steven Ngffde1472021-04-16 18:02:23 +0100184 mAdapters.get(AdapterHolder.WORK).setup(findViewById(R.id.work_widgets_list_view));
Steven Ng391404f2021-02-17 15:58:23 +0000185 } else {
186 mViewPager = null;
Steven Ng391404f2021-02-17 15:58:23 +0000187 }
188
Steven Ng167f81b2021-02-23 10:48:46 +0000189 layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout,
190 true);
Steven Ng167f81b2021-02-23 10:48:46 +0000191 mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder(
192 findViewById(R.id.search_and_recommendations_container));
193 mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
194 mHasWorkProfile,
Steven Ng4235fc12021-03-19 20:13:16 +0000195 mTabsHeight,
Steven Ng167f81b2021-02-23 10:48:46 +0000196 mSearchAndRecommendationViewHolder,
197 findViewById(R.id.primary_widgets_list_view),
198 mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
Alina Zaidi334e6592021-03-11 16:10:27 +0000199 findViewById(R.id.search_widgets_list_view),
Steven Ng167f81b2021-02-23 10:48:46 +0000200 mTabsView,
201 mViewPager);
202 fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
203
Steven Ngd73d6e52021-03-09 22:44:04 +0000204 mNoWidgetsView = findViewById(R.id.no_widgets_text);
205
Steven Ng4235fc12021-03-19 20:13:16 +0000206 onRecommendedWidgetsBound();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700207 onWidgetsBound();
Alina Zaidi334e6592021-03-11 16:10:27 +0000208
209 mSearchAndRecommendationViewHolder.mSearchBar.initialize(
Sunny Goyalce6cc7e2021-03-30 16:51:08 -0700210 mLauncher.getPopupDataProvider(), /* searchModeListener= */ this);
Alina Zaidi120d6472021-04-09 09:58:53 +0100211
212 if (!hasSeenEducationTip()) {
213 addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
214 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700215 }
216
Steven Ng391404f2021-02-17 15:58:23 +0000217 @Override
218 public void onActivePageChanged(int currentActivePage) {
Steven Ngd73d6e52021-03-09 22:44:04 +0000219 AdapterHolder currentAdapterHolder = mAdapters.get(currentActivePage);
Alina Zaidi334e6592021-03-11 16:10:27 +0000220 WidgetsRecyclerView currentRecyclerView =
221 mAdapters.get(currentActivePage).mWidgetsRecyclerView;
Steven Ng167f81b2021-02-23 10:48:46 +0000222
Steven Ngd73d6e52021-03-09 22:44:04 +0000223 updateNoWidgetsView(currentAdapterHolder);
Alina Zaidi334e6592021-03-11 16:10:27 +0000224 attachScrollbarToRecyclerView(currentRecyclerView);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000225 resetExpandedHeaders();
Alina Zaidi334e6592021-03-11 16:10:27 +0000226 }
227
228 private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) {
229 recyclerView.bindFastScrollbar();
230 mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView);
Steven Ng167f81b2021-02-23 10:48:46 +0000231 reset();
232 }
233
Steven Ngd73d6e52021-03-09 22:44:04 +0000234 private void updateNoWidgetsView(AdapterHolder adapterHolder) {
235 boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.getItemCount() > 0;
236 adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
237
238 // Always resets the text in case this is updated by search.
239 mNoWidgetsView.setText(R.string.no_widgets_available);
240 mNoWidgetsView.setVisibility(isWidgetAvailable ? GONE : VISIBLE);
241 }
242
Alina Zaidi2e732e92021-03-12 17:39:38 +0000243 private void updateNoSearchResultsView(boolean isVisible) {
244 mNoWidgetsView.setVisibility(isVisible ? VISIBLE : GONE);
245 if (isVisible) {
246 mNoWidgetsView.setText(R.string.no_search_results);
247 }
248 }
249
Steven Ng167f81b2021-02-23 10:48:46 +0000250 private void reset() {
251 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
252 if (mHasWorkProfile) {
253 mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
254 }
Alina Zaidi334e6592021-03-11 16:10:27 +0000255 mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
Steven Ng7f7b2f72021-03-03 14:51:36 +0000256 mSearchAndRecommendationsScrollController.reset();
Steven Ng391404f2021-02-17 15:58:23 +0000257 }
258
Sunny Goyal8b37c572020-03-31 12:12:14 -0700259 @VisibleForTesting
260 public WidgetsRecyclerView getRecyclerView() {
Alina Zaidi334e6592021-03-11 16:10:27 +0000261 if (mIsInSearchMode) {
262 return mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView;
263 }
Steven Ng391404f2021-02-17 15:58:23 +0000264 if (!mHasWorkProfile || mViewPager.getCurrentPage() == AdapterHolder.PRIMARY) {
265 return mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
266 }
267 return mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView;
Sunny Goyal8b37c572020-03-31 12:12:14 -0700268 }
269
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700270 @Override
Sunny Goyalde753212018-05-15 13:55:57 -0700271 protected Pair<View, String> getAccessibilityTarget() {
Steven Ng391404f2021-02-17 15:58:23 +0000272 return Pair.create(getRecyclerView(), getContext().getString(
Sunny Goyalde753212018-05-15 13:55:57 -0700273 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
274 }
275
276 @Override
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700277 protected void onAttachedToWindow() {
278 super.onAttachedToWindow();
279 mLauncher.getAppWidgetHost().addProviderChangeListener(this);
280 notifyWidgetProvidersChanged();
281 }
282
283 @Override
284 protected void onDetachedFromWindow() {
285 super.onDetachedFromWindow();
286 mLauncher.getAppWidgetHost().removeProviderChangeListener(this);
287 }
288
289 @Override
290 public void setInsets(Rect insets) {
291 mInsets.set(insets);
292
Steven Ng391404f2021-02-17 15:58:23 +0000293 setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, insets.bottom);
Alina Zaidic4f3f492021-03-19 14:22:43 +0000294 setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, insets.bottom);
Steven Ng391404f2021-02-17 15:58:23 +0000295 if (mHasWorkProfile) {
296 setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
297 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700298 if (insets.bottom > 0) {
299 setupNavBarColor();
Sunny Goyalf8d56fc2018-01-31 15:18:11 -0800300 } else {
301 clearNavBarColor();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700302 }
Sunny Goyal3661bfa2018-03-01 16:29:15 -0800303
304 ((TopRoundedCornerView) mContent).setNavBarScrimHeight(mInsets.bottom);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700305 requestLayout();
306 }
307
Steven Ng391404f2021-02-17 15:58:23 +0000308 private void setBottomPadding(RecyclerView recyclerView, int bottomPadding) {
309 recyclerView.setPadding(
310 recyclerView.getPaddingLeft(),
311 recyclerView.getPaddingTop(),
312 recyclerView.getPaddingRight(),
313 bottomPadding);
314 }
315
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700316 @Override
317 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Steven Ng4235fc12021-03-19 20:13:16 +0000318 doMeasure(widthMeasureSpec, heightMeasureSpec);
319
320 if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
321 doMeasure(widthMeasureSpec, heightMeasureSpec);
322 }
323
324 if (updateMaxSpansPerRow()) {
325 doMeasure(widthMeasureSpec, heightMeasureSpec);
326
327 if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
328 doMeasure(widthMeasureSpec, heightMeasureSpec);
329 }
330 }
331 }
332
333 private void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Steven Nge8d92342021-02-19 21:29:18 +0000334 DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700335 int widthUsed;
336 if (mInsets.bottom > 0) {
Sunny Goyal786940a2020-06-02 02:31:31 -0700337 widthUsed = mInsets.left + mInsets.right;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700338 } else {
Steven Nge8d92342021-02-19 21:29:18 +0000339 Rect padding = deviceProfile.workspacePadding;
Sunny Goyal07b69292018-01-08 14:19:34 -0800340 widthUsed = Math.max(padding.left + padding.right,
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700341 2 * (mInsets.left + mInsets.right));
342 }
343
Steven Nge8d92342021-02-19 21:29:18 +0000344 int heightUsed = mInsets.top + deviceProfile.edgeMarginPx;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700345 measureChildWithMargins(mContent, widthMeasureSpec,
346 widthUsed, heightMeasureSpec, heightUsed);
Sunny Goyal6639a5d2018-02-28 15:09:36 -0800347 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
348 MeasureSpec.getSize(heightMeasureSpec));
Steven Ng4235fc12021-03-19 20:13:16 +0000349 }
Steven Nge8d92342021-02-19 21:29:18 +0000350
Steven Ng4235fc12021-03-19 20:13:16 +0000351 /** Returns {@code true} if the max spans have been updated. */
352 private boolean updateMaxSpansPerRow() {
353 if (getMeasuredWidth() == 0) return false;
354
355 int previousMaxSpansPerRow = mMaxSpansPerRow;
356 mMaxSpansPerRow = getMeasuredWidth()
357 / (mLauncher.getDeviceProfile().cellWidthPx + mWidgetCellHorizontalPadding);
358
359 if (previousMaxSpansPerRow != mMaxSpansPerRow) {
360 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
361 mMaxSpansPerRow);
362 mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
363 mMaxSpansPerRow);
364 if (mHasWorkProfile) {
365 mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
366 mMaxSpansPerRow);
367 }
368 onRecommendedWidgetsBound();
369 return true;
Steven Nge8d92342021-02-19 21:29:18 +0000370 }
Steven Ng4235fc12021-03-19 20:13:16 +0000371 return false;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700372 }
373
374 @Override
375 protected void onLayout(boolean changed, int l, int t, int r, int b) {
376 int width = r - l;
377 int height = b - t;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700378
379 // Content is laid out as center bottom aligned
380 int contentWidth = mContent.getMeasuredWidth();
Sunny Goyal786940a2020-06-02 02:31:31 -0700381 int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700382 mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
383 contentLeft + contentWidth, height);
384
385 setTranslationShift(mTranslationShift);
386 }
387
388 @Override
389 public void notifyWidgetProvidersChanged() {
390 mLauncher.refreshAndBindWidgetsForPackageUser(null);
391 }
392
393 @Override
Sunny Goyal202ae0b2019-02-04 16:26:42 -0800394 public void onWidgetsBound() {
Steven Ng391404f2021-02-17 15:58:23 +0000395 List<WidgetsListBaseEntry> allWidgets = mLauncher.getPopupDataProvider().getAllWidgets();
396
397 AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY);
Steven Ng391404f2021-02-17 15:58:23 +0000398 primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
Steven Ngd73d6e52021-03-09 22:44:04 +0000399 updateNoWidgetsView(primaryUserAdapterHolder);
400
Steven Ng391404f2021-02-17 15:58:23 +0000401 if (mHasWorkProfile) {
402 AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
Steven Ng391404f2021-02-17 15:58:23 +0000403 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 Zaidi1ccd7ef2021-04-13 10:41:18 +0100433 mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
Alina Zaidi334e6592021-03-11 16:10:27 +0000434 }
435
436 private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
437 mIsInSearchMode = isInSearchMode;
Alina Zaidi1869a982021-04-19 20:02:17 +0100438 if (isInSearchMode) {
439 mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.setVisibility(GONE);
440 } else {
441 onRecommendedWidgetsBound();
442 }
Alina Zaidi334e6592021-03-11 16:10:27 +0000443 if (mHasWorkProfile) {
444 mViewPager.setVisibility(isInSearchMode ? GONE : VISIBLE);
445 mTabsView.setVisibility(isInSearchMode ? GONE : VISIBLE);
446 } else {
447 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView
448 .setVisibility(isInSearchMode ? GONE : VISIBLE);
449 }
450 mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView
451 .setVisibility(mIsInSearchMode ? VISIBLE : GONE);
Alina Zaidi2e732e92021-03-12 17:39:38 +0000452 mNoWidgetsView.setVisibility(GONE);
453 }
454
455 private void resetExpandedHeaders() {
456 mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.resetExpandedHeader();
457 mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader();
Alina Zaidi334e6592021-03-11 16:10:27 +0000458 }
459
Steven Ng4235fc12021-03-19 20:13:16 +0000460 @Override
461 public void onRecommendedWidgetsBound() {
462 List<WidgetItem> recommendedWidgets =
463 mLauncher.getPopupDataProvider().getRecommendedWidgets();
464 WidgetsRecommendationTableLayout table =
465 mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable;
466 if (recommendedWidgets.size() > 0) {
Steven Ng29f6b0b2021-04-16 17:11:47 +0100467 // TODO(b/185508758): Revert the following log after debugging.
468 if (getHeaderViewHeight() == 0) {
469 Log.d(TAG, "Header view height is 0 when inflating recommended widgets");
470 }
Steven Ng4235fc12021-03-19 20:13:16 +0000471 float maxTableHeight =
Steven Ng769a14e2021-04-19 22:06:38 +0100472 (mLauncher.getDeviceProfile().availableHeightPx - mTabsHeight
473 - getHeaderViewHeight()) * RECOMMENDATION_TABLE_HEIGHT_RATIO;
Steven Ng4235fc12021-03-19 20:13:16 +0000474 List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
475 WidgetsTableUtils.groupWidgetItemsIntoTable(recommendedWidgets,
476 mMaxSpansPerRow);
477 table.setRecommendedWidgets(recommendedWidgetsInTable, maxTableHeight);
478 } else {
479 table.setVisibility(GONE);
480 }
481 }
482
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700483 private void open(boolean animate) {
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700484 if (animate) {
Sunny Goyaldb6cdb02018-07-13 11:03:04 -0700485 if (getPopupContainer().getInsets().bottom > 0) {
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700486 mContent.setAlpha(0);
487 setTranslationShift(VERTICAL_START_POSITION);
488 }
489 mOpenCloseAnimator.setValues(
490 PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
491 mOpenCloseAnimator
492 .setDuration(DEFAULT_OPEN_DURATION)
493 .setInterpolator(AnimationUtils.loadInterpolator(
494 getContext(), android.R.interpolator.linear_out_slow_in));
495 mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
496 @Override
497 public void onAnimationEnd(Animator animation) {
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700498 mOpenCloseAnimator.removeListener(this);
499 }
500 });
Sunny Goyal6639a5d2018-02-28 15:09:36 -0800501 post(() -> {
Sunny Goyal6639a5d2018-02-28 15:09:36 -0800502 mOpenCloseAnimator.start();
503 mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700504 });
505 } else {
506 setTranslationShift(TRANSLATION_SHIFT_OPENED);
Sunny Goyalde753212018-05-15 13:55:57 -0700507 post(this::announceAccessibilityChanges);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700508 }
509 }
510
511 @Override
512 protected void handleClose(boolean animate) {
513 handleClose(animate, DEFAULT_OPEN_DURATION);
514 }
515
516 @Override
517 protected boolean isOfType(int type) {
518 return (type & TYPE_WIDGETS_FULL_SHEET) != 0;
519 }
520
521 @Override
522 public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
523 // Disable swipe down when recycler view is scrolling
524 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
525 mNoIntercept = false;
Steven Ng391404f2021-02-17 15:58:23 +0000526 RecyclerViewFastScroller scroller = getRecyclerView().getScrollbar();
Steven Ng2f5648a2021-02-08 17:18:25 +0000527 if (scroller.getThumbOffsetY() >= 0
528 && getPopupContainer().isEventOverView(scroller, ev)) {
Sunny Goyal3661bfa2018-03-01 16:29:15 -0800529 mNoIntercept = true;
Sunny Goyaldb6cdb02018-07-13 11:03:04 -0700530 } else if (getPopupContainer().isEventOverView(mContent, ev)) {
Steven Ng391404f2021-02-17 15:58:23 +0000531 mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer());
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700532 }
533 }
534 return super.onControllerInterceptTouchEvent(ev);
535 }
536
Steven Ng2f5648a2021-02-08 17:18:25 +0000537 /** Shows the {@link WidgetsFullSheet} on the launcher. */
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700538 public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
539 WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
540 .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
Sunny Goyal1642f712018-09-18 11:40:35 -0700541 sheet.attachToContainer();
Sunny Goyalde753212018-05-15 13:55:57 -0700542 sheet.mIsOpen = true;
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700543 sheet.open(animate);
544 return sheet;
545 }
Sunny Goyalaeb16432017-10-16 11:46:41 -0700546
Steven Ng2f5648a2021-02-08 17:18:25 +0000547 /** Gets the {@link WidgetsRecyclerView} which shows all widgets in {@link WidgetsFullSheet}. */
Vadim Tryshev2ce6a132018-06-18 19:14:44 -0700548 @VisibleForTesting
549 public static WidgetsRecyclerView getWidgetsView(Launcher launcher) {
Steven Ng391404f2021-02-17 15:58:23 +0000550 return launcher.findViewById(R.id.primary_widgets_list_view);
Vadim Tryshev2ce6a132018-06-18 19:14:44 -0700551 }
552
Sunny Goyalaeb16432017-10-16 11:46:41 -0700553 @Override
Sunny Goyalf3ac7032020-03-13 13:01:33 -0700554 public void addHintCloseAnim(
555 float distanceToMove, Interpolator interpolator, PendingAnimation target) {
Steven Ng391404f2021-02-17 15:58:23 +0000556 target.setFloat(getRecyclerView(), VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
557 target.setViewAlpha(getRecyclerView(), 0.5f, interpolator);
Tony Wickham9791bd12019-04-05 13:52:35 -0700558 }
vadimt9f48a8e2019-12-11 11:52:37 -0800559
560 @Override
561 protected void onCloseComplete() {
562 super.onCloseComplete();
563 AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
564 }
Steven Ng391404f2021-02-17 15:58:23 +0000565
Steven Ng167f81b2021-02-23 10:48:46 +0000566 @Override
567 public int getHeaderViewHeight() {
Steven Ng53d13642021-03-10 22:40:55 +0000568 return measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mCollapseHandle)
569 + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mHeaderTitle)
Alina Zaidi16327522021-03-15 07:00:49 +0000570 + measureHeightWithVerticalMargins(
571 (View) mSearchAndRecommendationViewHolder.mSearchBar);
Steven Ng7f7b2f72021-03-03 14:51:36 +0000572 }
573
574 /** private the height, in pixel, + the vertical margins of a given view. */
575 private static int measureHeightWithVerticalMargins(View view) {
Steven Ng53d13642021-03-10 22:40:55 +0000576 if (view.getVisibility() != VISIBLE) {
577 return 0;
578 }
Steven Ng7f7b2f72021-03-03 14:51:36 +0000579 MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
580 return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
581 + marginLayoutParams.topMargin;
Steven Ng167f81b2021-02-23 10:48:46 +0000582 }
583
Alina Zaidic4f3f492021-03-19 14:22:43 +0000584 @Override
585 protected void onConfigurationChanged(Configuration newConfig) {
586 super.onConfigurationChanged(newConfig);
587 if (mIsInSearchMode) {
588 mSearchAndRecommendationViewHolder.mSearchBar.reset();
589 }
590 }
591
592 @Override
593 public boolean onBackPressed() {
594 if (mIsInSearchMode) {
595 mSearchAndRecommendationViewHolder.mSearchBar.reset();
596 return true;
597 }
598 return super.onBackPressed();
599 }
600
Alina Zaidi76060eb2021-03-22 12:25:37 +0000601 @Override
602 public void onDragStart(boolean start, float startDisplacement) {
603 super.onDragStart(start, startDisplacement);
604 getWindowInsetsController().hide(WindowInsets.Type.ime());
605 }
606
Alina Zaidi2cff1642021-03-24 09:52:45 +0000607 @Override
608 public void clearSearchBarFocus() {
609 mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus();
610 }
611
Alina Zaidi120d6472021-04-09 09:58:53 +0100612 private void showEducationTipOnView(View view) {
613 mLauncher.getSharedPrefs().edit().putBoolean(WIDGETS_EDUCATION_TIP_SEEN, true).apply();
614 int[] coords = new int[2];
615 view.getLocationOnScreen(coords);
616 ArrowTipView arrowTipView = new ArrowTipView(mLauncher);
617 arrowTipView.showAtLocation(
618 getContext().getString(R.string.long_press_widget_to_add),
619 /* arrowXCoord= */coords[0] + view.getWidth() / 2,
620 /* yCoord= */coords[1]);
621 }
622
623 @Nullable private View getViewToShowEducationTip() {
624 if (mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getVisibility() == VISIBLE
625 && mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getChildCount() > 0
626 ) {
627 return ((ViewGroup) mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
628 .getChildAt(0)).getChildAt(0);
629 }
630
631 AdapterHolder adapterHolder = mAdapters.get(mIsInSearchMode
632 ? AdapterHolder.SEARCH
633 : mViewPager == null
634 ? AdapterHolder.PRIMARY
635 : mViewPager.getCurrentPage());
636 WidgetsRowViewHolder viewHolderForTip =
637 (WidgetsRowViewHolder) IntStream.range(
638 0, adapterHolder.mWidgetsListAdapter.getItemCount())
639 .mapToObj(adapterHolder.mWidgetsRecyclerView::
640 findViewHolderForAdapterPosition)
641 .filter(viewHolder -> viewHolder instanceof WidgetsRowViewHolder)
642 .findFirst()
643 .orElse(null);
644 if (viewHolderForTip != null) {
645 return ((ViewGroup) viewHolderForTip.mTableContainer.getChildAt(0)).getChildAt(0);
646 }
647
648 return null;
649 }
650
651 private boolean hasSeenEducationTip() {
652 return mLauncher.getSharedPrefs().getBoolean(WIDGETS_EDUCATION_TIP_SEEN, false);
653 }
654
Steven Ng391404f2021-02-17 15:58:23 +0000655 /** A holder class for holding adapters & their corresponding recycler view. */
656 private final class AdapterHolder {
657 static final int PRIMARY = 0;
658 static final int WORK = 1;
Alina Zaidi334e6592021-03-11 16:10:27 +0000659 static final int SEARCH = 2;
Steven Ng391404f2021-02-17 15:58:23 +0000660
661 private final int mAdapterType;
662 private final WidgetsListAdapter mWidgetsListAdapter;
663
664 private WidgetsRecyclerView mWidgetsRecyclerView;
665
666 AdapterHolder(int adapterType) {
667 mAdapterType = adapterType;
668
669 Context context = getContext();
670 LauncherAppState apps = LauncherAppState.getInstance(context);
671 mWidgetsListAdapter = new WidgetsListAdapter(
672 context,
673 LayoutInflater.from(context),
674 apps.getWidgetCache(),
675 apps.getIconCache(),
676 /* iconClickListener= */ WidgetsFullSheet.this,
Alina Zaidi2cff1642021-03-24 09:52:45 +0000677 /* iconLongClickListener= */ WidgetsFullSheet.this,
678 /* WidgetsSearchBarUIHelper= */
679 mAdapterType == SEARCH ? WidgetsFullSheet.this : null);
Steven Nge85b5562021-03-19 23:52:12 +0000680 mWidgetsListAdapter.setHasStableIds(true);
Alina Zaidi334e6592021-03-11 16:10:27 +0000681 switch (mAdapterType) {
682 case PRIMARY:
683 mWidgetsListAdapter.setFilter(mPrimaryWidgetsFilter);
684 break;
685 case WORK:
686 mWidgetsListAdapter.setFilter(mWorkWidgetsFilter);
687 break;
688 default:
689 break;
690 }
Steven Ng391404f2021-02-17 15:58:23 +0000691 }
692
693 void setup(WidgetsRecyclerView recyclerView) {
694 mWidgetsRecyclerView = recyclerView;
695 mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
Steven Nge85b5562021-03-19 23:52:12 +0000696 // Disables animation because it disrupts the item focus upon adapter item change.
697 mWidgetsRecyclerView.setItemAnimator(null);
Steven Ng167f81b2021-02-23 10:48:46 +0000698 mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
Steven Ng391404f2021-02-17 15:58:23 +0000699 mWidgetsRecyclerView.setEdgeEffectFactory(
700 ((TopRoundedCornerView) mContent).createEdgeEffectFactory());
701 mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
Steven Ng4235fc12021-03-19 20:13:16 +0000702 mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
Steven Ng391404f2021-02-17 15:58:23 +0000703 }
704 }
Steven Ng167f81b2021-02-23 10:48:46 +0000705
706 final class SearchAndRecommendationViewHolder {
Steven Ng4235fc12021-03-19 20:13:16 +0000707 final ViewGroup mContainer;
Steven Ng167f81b2021-02-23 10:48:46 +0000708 final View mCollapseHandle;
Alina Zaidi334e6592021-03-11 16:10:27 +0000709 final WidgetsSearchBar mSearchBar;
Steven Ng167f81b2021-02-23 10:48:46 +0000710 final TextView mHeaderTitle;
Steven Ng4235fc12021-03-19 20:13:16 +0000711 final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
Steven Ng167f81b2021-02-23 10:48:46 +0000712
Steven Ng4235fc12021-03-19 20:13:16 +0000713 SearchAndRecommendationViewHolder(ViewGroup searchAndRecommendationContainer) {
Steven Ng167f81b2021-02-23 10:48:46 +0000714 mContainer = searchAndRecommendationContainer;
715 mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
716 mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
717 mHeaderTitle = mContainer.findViewById(R.id.title);
Steven Ng4235fc12021-03-19 20:13:16 +0000718 mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
719 mRecommendedWidgetsTable.setWidgetCellOnTouchListener((view, event) -> {
720 getRecyclerView().onTouchEvent(event);
721 return false;
722 });
723 mRecommendedWidgetsTable.setWidgetCellLongClickListener(WidgetsFullSheet.this);
724 mRecommendedWidgetsTable.setWidgetCellOnClickListener(WidgetsFullSheet.this);
Steven Ng167f81b2021-02-23 10:48:46 +0000725 }
726 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700727}