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