• 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 package com.android.launcher3.widget;
17 
18 import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
19 
20 import android.content.Context;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.Point;
24 import android.graphics.Rect;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.view.View;
28 import android.view.View.OnClickListener;
29 import android.view.View.OnLongClickListener;
30 import android.view.WindowInsets;
31 import android.view.animation.Interpolator;
32 import android.widget.Toast;
33 
34 import androidx.annotation.GuardedBy;
35 import androidx.annotation.Nullable;
36 import androidx.annotation.Px;
37 import androidx.core.view.ViewCompat;
38 
39 import com.android.launcher3.DeviceProfile;
40 import com.android.launcher3.DragSource;
41 import com.android.launcher3.DropTarget.DragObject;
42 import com.android.launcher3.Insettable;
43 import com.android.launcher3.Launcher;
44 import com.android.launcher3.R;
45 import com.android.launcher3.Utilities;
46 import com.android.launcher3.dragndrop.DragOptions;
47 import com.android.launcher3.popup.PopupDataProvider;
48 import com.android.launcher3.testing.TestLogging;
49 import com.android.launcher3.testing.shared.TestProtocol;
50 import com.android.launcher3.touch.ItemLongClickListener;
51 import com.android.launcher3.util.SystemUiController;
52 import com.android.launcher3.util.Themes;
53 import com.android.launcher3.util.window.WindowManagerProxy;
54 import com.android.launcher3.views.AbstractSlideInView;
55 import com.android.launcher3.views.ActivityContext;
56 import com.android.launcher3.views.ArrowTipView;
57 
58 /**
59  * Base class for various widgets popup
60  */
61 public abstract class BaseWidgetSheet extends AbstractSlideInView<Launcher>
62         implements OnClickListener, OnLongClickListener, DragSource,
63         PopupDataProvider.PopupDataChangeListener, Insettable {
64     /** The default number of cells that can fit horizontally in a widget sheet. */
65     public static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4;
66 
67     protected static final String KEY_WIDGETS_EDUCATION_TIP_SEEN =
68             "launcher.widgets_education_tip_seen";
69     protected final Rect mInsets = new Rect();
70 
71     /* Touch handling related member variables. */
72     private Toast mWidgetInstructionToast;
73 
74     @Px protected int mContentHorizontalMargin;
75     @Px protected int mWidgetCellHorizontalPadding;
76 
77     protected int mNavBarScrimHeight;
78     private final Paint mNavBarScrimPaint;
79 
BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr)80     public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
81         super(context, attrs, defStyleAttr);
82         mContentHorizontalMargin = getResources().getDimensionPixelSize(
83                 R.dimen.widget_list_horizontal_margin);
84         mWidgetCellHorizontalPadding = getResources().getDimensionPixelSize(
85                 R.dimen.widget_cell_horizontal_padding);
86         mNavBarScrimPaint = new Paint();
87         mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
88     }
89 
getScrimColor(Context context)90     protected int getScrimColor(Context context) {
91         return context.getResources().getColor(R.color.widgets_picker_scrim);
92     }
93 
94     @Override
onAttachedToWindow()95     protected void onAttachedToWindow() {
96         super.onAttachedToWindow();
97         WindowInsets windowInsets = WindowManagerProxy.INSTANCE.get(getContext())
98                 .normalizeWindowInsets(getContext(), getRootWindowInsets(), new Rect());
99         mNavBarScrimHeight = getNavBarScrimHeight(windowInsets);
100         mActivityContext.getPopupDataProvider().setChangeListener(this);
101     }
102 
103     @Override
onDetachedFromWindow()104     protected void onDetachedFromWindow() {
105         super.onDetachedFromWindow();
106         mActivityContext.getPopupDataProvider().setChangeListener(null);
107     }
108 
109     @Override
onClick(View v)110     public final void onClick(View v) {
111         Object tag = null;
112         if (v instanceof WidgetCell) {
113             tag = v.getTag();
114         } else if (v.getParent() instanceof WidgetCell) {
115             tag = ((WidgetCell) v.getParent()).getTag();
116         }
117         if (tag instanceof PendingAddShortcutInfo) {
118             mWidgetInstructionToast = showShortcutToast(getContext(), mWidgetInstructionToast);
119         } else {
120             mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast);
121         }
122 
123     }
124 
125     @Override
onLongClick(View v)126     public boolean onLongClick(View v) {
127         if (TestProtocol.sDebugTracing) {
128             Log.d(TestProtocol.NO_DROP_TARGET, "1");
129         }
130         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick");
131         v.cancelLongPress();
132         if (!ItemLongClickListener.canStartDrag(mActivityContext)) return false;
133 
134         if (v instanceof WidgetCell) {
135             return beginDraggingWidget((WidgetCell) v);
136         } else if (v.getParent() instanceof WidgetCell) {
137             return beginDraggingWidget((WidgetCell) v.getParent());
138         }
139         return true;
140     }
141 
142     @Override
setInsets(Rect insets)143     public void setInsets(Rect insets) {
144         mInsets.set(insets);
145         @Px int contentHorizontalMargin = getResources().getDimensionPixelSize(
146                 R.dimen.widget_list_horizontal_margin);
147         if (contentHorizontalMargin != mContentHorizontalMargin) {
148             onContentHorizontalMarginChanged(contentHorizontalMargin);
149             mContentHorizontalMargin = contentHorizontalMargin;
150         }
151     }
152 
getNavBarScrimHeight(WindowInsets insets)153     private int getNavBarScrimHeight(WindowInsets insets) {
154         if (Utilities.ATLEAST_Q) {
155             return insets.getTappableElementInsets().bottom;
156         } else {
157             return insets.getStableInsetBottom();
158         }
159     }
160 
161     @Override
onApplyWindowInsets(WindowInsets insets)162     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
163         mNavBarScrimHeight = getNavBarScrimHeight(insets);
164         return super.onApplyWindowInsets(insets);
165     }
166 
167     @Override
dispatchDraw(Canvas canvas)168     protected void dispatchDraw(Canvas canvas) {
169         super.dispatchDraw(canvas);
170 
171         if (mNavBarScrimHeight > 0) {
172             canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
173                     mNavBarScrimPaint);
174         }
175     }
176 
177     /** Called when the horizontal margin of the content view has changed. */
onContentHorizontalMarginChanged(int contentHorizontalMarginInPx)178     protected abstract void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx);
179 
180     /**
181      * Measures the dimension of this view and its children by taking system insets, navigation bar,
182      * status bar, into account.
183      */
184     @GuardedBy("MainThread")
doMeasure(int widthMeasureSpec, int heightMeasureSpec)185     protected void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
186         DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
187         int widthUsed;
188         if (deviceProfile.isTablet) {
189             int margin = deviceProfile.allAppsLeftRightMargin;
190             widthUsed = Math.max(2 * margin, 2 * (mInsets.left + mInsets.right));
191         } else if (mInsets.bottom > 0) {
192             widthUsed = mInsets.left + mInsets.right;
193         } else {
194             Rect padding = deviceProfile.workspacePadding;
195             widthUsed = Math.max(padding.left + padding.right,
196                     2 * (mInsets.left + mInsets.right));
197         }
198 
199         measureChildWithMargins(mContent, widthMeasureSpec,
200                 widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding);
201         setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
202                 MeasureSpec.getSize(heightMeasureSpec));
203     }
204 
beginDraggingWidget(WidgetCell v)205     private boolean beginDraggingWidget(WidgetCell v) {
206         if (TestProtocol.sDebugTracing) {
207             Log.d(TestProtocol.NO_DROP_TARGET, "2");
208         }
209         // Get the widget preview as the drag representation
210         WidgetImageView image = v.getWidgetView();
211 
212         // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
213         // we abort the drag.
214         if (image.getDrawable() == null && v.getAppWidgetHostViewPreview() == null) {
215             return false;
216         }
217 
218         PendingItemDragHelper dragHelper = new PendingItemDragHelper(v);
219         // RemoteViews are being rendered in AppWidgetHostView in WidgetCell. And thus, the scale of
220         // RemoteViews is equivalent to the AppWidgetHostView scale.
221         dragHelper.setRemoteViewsPreview(v.getRemoteViewsPreview(), v.getAppWidgetHostViewScale());
222         dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview());
223 
224         if (image.getDrawable() != null) {
225             int[] loc = new int[2];
226             getPopupContainer().getLocationInDragLayer(image, loc);
227 
228             dragHelper.startDrag(image.getBitmapBounds(), image.getDrawable().getIntrinsicWidth(),
229                     image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions());
230         } else {
231             NavigableAppWidgetHostView preview = v.getAppWidgetHostViewPreview();
232             int[] loc = new int[2];
233             getPopupContainer().getLocationInDragLayer(preview, loc);
234             Rect r = new Rect();
235             preview.getWorkspaceVisualDragBounds(r);
236             dragHelper.startDrag(r, preview.getMeasuredWidth(), preview.getMeasuredWidth(),
237                     new Point(loc[0], loc[1]), this, new DragOptions());
238         }
239         close(true);
240         return true;
241     }
242 
243     @Override
getIdleInterpolator()244     protected Interpolator getIdleInterpolator() {
245         return mActivityContext.getDeviceProfile().isTablet
246                 ? EMPHASIZED : super.getIdleInterpolator();
247     }
248 
249     //
250     // Drag related handling methods that implement {@link DragSource} interface.
251     //
252 
253     @Override
onDropCompleted(View target, DragObject d, boolean success)254     public void onDropCompleted(View target, DragObject d, boolean success) { }
255 
256 
onCloseComplete()257     protected void onCloseComplete() {
258         super.onCloseComplete();
259         clearNavBarColor();
260     }
261 
clearNavBarColor()262     protected void clearNavBarColor() {
263         getSystemUiController().updateUiState(
264                 SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0);
265     }
266 
setupNavBarColor()267     protected void setupNavBarColor() {
268         boolean isSheetDark = Themes.getAttrBoolean(getContext(), R.attr.isMainColorDark);
269         getSystemUiController().updateUiState(
270                 SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET,
271                 isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV);
272     }
273 
getSystemUiController()274     protected SystemUiController getSystemUiController() {
275         return mActivityContext.getSystemUiController();
276     }
277 
278     /**
279      * Show Widget tap toast prompting user to drag instead
280      */
showWidgetToast(Context context, Toast toast)281     public static Toast showWidgetToast(Context context, Toast toast) {
282         // Let the user know that they have to long press to add a widget
283         if (toast != null) {
284             toast.cancel();
285         }
286 
287         CharSequence msg = Utilities.wrapForTts(
288                 context.getText(R.string.long_press_widget_to_add),
289                 context.getString(R.string.long_accessible_way_to_add));
290         toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
291         toast.show();
292         return toast;
293     }
294 
295     /**
296      * Show shortcut tap toast prompting user to drag instead.
297      */
showShortcutToast(Context context, Toast toast)298     private static Toast showShortcutToast(Context context, Toast toast) {
299         // Let the user know that they have to long press to add a widget
300         if (toast != null) {
301             toast.cancel();
302         }
303 
304         CharSequence msg = Utilities.wrapForTts(
305                 context.getText(R.string.long_press_shortcut_to_add),
306                 context.getString(R.string.long_accessible_way_to_add_shortcut));
307         toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
308         toast.show();
309         return toast;
310     }
311 
312     /** Shows education tip on top center of {@code view} if view is laid out. */
313     @Nullable
showEducationTipOnViewIfPossible(@ullable View view)314     protected ArrowTipView showEducationTipOnViewIfPossible(@Nullable View view) {
315         if (view == null || !ViewCompat.isLaidOut(view)) {
316             return null;
317         }
318         int[] coords = new int[2];
319         view.getLocationOnScreen(coords);
320         ArrowTipView arrowTipView =
321                 new ArrowTipView(mActivityContext,  /* isPointingUp= */ false).showAtLocation(
322                         getContext().getString(R.string.long_press_widget_to_add),
323                         /* arrowXCoord= */coords[0] + view.getWidth() / 2,
324                         /* yCoord= */coords[1]);
325         if (arrowTipView != null) {
326             mActivityContext.getSharedPrefs().edit()
327                     .putBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, true).apply();
328         }
329         return arrowTipView;
330     }
331 
332     /** Returns {@code true} if tip has previously been shown on any of {@link BaseWidgetSheet}. */
hasSeenEducationTip()333     protected boolean hasSeenEducationTip() {
334         return mActivityContext.getSharedPrefs().getBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, false)
335                 || Utilities.isRunningInTestHarness();
336     }
337 
338     @Override
setTranslationShift(float translationShift)339     protected void setTranslationShift(float translationShift) {
340         super.setTranslationShift(translationShift);
341         Launcher launcher = ActivityContext.lookupContext(getContext());
342         launcher.onWidgetsTransition(1 - translationShift);
343     }
344 }
345