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