• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
21 
22 import android.animation.PropertyValuesHolder;
23 import android.content.Context;
24 import android.graphics.Rect;
25 import android.util.AttributeSet;
26 import android.util.IntProperty;
27 import android.util.Pair;
28 import android.view.Gravity;
29 import android.view.LayoutInflater;
30 import android.view.MotionEvent;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.view.animation.Interpolator;
34 import android.widget.ScrollView;
35 import android.widget.TableLayout;
36 import android.widget.TableRow;
37 import android.widget.TextView;
38 
39 import com.android.launcher3.DeviceProfile;
40 import com.android.launcher3.LauncherAppState;
41 import com.android.launcher3.R;
42 import com.android.launcher3.anim.PendingAnimation;
43 import com.android.launcher3.model.WidgetItem;
44 import com.android.launcher3.model.data.ItemInfo;
45 import com.android.launcher3.util.PackageUserKey;
46 import com.android.launcher3.widget.util.WidgetsTableUtils;
47 
48 import java.util.List;
49 
50 /**
51  * Bottom sheet for the "Widgets" system shortcut in the long-press popup.
52  */
53 public class WidgetsBottomSheet extends BaseWidgetSheet {
54     private static final String TAG = "WidgetsBottomSheet";
55 
56     private static final IntProperty<View> PADDING_BOTTOM =
57             new IntProperty<View>("paddingBottom") {
58                 @Override
59                 public void setValue(View view, int paddingBottom) {
60                     view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
61                             view.getPaddingRight(), paddingBottom);
62                 }
63 
64                 @Override
65                 public Integer get(View view) {
66                     return view.getPaddingBottom();
67                 }
68             };
69 
70     private static final int DEFAULT_CLOSE_DURATION = 200;
71     private static final long EDUCATION_TIP_DELAY_MS = 300;
72 
73     private ItemInfo mOriginalItemInfo;
74     private final int mMaxTableHeight;
75     private int mMaxHorizontalSpan = 4;
76 
77     private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
78             new OnLayoutChangeListener() {
79                 @Override
80                 public void onLayoutChange(View v, int left, int top, int right, int bottom,
81                         int oldLeft, int oldTop, int oldRight, int oldBottom) {
82                     if (hasSeenEducationTip()) {
83                         removeOnLayoutChangeListener(this);
84                         return;
85                     }
86                     // Widgets are loaded asynchronously, We are adding a delay because we only want
87                     // to show the tip when the widget preview has finished loading and rendering in
88                     // this view.
89                     removeCallbacks(mShowEducationTipTask);
90                     postDelayed(mShowEducationTipTask, EDUCATION_TIP_DELAY_MS);
91                 }
92             };
93 
94     private final Runnable mShowEducationTipTask = () -> {
95         if (hasSeenEducationTip()) {
96             removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
97             return;
98         }
99         View viewForTip = ((ViewGroup) ((TableLayout) findViewById(R.id.widgets_table))
100                                     .getChildAt(0)).getChildAt(0);
101         if (showEducationTipOnViewIfPossible(viewForTip) != null) {
102             removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
103         }
104     };
105 
WidgetsBottomSheet(Context context, AttributeSet attrs)106     public WidgetsBottomSheet(Context context, AttributeSet attrs) {
107         this(context, attrs, 0);
108     }
109 
WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr)110     public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) {
111         super(context, attrs, defStyleAttr);
112         setWillNotDraw(false);
113         DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
114         // Set the max table height to 2 / 3 of the grid height so that the bottom picker won't
115         // take over the entire view vertically.
116         mMaxTableHeight = deviceProfile.inv.numRows * 2 / 3  * deviceProfile.cellHeightPx;
117         if (!hasSeenEducationTip()) {
118             addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
119         }
120     }
121 
122     @Override
onFinishInflate()123     protected void onFinishInflate() {
124         super.onFinishInflate();
125         mContent = findViewById(R.id.widgets_bottom_sheet);
126     }
127 
128     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)129     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
130         doMeasure(widthMeasureSpec, heightMeasureSpec);
131         if (updateMaxSpansPerRow()) {
132             doMeasure(widthMeasureSpec, heightMeasureSpec);
133         }
134     }
135 
136     /** Returns {@code true} if the max spans have been updated. */
updateMaxSpansPerRow()137     private boolean updateMaxSpansPerRow() {
138         if (getMeasuredWidth() == 0) return false;
139 
140         int paddingPx = 2 * getResources().getDimensionPixelOffset(
141                 R.dimen.widget_cell_horizontal_padding);
142         int maxHorizontalSpan = findViewById(R.id.widgets_table).getMeasuredWidth()
143                 / (mActivityContext.getDeviceProfile().cellWidthPx + paddingPx);
144         if (mMaxHorizontalSpan != maxHorizontalSpan) {
145             // Ensure the table layout is showing widgets in the right column after measure.
146             mMaxHorizontalSpan = maxHorizontalSpan;
147             onWidgetsBound();
148             return true;
149         }
150         return false;
151     }
152 
153     @Override
onLayout(boolean changed, int l, int t, int r, int b)154     protected void onLayout(boolean changed, int l, int t, int r, int b) {
155         int width = r - l;
156         int height = b - t;
157 
158         // Content is laid out as center bottom aligned.
159         int contentWidth = mContent.getMeasuredWidth();
160         int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
161         mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
162                 contentLeft + contentWidth, height);
163 
164         setTranslationShift(mTranslationShift);
165 
166         // Ensure the scroll view height is not larger than mMaxTableHeight, which is a value
167         // smaller than the entire screen height.
168         ScrollView widgetsTableScrollView = findViewById(R.id.widgets_table_scroll_view);
169         if (widgetsTableScrollView.getMeasuredHeight() > mMaxTableHeight) {
170             ViewGroup.LayoutParams layoutParams = widgetsTableScrollView.getLayoutParams();
171             layoutParams.height = mMaxTableHeight;
172             widgetsTableScrollView.setLayoutParams(layoutParams);
173             findViewById(R.id.collapse_handle).setVisibility(VISIBLE);
174         }
175     }
176 
populateAndShow(ItemInfo itemInfo)177     public void populateAndShow(ItemInfo itemInfo) {
178         mOriginalItemInfo = itemInfo;
179         ((TextView) findViewById(R.id.title)).setText(mOriginalItemInfo.title);
180 
181         onWidgetsBound();
182         attachToContainer();
183         mIsOpen = false;
184         animateOpen();
185     }
186 
187     @Override
onWidgetsBound()188     public void onWidgetsBound() {
189         List<WidgetItem> widgets = mActivityContext.getPopupDataProvider().getWidgetsForPackageUser(
190                 new PackageUserKey(
191                         mOriginalItemInfo.getTargetComponent().getPackageName(),
192                         mOriginalItemInfo.user));
193 
194         TableLayout widgetsTable = findViewById(R.id.widgets_table);
195         widgetsTable.removeAllViews();
196 
197         WidgetsTableUtils.groupWidgetItemsIntoTable(widgets, mMaxHorizontalSpan).forEach(row -> {
198             TableRow tableRow = new TableRow(getContext());
199             tableRow.setGravity(Gravity.TOP);
200             row.forEach(widgetItem -> {
201                 WidgetCell widget = addItemCell(tableRow);
202                 widget.setPreviewSize(widgetItem);
203                 widget.applyFromCellItem(widgetItem, LauncherAppState.getInstance(mActivityContext)
204                         .getWidgetCache());
205                 widget.ensurePreview();
206                 widget.setVisibility(View.VISIBLE);
207             });
208             widgetsTable.addView(tableRow);
209         });
210     }
211 
212     @Override
onControllerInterceptTouchEvent(MotionEvent ev)213     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
214         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
215             mNoIntercept = false;
216             ScrollView scrollView = findViewById(R.id.widgets_table_scroll_view);
217             if (getPopupContainer().isEventOverView(scrollView, ev)
218                     && scrollView.getScrollY() > 0) {
219                 mNoIntercept = true;
220             }
221         }
222         return super.onControllerInterceptTouchEvent(ev);
223     }
224 
addItemCell(ViewGroup parent)225     protected WidgetCell addItemCell(ViewGroup parent) {
226         WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext())
227                 .inflate(R.layout.widget_cell, parent, false);
228 
229         View previewContainer = widget.findViewById(R.id.widget_preview_container);
230         previewContainer.setOnClickListener(this);
231         previewContainer.setOnLongClickListener(this);
232         widget.setAnimatePreview(false);
233         widget.setSourceContainer(CONTAINER_BOTTOM_WIDGETS_TRAY);
234 
235         parent.addView(widget);
236         return widget;
237     }
238 
animateOpen()239     private void animateOpen() {
240         if (mIsOpen || mOpenCloseAnimator.isRunning()) {
241             return;
242         }
243         mIsOpen = true;
244         setupNavBarColor();
245         mOpenCloseAnimator.setValues(
246                 PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
247         mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
248         mOpenCloseAnimator.start();
249     }
250 
251     @Override
handleClose(boolean animate)252     protected void handleClose(boolean animate) {
253         handleClose(animate, DEFAULT_CLOSE_DURATION);
254     }
255 
256     @Override
isOfType(@loatingViewType int type)257     protected boolean isOfType(@FloatingViewType int type) {
258         return (type & TYPE_WIDGETS_BOTTOM_SHEET) != 0;
259     }
260 
261     @Override
setInsets(Rect insets)262     public void setInsets(Rect insets) {
263         super.setInsets(insets);
264 
265         mContent.setPadding(mContent.getPaddingStart(),
266                 mContent.getPaddingTop(), mContent.getPaddingEnd(), insets.bottom);
267         if (insets.bottom > 0) {
268             setupNavBarColor();
269         } else {
270             clearNavBarColor();
271         }
272     }
273 
274     @Override
getAccessibilityTarget()275     protected Pair<View, String> getAccessibilityTarget() {
276         return Pair.create(findViewById(R.id.title),  getContext().getString(
277                 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
278     }
279 
280     @Override
addHintCloseAnim( float distanceToMove, Interpolator interpolator, PendingAnimation target)281     public void addHintCloseAnim(
282             float distanceToMove, Interpolator interpolator, PendingAnimation target) {
283         target.setInt(this, PADDING_BOTTOM, (int) (distanceToMove + mInsets.bottom), interpolator);
284     }
285 }
286