1 /*
2  * Copyright (C) 2016 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.widget.util.WidgetSizes.getWidgetSizePx;
20 
21 import android.graphics.Bitmap;
22 import android.graphics.Canvas;
23 import android.graphics.Paint;
24 import android.graphics.Point;
25 import android.graphics.Rect;
26 import android.graphics.drawable.Drawable;
27 import android.util.Size;
28 import android.view.View;
29 import android.view.View.MeasureSpec;
30 import android.widget.RemoteViews;
31 
32 import androidx.annotation.Nullable;
33 
34 import com.android.launcher3.DeviceProfile;
35 import com.android.launcher3.DragSource;
36 import com.android.launcher3.Launcher;
37 import com.android.launcher3.LauncherAppState;
38 import com.android.launcher3.PendingAddItemInfo;
39 import com.android.launcher3.R;
40 import com.android.launcher3.dragndrop.DragOptions;
41 import com.android.launcher3.dragndrop.DraggableView;
42 import com.android.launcher3.graphics.DragPreviewProvider;
43 import com.android.launcher3.icons.BaseIconFactory;
44 import com.android.launcher3.icons.FastBitmapDrawable;
45 import com.android.launcher3.icons.LauncherIcons;
46 import com.android.launcher3.icons.RoundDrawableWrapper;
47 
48 /**
49  * Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts
50  * dragged from the widget tray.
51  */
52 public class PendingItemDragHelper extends DragPreviewProvider {
53 
54     private static final float MAX_WIDGET_SCALE = 1.25f;
55 
56     private final PendingAddItemInfo mAddInfo;
57     private int[] mEstimatedCellSize;
58 
59     @Nullable private RemoteViews mRemoteViewsPreview;
60     private float mRemoteViewsPreviewScale = 1f;
61     @Nullable private NavigableAppWidgetHostView mAppWidgetHostViewPreview;
62     private final float mEnforcedRoundedCornersForWidget;
63 
PendingItemDragHelper(View view)64     public PendingItemDragHelper(View view) {
65         super(view);
66         mAddInfo = (PendingAddItemInfo) view.getTag();
67         mEnforcedRoundedCornersForWidget = RoundedCornerEnforcement.computeEnforcedRadius(
68                 view.getContext());
69     }
70 
71     /**
72      * Sets a {@link RemoteViews} which shows an app widget preview provided by app developers in
73      * the pin widget flow.
74      */
setRemoteViewsPreview(@ullable RemoteViews remoteViewsPreview, float previewScale)75     public void setRemoteViewsPreview(@Nullable RemoteViews remoteViewsPreview,
76             float previewScale) {
77         mRemoteViewsPreview = remoteViewsPreview;
78         mRemoteViewsPreviewScale = previewScale;
79     }
80 
81     /** Sets a {@link NavigableAppWidgetHostView} which shows a preview layout of an app widget. */
setAppWidgetHostViewPreview( @ullable NavigableAppWidgetHostView appWidgetHostViewPreview)82     public void setAppWidgetHostViewPreview(
83             @Nullable NavigableAppWidgetHostView appWidgetHostViewPreview) {
84         mAppWidgetHostViewPreview = appWidgetHostViewPreview;
85     }
86 
87     /**
88      * Starts the drag for the pending item associated with the view.
89      *
90      * @param previewBounds The bounds where the image was displayed,
91      *                      {@link WidgetImageView#getBitmapBounds()}
92      * @param previewBitmapWidth The actual width of the bitmap displayed in the view.
93      * @param previewViewWidth The width of {@link WidgetImageView} displaying the preview
94      * @param screenPos Position of {@link WidgetImageView} on the screen
95      */
startDrag(Rect previewBounds, int previewBitmapWidth, int previewViewWidth, Point screenPos, DragSource source, DragOptions options)96     public void startDrag(Rect previewBounds, int previewBitmapWidth, int previewViewWidth,
97             Point screenPos, DragSource source, DragOptions options) {
98         final Launcher launcher = Launcher.getLauncher(mView.getContext());
99         LauncherAppState app = LauncherAppState.getInstance(launcher);
100 
101         Drawable preview = null;
102         final int previewWidth;
103         final int previewHeight;
104         final float scale;
105         final Rect dragRegion;
106 
107         mEstimatedCellSize = launcher.getWorkspace().estimateItemSize(mAddInfo);
108 
109         DraggableView draggableView;
110 
111         if (mAddInfo instanceof PendingAddWidgetInfo) {
112             PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) mAddInfo;
113 
114             int maxWidth = Math.min((int) (previewBitmapWidth * MAX_WIDGET_SCALE), mEstimatedCellSize[0]);
115 
116             int[] previewSizeBeforeScale = new int[1];
117 
118             if (mRemoteViewsPreview != null) {
119                 mAppWidgetHostViewPreview = new LauncherAppWidgetHostView(launcher);
120                 mAppWidgetHostViewPreview.setAppWidget(/* appWidgetId= */ -1,
121                         ((PendingAddWidgetInfo) mAddInfo).info);
122                 DeviceProfile deviceProfile = launcher.getDeviceProfile();
123                 mAppWidgetHostViewPreview.updateAppWidget(/* remoteViews= */ mRemoteViewsPreview);
124                 Size widgetSizes = getWidgetSizePx(deviceProfile, mAddInfo.spanX, mAddInfo.spanY);
125                 mAppWidgetHostViewPreview.measure(
126                         MeasureSpec.makeMeasureSpec(widgetSizes.getWidth(), MeasureSpec.EXACTLY),
127                         MeasureSpec.makeMeasureSpec(widgetSizes.getHeight(), MeasureSpec.EXACTLY));
128                 mAppWidgetHostViewPreview.setClipChildren(false);
129                 mAppWidgetHostViewPreview.setClipToPadding(false);
130                 mAppWidgetHostViewPreview.setScaleToFit(mRemoteViewsPreviewScale);
131             }
132             if (mAppWidgetHostViewPreview != null) {
133                 previewSizeBeforeScale[0] = mAppWidgetHostViewPreview.getMeasuredWidth();
134             }
135             if (preview == null && mAppWidgetHostViewPreview == null) {
136                 Drawable p = new FastBitmapDrawable(new DatabaseWidgetPreviewLoader(launcher)
137                         .generateWidgetPreview(
138                                 createWidgetInfo.info, maxWidth, previewSizeBeforeScale));
139                 if (RoundedCornerEnforcement.isRoundedCornerEnabled()) {
140                     p = new RoundDrawableWrapper(p, mEnforcedRoundedCornersForWidget);
141                 }
142                 preview = p;
143             }
144 
145             if (previewSizeBeforeScale[0] < previewBitmapWidth) {
146                 // The icon has extra padding around it.
147                 int padding = (previewBitmapWidth - previewSizeBeforeScale[0]) / 2;
148                 if (previewBitmapWidth > previewViewWidth) {
149                     padding = padding * previewViewWidth / previewBitmapWidth;
150                 }
151 
152                 previewBounds.left += padding;
153                 previewBounds.right -= padding;
154             }
155             if (mAppWidgetHostViewPreview != null) {
156                 float previewScale = mAppWidgetHostViewPreview.getScaleX();
157                 int widgetWidth = mAppWidgetHostViewPreview.getMeasuredWidth();
158                 int widgetHeight = mAppWidgetHostViewPreview.getMeasuredHeight();
159                 previewWidth = Math.round(widgetWidth * previewScale);
160                 previewHeight = Math.round(widgetHeight * previewScale);
161 
162                 previewBounds.offset(
163                         Math.round(widgetWidth * (previewScale - 1) / 2),
164                         Math.round(widgetHeight * (previewScale - 1) / 2));
165             } else {
166                 previewWidth = preview.getIntrinsicWidth();
167                 previewHeight = preview.getIntrinsicHeight();
168             }
169             scale = previewBounds.width() / (float) previewWidth;
170             launcher.getDragController().addDragListener(new WidgetHostViewLoader(launcher, mView));
171 
172             dragRegion = null;
173             draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_WIDGET);
174         } else {
175             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
176             Drawable icon = createShortcutInfo.getActivityInfo(launcher)
177                     .getFullResIcon(app.getIconCache());
178             LauncherIcons li = LauncherIcons.obtain(launcher);
179             preview = new FastBitmapDrawable(
180                     li.createScaledBitmap(icon, BaseIconFactory.MODE_DEFAULT));
181             previewWidth = preview.getIntrinsicWidth();
182             previewHeight = preview.getIntrinsicHeight();
183             li.recycle();
184             scale = ((float) launcher.getDeviceProfile().iconSizePx) / previewWidth;
185 
186             // Create a preview same as the workspace cell size and draw the icon at the
187             // appropriate position.
188             DeviceProfile dp = launcher.getDeviceProfile();
189             int iconSize = dp.iconSizePx;
190 
191             int padding = launcher.getResources()
192                     .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
193             previewBounds.left += padding;
194             previewBounds.top += padding;
195 
196             dragRegion = new Rect();
197             dragRegion.left = (mEstimatedCellSize[0] - iconSize) / 2;
198             dragRegion.right = dragRegion.left + iconSize;
199             dragRegion.top = (mEstimatedCellSize[1]
200                     - iconSize - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2;
201             dragRegion.bottom = dragRegion.top + iconSize;
202             draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
203         }
204 
205         int dragLayerX = screenPos.x + previewBounds.left
206                 + (int) ((scale * previewWidth - previewWidth) / 2);
207         int dragLayerY = screenPos.y + previewBounds.top
208                 + (int) ((scale * previewHeight - previewHeight) / 2);
209 
210         // Start the drag
211         if (mAppWidgetHostViewPreview != null) {
212             launcher.getDragController().startDrag(mAppWidgetHostViewPreview, draggableView,
213                     dragLayerX, dragLayerY, source, mAddInfo, dragRegion, scale, scale, options);
214         } else {
215             launcher.getDragController().startDrag(preview, draggableView, dragLayerX, dragLayerY,
216                     source, mAddInfo, dragRegion, scale, scale, options);
217         }
218     }
219 
220     @Override
convertPreviewToAlphaBitmap(Bitmap preview)221     protected Bitmap convertPreviewToAlphaBitmap(Bitmap preview) {
222         if (mAddInfo instanceof PendingAddShortcutInfo || mEstimatedCellSize == null) {
223             return super.convertPreviewToAlphaBitmap(preview);
224         }
225 
226         int w = mEstimatedCellSize[0];
227         int h = mEstimatedCellSize[1];
228         final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
229         Rect src = new Rect(0, 0, preview.getWidth(), preview.getHeight());
230 
231         float scaleFactor = Math.min((w - blurSizeOutline) / (float) preview.getWidth(),
232                 (h - blurSizeOutline) / (float) preview.getHeight());
233         int scaledWidth = (int) (scaleFactor * preview.getWidth());
234         int scaledHeight = (int) (scaleFactor * preview.getHeight());
235         Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
236 
237         // center the image
238         dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
239         new Canvas(b).drawBitmap(preview, src, dst, new Paint(Paint.FILTER_BITMAP_FLAG));
240         return b;
241     }
242 }
243