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.Launcher; 34 import com.android.launcher3.R; 35 import com.android.launcher3.config.FeatureFlags; 36 import com.android.launcher3.dragndrop.DraggableView; 37 import com.android.launcher3.icons.BitmapRenderer; 38 import com.android.launcher3.util.SafeCloseable; 39 import com.android.launcher3.widget.LauncherAppWidgetHostView; 40 41 import java.nio.ByteBuffer; 42 43 /** 44 * A utility class to generate preview bitmap for dragging. 45 */ 46 public class DragPreviewProvider { 47 48 private final Rect mTempRect = new Rect(); 49 50 protected final View mView; 51 52 // The padding added to the drag view during the preview generation. 53 public final int previewPadding; 54 55 public final int blurSizeOutline; 56 57 private OutlineGeneratorCallback mOutlineGeneratorCallback; 58 public Bitmap generatedDragOutline; 59 DragPreviewProvider(View view)60 public DragPreviewProvider(View view) { 61 this(view, view.getContext()); 62 } 63 DragPreviewProvider(View view, Context context)64 public DragPreviewProvider(View view, Context context) { 65 mView = view; 66 blurSizeOutline = 67 context.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline); 68 previewPadding = blurSizeOutline; 69 } 70 71 /** 72 * Draws the {@link #mView} into the given {@param destCanvas}. 73 */ drawDragView(Canvas destCanvas, float scale)74 protected void drawDragView(Canvas destCanvas, float scale) { 75 int saveCount = destCanvas.save(); 76 destCanvas.scale(scale, scale); 77 78 if (mView instanceof DraggableView) { 79 DraggableView dv = (DraggableView) mView; 80 try (SafeCloseable t = dv.prepareDrawDragView()) { 81 dv.getSourceVisualDragBounds(mTempRect); 82 destCanvas.translate(blurSizeOutline / 2 - mTempRect.left, 83 blurSizeOutline / 2 - mTempRect.top); 84 mView.draw(destCanvas); 85 } 86 } 87 destCanvas.restoreToCount(saveCount); 88 } 89 90 /** 91 * Returns a new bitmap to show when the {@link #mView} is being dragged around. 92 * Responsibility for the bitmap is transferred to the caller. 93 */ createDragBitmap()94 public Bitmap createDragBitmap() { 95 int width = 0; 96 int height = 0; 97 // Assume scaleX == scaleY, which is always the case for workspace items. 98 float scale = mView.getScaleX(); 99 if (mView instanceof DraggableView) { 100 ((DraggableView) mView).getSourceVisualDragBounds(mTempRect); 101 width = mTempRect.width(); 102 height = mTempRect.height(); 103 } else { 104 width = mView.getWidth(); 105 height = mView.getHeight(); 106 } 107 108 return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline, 109 height + blurSizeOutline, (c) -> drawDragView(c, scale)); 110 } 111 generateDragOutline(Bitmap preview)112 public final void generateDragOutline(Bitmap preview) { 113 if (FeatureFlags.IS_STUDIO_BUILD && mOutlineGeneratorCallback != null) { 114 throw new RuntimeException("Drag outline generated twice"); 115 } 116 117 mOutlineGeneratorCallback = new OutlineGeneratorCallback(preview); 118 UI_HELPER_EXECUTOR.post(mOutlineGeneratorCallback); 119 } 120 getDrawableBounds(Drawable d)121 protected static Rect getDrawableBounds(Drawable d) { 122 Rect bounds = new Rect(); 123 d.copyBounds(bounds); 124 if (bounds.width() == 0 || bounds.height() == 0) { 125 bounds.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); 126 } else { 127 bounds.offsetTo(0, 0); 128 } 129 return bounds; 130 } 131 getScaleAndPosition(Bitmap preview, int[] outPos)132 public float getScaleAndPosition(Bitmap preview, int[] outPos) { 133 float scale = Launcher.getLauncher(mView.getContext()) 134 .getDragLayer().getLocationInDragLayer(mView, outPos); 135 if (mView instanceof LauncherAppWidgetHostView) { 136 // App widgets are technically scaled, but are drawn at their expected size -- so the 137 // app widget scale should not affect the scale of the preview. 138 scale /= ((LauncherAppWidgetHostView) mView).getScaleToFit(); 139 } 140 141 outPos[0] = Math.round(outPos[0] - 142 (preview.getWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2); 143 outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2 144 - previewPadding / 2); 145 return scale; 146 } 147 convertPreviewToAlphaBitmap(Bitmap preview)148 protected Bitmap convertPreviewToAlphaBitmap(Bitmap preview) { 149 return preview.copy(Bitmap.Config.ALPHA_8, true); 150 } 151 152 private class OutlineGeneratorCallback implements Runnable { 153 154 private final Bitmap mPreviewSnapshot; 155 private final Context mContext; 156 private final boolean mIsIcon; 157 OutlineGeneratorCallback(Bitmap preview)158 OutlineGeneratorCallback(Bitmap preview) { 159 mPreviewSnapshot = preview; 160 mContext = mView.getContext(); 161 mIsIcon = mView instanceof BubbleTextView; 162 } 163 164 @Override run()165 public void run() { 166 Bitmap preview = convertPreviewToAlphaBitmap(mPreviewSnapshot); 167 if (mIsIcon) { 168 int size = Launcher.getLauncher(mContext).getDeviceProfile().iconSizePx; 169 preview = Bitmap.createScaledBitmap(preview, size, size, false); 170 } 171 //else case covers AppWidgetHost (doesn't drag/drop across different device profiles) 172 173 // We start by removing most of the alpha channel so as to ignore shadows, and 174 // other types of partial transparency when defining the shape of the object 175 byte[] pixels = new byte[preview.getWidth() * preview.getHeight()]; 176 ByteBuffer buffer = ByteBuffer.wrap(pixels); 177 buffer.rewind(); 178 preview.copyPixelsToBuffer(buffer); 179 180 for (int i = 0; i < pixels.length; i++) { 181 if ((pixels[i] & 0xFF) < 188) { 182 pixels[i] = 0; 183 } 184 } 185 186 buffer.rewind(); 187 preview.copyPixelsFromBuffer(buffer); 188 189 final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 190 Canvas canvas = new Canvas(); 191 192 // calculate the outer blur first 193 paint.setMaskFilter(new BlurMaskFilter(blurSizeOutline, BlurMaskFilter.Blur.OUTER)); 194 int[] outerBlurOffset = new int[2]; 195 Bitmap thickOuterBlur = preview.extractAlpha(paint, outerBlurOffset); 196 197 paint.setMaskFilter(new BlurMaskFilter( 198 mContext.getResources().getDimension(R.dimen.blur_size_thin_outline), 199 BlurMaskFilter.Blur.OUTER)); 200 int[] brightOutlineOffset = new int[2]; 201 Bitmap brightOutline = preview.extractAlpha(paint, brightOutlineOffset); 202 203 // calculate the inner blur 204 canvas.setBitmap(preview); 205 canvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT); 206 paint.setMaskFilter(new BlurMaskFilter(blurSizeOutline, BlurMaskFilter.Blur.NORMAL)); 207 int[] thickInnerBlurOffset = new int[2]; 208 Bitmap thickInnerBlur = preview.extractAlpha(paint, thickInnerBlurOffset); 209 210 // mask out the inner blur 211 paint.setMaskFilter(null); 212 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 213 canvas.setBitmap(thickInnerBlur); 214 canvas.drawBitmap(preview, -thickInnerBlurOffset[0], 215 -thickInnerBlurOffset[1], paint); 216 canvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(), paint); 217 canvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1], paint); 218 219 // draw the inner and outer blur 220 paint.setXfermode(null); 221 canvas.setBitmap(preview); 222 canvas.drawColor(0, PorterDuff.Mode.CLEAR); 223 canvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1], 224 paint); 225 canvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1], paint); 226 227 // draw the bright outline 228 canvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1], paint); 229 230 // cleanup 231 canvas.setBitmap(null); 232 brightOutline.recycle(); 233 thickOuterBlur.recycle(); 234 thickInnerBlur.recycle(); 235 236 generatedDragOutline = preview; 237 } 238 } 239 } 240