1 /* 2 * Copyright (C) 2017 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.dragndrop; 18 19 import android.annotation.TargetApi; 20 import android.graphics.Bitmap; 21 import android.graphics.Canvas; 22 import android.graphics.ColorFilter; 23 import android.graphics.Matrix; 24 import android.graphics.Paint; 25 import android.graphics.Path; 26 import android.graphics.PixelFormat; 27 import android.graphics.Point; 28 import android.graphics.drawable.AdaptiveIconDrawable; 29 import android.graphics.drawable.ColorDrawable; 30 import android.graphics.drawable.Drawable; 31 import android.os.Build; 32 import android.util.Log; 33 34 import com.android.launcher3.Launcher; 35 import com.android.launcher3.MainThreadExecutor; 36 import com.android.launcher3.R; 37 import com.android.launcher3.folder.FolderIcon; 38 import com.android.launcher3.folder.PreviewBackground; 39 import com.android.launcher3.graphics.BitmapRenderer; 40 import com.android.launcher3.util.Preconditions; 41 42 /** 43 * {@link AdaptiveIconDrawable} representation of a {@link FolderIcon} 44 */ 45 @TargetApi(Build.VERSION_CODES.O) 46 public class FolderAdaptiveIcon extends AdaptiveIconDrawable { 47 private static final String TAG = "FolderAdaptiveIcon"; 48 49 private final Drawable mBadge; 50 private final Path mMask; 51 52 private FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask) { 53 super(bg, fg); 54 mBadge = badge; 55 mMask = mask; 56 } 57 58 @Override 59 public Path getIconMask() { 60 return mMask; 61 } 62 63 public Drawable getBadge() { 64 return mBadge; 65 } 66 67 public static FolderAdaptiveIcon createFolderAdaptiveIcon( 68 Launcher launcher, long folderId, Point dragViewSize) { 69 Preconditions.assertNonUiThread(); 70 int margin = launcher.getResources() 71 .getDimensionPixelSize(R.dimen.blur_size_medium_outline); 72 73 // Allocate various bitmaps on the background thread, because why not! 74 final Bitmap badge = Bitmap.createBitmap( 75 dragViewSize.x - margin, dragViewSize.y - margin, Bitmap.Config.ARGB_8888); 76 77 // Create the actual drawable on the UI thread to avoid race conditions with 78 // FolderIcon draw pass 79 try { 80 return new MainThreadExecutor().submit(() -> { 81 FolderIcon icon = launcher.findFolderIcon(folderId); 82 return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize); 83 }).get(); 84 } catch (Exception e) { 85 Log.e(TAG, "Unable to create folder icon", e); 86 return null; 87 } 88 } 89 90 /** 91 * Initializes various bitmaps on the UI thread and returns the final drawable. 92 */ 93 private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon, 94 Bitmap badgeBitmap, Point dragViewSize) { 95 Preconditions.assertUIThread(); 96 float margin = icon.getResources().getDimension(R.dimen.blur_size_medium_outline) / 2; 97 98 Canvas c = new Canvas(); 99 PreviewBackground bg = icon.getFolderBackground(); 100 101 // Initialize badge 102 c.setBitmap(badgeBitmap); 103 bg.drawShadow(c); 104 bg.drawBackgroundStroke(c); 105 icon.drawBadge(c); 106 107 // Initialize preview 108 final float sizeScaleFactor = 1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction(); 109 final int previewWidth = (int) (dragViewSize.x * sizeScaleFactor); 110 final int previewHeight = (int) (dragViewSize.y * sizeScaleFactor); 111 112 final float shiftFactor = AdaptiveIconDrawable.getExtraInsetFraction() / sizeScaleFactor; 113 final float previewShiftX = shiftFactor * previewWidth; 114 final float previewShiftY = shiftFactor * previewHeight; 115 116 Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight, 117 (canvas) -> { 118 int count = canvas.save(); 119 canvas.translate(previewShiftX, previewShiftY); 120 icon.getPreviewItemManager().draw(canvas); 121 canvas.restoreToCount(count); 122 }); 123 124 // Initialize mask 125 Path mask = new Path(); 126 Matrix m = new Matrix(); 127 m.setTranslate(margin, margin); 128 bg.getClipPath().transform(m, mask); 129 130 ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBitmap, margin, margin); 131 ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, 132 margin - previewShiftX, margin - previewShiftY); 133 134 return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask); 135 } 136 137 /** 138 * A simple drawable which draws a bitmap at a fixed position irrespective of the bounds 139 */ 140 private static class ShiftedBitmapDrawable extends Drawable { 141 142 private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 143 private final Bitmap mBitmap; 144 private final float mShiftX; 145 private final float mShiftY; 146 147 ShiftedBitmapDrawable(Bitmap bitmap, float shiftX, float shiftY) { 148 mBitmap = bitmap; 149 mShiftX = shiftX; 150 mShiftY = shiftY; 151 } 152 153 @Override 154 public void draw(Canvas canvas) { 155 canvas.drawBitmap(mBitmap, mShiftX, mShiftY, mPaint); 156 } 157 158 @Override 159 public void setAlpha(int i) { } 160 161 @Override 162 public void setColorFilter(ColorFilter colorFilter) { 163 mPaint.setColorFilter(colorFilter); 164 } 165 166 @Override 167 public int getOpacity() { 168 return PixelFormat.TRANSLUCENT; 169 } 170 } 171 } 172