1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.widget;
18 
19 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_BOTTOM_WIDGETS_TRAY;
20 
21 import android.content.Context;
22 import android.graphics.Rect;
23 import android.util.AttributeSet;
24 import android.util.Pair;
25 import android.view.Gravity;
26 import android.view.LayoutInflater;
27 import android.view.MotionEvent;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.ViewParent;
31 import android.view.animation.Interpolator;
32 import android.widget.ScrollView;
33 import android.widget.TableLayout;
34 import android.widget.TextView;
35 
36 import androidx.annotation.Px;
37 
38 import com.android.launcher3.R;
39 import com.android.launcher3.anim.PendingAnimation;
40 import com.android.launcher3.model.WidgetItem;
41 import com.android.launcher3.model.data.ItemInfo;
42 import com.android.launcher3.util.PackageUserKey;
43 import com.android.launcher3.widget.util.WidgetsTableUtils;
44 
45 import java.util.List;
46 
47 /**
48  * Bottom sheet for the "Widgets" system shortcut in the long-press popup.
49  */
50 public class WidgetsBottomSheet extends BaseWidgetSheet {
51     private static final int DEFAULT_CLOSE_DURATION = 200;
52 
53     private ItemInfo mOriginalItemInfo;
54     @Px private int mMaxHorizontalSpan;
55 
WidgetsBottomSheet(Context context, AttributeSet attrs)56     public WidgetsBottomSheet(Context context, AttributeSet attrs) {
57         this(context, attrs, 0);
58     }
59 
WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr)60     public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) {
61         super(context, attrs, defStyleAttr);
62         setWillNotDraw(false);
63     }
64 
65     @Override
onFinishInflate()66     protected void onFinishInflate() {
67         super.onFinishInflate();
68         mContent = findViewById(R.id.widgets_bottom_sheet);
69         setContentBackgroundWithParent(
70                 getContext().getDrawable(R.drawable.bg_rounded_corner_bottom_sheet), mContent);
71     }
72 
73     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)74     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
75         doMeasure(widthMeasureSpec, heightMeasureSpec);
76         if (updateMaxSpansPerRow()) {
77             doMeasure(widthMeasureSpec, heightMeasureSpec);
78         }
79     }
80 
81     /** Returns {@code true} if the max spans have been updated. */
updateMaxSpansPerRow()82     private boolean updateMaxSpansPerRow() {
83         if (getMeasuredWidth() == 0) return false;
84 
85         @Px int maxHorizontalSpan = mContent.getMeasuredWidth() - (2 * mContentHorizontalMargin);
86         if (mMaxHorizontalSpan != maxHorizontalSpan) {
87             // Ensure the table layout is showing widgets in the right column after measure.
88             mMaxHorizontalSpan = maxHorizontalSpan;
89             onWidgetsBound();
90             return true;
91         }
92         return false;
93     }
94 
95     @Override
onLayout(boolean changed, int l, int t, int r, int b)96     protected void onLayout(boolean changed, int l, int t, int r, int b) {
97         int width = r - l;
98         int height = b - t;
99 
100         // Content is laid out as center bottom aligned.
101         int contentWidth = mContent.getMeasuredWidth();
102         int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
103         mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
104                 contentLeft + contentWidth, height);
105 
106         setTranslationShift(mTranslationShift);
107 
108         ScrollView widgetsTableScrollView = findViewById(R.id.widgets_table_scroll_view);
109         TableLayout widgetsTable = findViewById(R.id.widgets_table);
110         if (widgetsTable.getMeasuredHeight() > widgetsTableScrollView.getMeasuredHeight()) {
111             findViewById(R.id.collapse_handle).setVisibility(VISIBLE);
112         }
113     }
114 
populateAndShow(ItemInfo itemInfo)115     public void populateAndShow(ItemInfo itemInfo) {
116         mOriginalItemInfo = itemInfo;
117         ((TextView) findViewById(R.id.title)).setText(mOriginalItemInfo.title);
118 
119         onWidgetsBound();
120         attachToContainer();
121         mIsOpen = false;
122         animateOpen();
123     }
124 
125     @Override
onWidgetsBound()126     public void onWidgetsBound() {
127         List<WidgetItem> widgets = mActivityContext.getPopupDataProvider().getWidgetsForPackageUser(
128                 new PackageUserKey(
129                         mOriginalItemInfo.getTargetComponent().getPackageName(),
130                         mOriginalItemInfo.user));
131 
132         TableLayout widgetsTable = findViewById(R.id.widgets_table);
133         widgetsTable.removeAllViews();
134 
135         WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgets, mActivityContext,
136                 mActivityContext.getDeviceProfile(), mMaxHorizontalSpan,
137                 mWidgetCellHorizontalPadding)
138                 .forEach(row -> {
139                     WidgetTableRow tableRow = new WidgetTableRow(getContext());
140                     tableRow.setGravity(Gravity.TOP);
141                     tableRow.setupRow(row.size(), /*resizeDelayMs=*/ 0);
142                     row.forEach(widgetItem -> {
143                         WidgetCell widget = addItemCell(tableRow);
144                         widget.applyFromCellItem(widgetItem);
145                         if (widget.matchesItem(getLastSelectedWidgetItem())) {
146                             widget.callOnClick();
147                         }
148                     });
149                     widgetsTable.addView(tableRow);
150                 });
151     }
152 
153     @Override
onControllerInterceptTouchEvent(MotionEvent ev)154     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
155         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
156             mNoIntercept = false;
157             ScrollView scrollView = findViewById(R.id.widgets_table_scroll_view);
158             if (getPopupContainer().isEventOverView(scrollView, ev)
159                     && scrollView.getScrollY() > 0) {
160                 mNoIntercept = true;
161             }
162         }
163         return super.onControllerInterceptTouchEvent(ev);
164     }
165 
addItemCell(WidgetTableRow parent)166     protected WidgetCell addItemCell(WidgetTableRow parent) {
167         WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext())
168                 .inflate(R.layout.widget_cell, parent, false);
169         widget.addPreviewReadyListener(parent);
170         widget.setOnClickListener(this);
171 
172         View previewContainer = widget.findViewById(R.id.widget_preview_container);
173         previewContainer.setOnClickListener(this);
174         previewContainer.setOnLongClickListener(this);
175         widget.setAnimatePreview(false);
176         widget.setSourceContainer(CONTAINER_BOTTOM_WIDGETS_TRAY);
177 
178         parent.addView(widget);
179         return widget;
180     }
181 
animateOpen()182     private void animateOpen() {
183         if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
184             return;
185         }
186         mIsOpen = true;
187         setupNavBarColor();
188         setUpDefaultOpenAnimation().start();
189     }
190 
191     @Override
handleClose(boolean animate)192     protected void handleClose(boolean animate) {
193         handleClose(animate, DEFAULT_CLOSE_DURATION);
194     }
195 
196     @Override
isOfType(@loatingViewType int type)197     protected boolean isOfType(@FloatingViewType int type) {
198         return (type & TYPE_WIDGETS_BOTTOM_SHEET) != 0;
199     }
200 
201     @Override
setInsets(Rect insets)202     public void setInsets(Rect insets) {
203         super.setInsets(insets);
204         int bottomPadding = Math.max(insets.bottom, mNavBarScrimHeight);
205 
206         View widgetsTable = findViewById(R.id.widgets_table);
207         widgetsTable.setPadding(
208                 widgetsTable.getPaddingLeft(),
209                 widgetsTable.getPaddingTop(),
210                 widgetsTable.getPaddingRight(),
211                 bottomPadding);
212         if (bottomPadding > 0) {
213             setupNavBarColor();
214         } else {
215             clearNavBarColor();
216         }
217     }
218 
219     @Override
onContentHorizontalMarginChanged(int contentHorizontalMarginInPx)220     protected void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx) {
221         ViewGroup.MarginLayoutParams layoutParams =
222                 ((ViewGroup.MarginLayoutParams) findViewById(R.id.widgets_table).getLayoutParams());
223         layoutParams.setMarginStart(contentHorizontalMarginInPx);
224         layoutParams.setMarginEnd(contentHorizontalMarginInPx);
225     }
226 
227     @Override
getAccessibilityTarget()228     protected Pair<View, String> getAccessibilityTarget() {
229         return Pair.create(findViewById(R.id.title),  getContext().getString(
230                 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
231     }
232 
233     @Override
addHintCloseAnim( float distanceToMove, Interpolator interpolator, PendingAnimation target)234     public void addHintCloseAnim(
235             float distanceToMove, Interpolator interpolator, PendingAnimation target) {
236         target.addAnimatedFloat(mSwipeToDismissProgress, 0f, 1f, interpolator);
237     }
238 
239     @Override
scrollCellContainerByY(WidgetCell wc, int scrollByY)240     protected void scrollCellContainerByY(WidgetCell wc, int scrollByY) {
241         for (ViewParent parent = wc.getParent(); parent != null; parent = parent.getParent()) {
242             if (parent instanceof ScrollView scrollView) {
243                 scrollView.smoothScrollBy(0, scrollByY);
244                 return;
245             } else if (parent == this) {
246                 return;
247             }
248         }
249     }
250 }
251