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