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