• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.graphics;
18 
19 import android.content.Context;
20 import android.graphics.Bitmap;
21 import android.graphics.BlurMaskFilter;
22 import android.graphics.Canvas;
23 import android.graphics.Paint;
24 import android.graphics.PorterDuff;
25 import android.graphics.PorterDuffXfermode;
26 import android.graphics.Rect;
27 import android.graphics.drawable.Drawable;
28 import android.os.Handler;
29 import android.view.View;
30 
31 import com.android.launcher3.BubbleTextView;
32 import com.android.launcher3.Launcher;
33 import com.android.launcher3.R;
34 import com.android.launcher3.config.FeatureFlags;
35 import com.android.launcher3.folder.FolderIcon;
36 import com.android.launcher3.util.UiThreadHelper;
37 import com.android.launcher3.widget.LauncherAppWidgetHostView;
38 
39 import java.nio.ByteBuffer;
40 
41 /**
42  * A utility class to generate preview bitmap for dragging.
43  */
44 public class DragPreviewProvider {
45 
46     private final Rect mTempRect = new Rect();
47 
48     protected final View mView;
49 
50     // The padding added to the drag view during the preview generation.
51     public final int previewPadding;
52 
53     protected final int blurSizeOutline;
54 
55     private OutlineGeneratorCallback mOutlineGeneratorCallback;
56     public Bitmap generatedDragOutline;
57 
DragPreviewProvider(View view)58     public DragPreviewProvider(View view) {
59         this(view, view.getContext());
60     }
61 
DragPreviewProvider(View view, Context context)62     public DragPreviewProvider(View view, Context context) {
63         mView = view;
64         blurSizeOutline =
65                 context.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
66 
67         if (mView instanceof BubbleTextView) {
68             Drawable d = ((BubbleTextView) mView).getIcon();
69             Rect bounds = getDrawableBounds(d);
70             previewPadding = blurSizeOutline - bounds.left - bounds.top;
71         } else {
72             previewPadding = blurSizeOutline;
73         }
74     }
75 
76     /**
77      * Draws the {@link #mView} into the given {@param destCanvas}.
78      */
drawDragView(Canvas destCanvas, float scale)79     protected void drawDragView(Canvas destCanvas, float scale) {
80         destCanvas.save();
81         destCanvas.scale(scale, scale);
82 
83         if (mView instanceof BubbleTextView) {
84             Drawable d = ((BubbleTextView) mView).getIcon();
85             Rect bounds = getDrawableBounds(d);
86             destCanvas.translate(blurSizeOutline / 2 - bounds.left,
87                     blurSizeOutline / 2 - bounds.top);
88             d.draw(destCanvas);
89         } else {
90             final Rect clipRect = mTempRect;
91             mView.getDrawingRect(clipRect);
92 
93             boolean textVisible = false;
94             if (mView instanceof FolderIcon) {
95                 // For FolderIcons the text can bleed into the icon area, and so we need to
96                 // hide the text completely (which can't be achieved by clipping).
97                 if (((FolderIcon) mView).getTextVisible()) {
98                     ((FolderIcon) mView).setTextVisible(false);
99                     textVisible = true;
100                 }
101             }
102             destCanvas.translate(-mView.getScrollX() + blurSizeOutline / 2,
103                     -mView.getScrollY() + blurSizeOutline / 2);
104             destCanvas.clipRect(clipRect);
105             mView.draw(destCanvas);
106 
107             // Restore text visibility of FolderIcon if necessary
108             if (textVisible) {
109                 ((FolderIcon) mView).setTextVisible(true);
110             }
111         }
112         destCanvas.restore();
113     }
114 
115     /**
116      * Returns a new bitmap to show when the {@link #mView} is being dragged around.
117      * Responsibility for the bitmap is transferred to the caller.
118      */
createDragBitmap()119     public Bitmap createDragBitmap() {
120         int width = mView.getWidth();
121         int height = mView.getHeight();
122 
123         if (mView instanceof BubbleTextView) {
124             Drawable d = ((BubbleTextView) mView).getIcon();
125             Rect bounds = getDrawableBounds(d);
126             width = bounds.width();
127             height = bounds.height();
128         } else if (mView instanceof LauncherAppWidgetHostView) {
129             float scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
130             width = (int) (mView.getWidth() * scale);
131             height = (int) (mView.getHeight() * scale);
132 
133             // Use software renderer for widgets as we know that they already work
134             return BitmapRenderer.createSoftwareBitmap(width + blurSizeOutline,
135                     height + blurSizeOutline, (c) -> drawDragView(c, scale));
136         }
137 
138         return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
139                 height + blurSizeOutline, (c) -> drawDragView(c, 1));
140     }
141 
generateDragOutline(Bitmap preview)142     public final void generateDragOutline(Bitmap preview) {
143         if (FeatureFlags.IS_DOGFOOD_BUILD && mOutlineGeneratorCallback != null) {
144             throw new RuntimeException("Drag outline generated twice");
145         }
146 
147         mOutlineGeneratorCallback = new OutlineGeneratorCallback(preview);
148         new Handler(UiThreadHelper.getBackgroundLooper()).post(mOutlineGeneratorCallback);
149     }
150 
getDrawableBounds(Drawable d)151     protected static Rect getDrawableBounds(Drawable d) {
152         Rect bounds = new Rect();
153         d.copyBounds(bounds);
154         if (bounds.width() == 0 || bounds.height() == 0) {
155             bounds.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
156         } else {
157             bounds.offsetTo(0, 0);
158         }
159         return bounds;
160     }
161 
getScaleAndPosition(Bitmap preview, int[] outPos)162     public float getScaleAndPosition(Bitmap preview, int[] outPos) {
163         float scale = Launcher.getLauncher(mView.getContext())
164                 .getDragLayer().getLocationInDragLayer(mView, outPos);
165         if (mView instanceof LauncherAppWidgetHostView) {
166             // App widgets are technically scaled, but are drawn at their expected size -- so the
167             // app widget scale should not affect the scale of the preview.
168             scale /= ((LauncherAppWidgetHostView) mView).getScaleToFit();
169         }
170 
171         outPos[0] = Math.round(outPos[0] -
172                 (preview.getWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
173         outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2
174                 - previewPadding / 2);
175         return scale;
176     }
177 
convertPreviewToAlphaBitmap(Bitmap preview)178     protected Bitmap convertPreviewToAlphaBitmap(Bitmap preview) {
179         return preview.copy(Bitmap.Config.ALPHA_8, true);
180     }
181 
182     private class OutlineGeneratorCallback implements Runnable {
183 
184         private final Bitmap mPreviewSnapshot;
185         private final Context mContext;
186 
OutlineGeneratorCallback(Bitmap preview)187         OutlineGeneratorCallback(Bitmap preview) {
188             mPreviewSnapshot = preview;
189             mContext = mView.getContext();
190         }
191 
192         @Override
run()193         public void run() {
194             Bitmap preview = convertPreviewToAlphaBitmap(mPreviewSnapshot);
195 
196             // We start by removing most of the alpha channel so as to ignore shadows, and
197             // other types of partial transparency when defining the shape of the object
198             byte[] pixels = new byte[preview.getWidth() * preview.getHeight()];
199             ByteBuffer buffer = ByteBuffer.wrap(pixels);
200             buffer.rewind();
201             preview.copyPixelsToBuffer(buffer);
202 
203             for (int i = 0; i < pixels.length; i++) {
204                 if ((pixels[i] & 0xFF) < 188) {
205                     pixels[i] = 0;
206                 }
207             }
208 
209             buffer.rewind();
210             preview.copyPixelsFromBuffer(buffer);
211 
212             final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
213             Canvas canvas = new Canvas();
214 
215             // calculate the outer blur first
216             paint.setMaskFilter(new BlurMaskFilter(blurSizeOutline, BlurMaskFilter.Blur.OUTER));
217             int[] outerBlurOffset = new int[2];
218             Bitmap thickOuterBlur = preview.extractAlpha(paint, outerBlurOffset);
219 
220             paint.setMaskFilter(new BlurMaskFilter(
221                     mContext.getResources().getDimension(R.dimen.blur_size_thin_outline),
222                     BlurMaskFilter.Blur.OUTER));
223             int[] brightOutlineOffset = new int[2];
224             Bitmap brightOutline = preview.extractAlpha(paint, brightOutlineOffset);
225 
226             // calculate the inner blur
227             canvas.setBitmap(preview);
228             canvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT);
229             paint.setMaskFilter(new BlurMaskFilter(blurSizeOutline, BlurMaskFilter.Blur.NORMAL));
230             int[] thickInnerBlurOffset = new int[2];
231             Bitmap thickInnerBlur = preview.extractAlpha(paint, thickInnerBlurOffset);
232 
233             // mask out the inner blur
234             paint.setMaskFilter(null);
235             paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
236             canvas.setBitmap(thickInnerBlur);
237             canvas.drawBitmap(preview, -thickInnerBlurOffset[0],
238                     -thickInnerBlurOffset[1], paint);
239             canvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(), paint);
240             canvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1], paint);
241 
242             // draw the inner and outer blur
243             paint.setXfermode(null);
244             canvas.setBitmap(preview);
245             canvas.drawColor(0, PorterDuff.Mode.CLEAR);
246             canvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1],
247                     paint);
248             canvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1], paint);
249 
250             // draw the bright outline
251             canvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1], paint);
252 
253             // cleanup
254             canvas.setBitmap(null);
255             brightOutline.recycle();
256             thickOuterBlur.recycle();
257             thickInnerBlur.recycle();
258 
259             generatedDragOutline = preview;
260         }
261     }
262 }
263