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.Matrix; 24 import android.graphics.Path; 25 import android.graphics.Point; 26 import android.graphics.Rect; 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.folder.FolderIcon; 37 import com.android.launcher3.folder.PreviewBackground; 38 import com.android.launcher3.graphics.ShiftedBitmapDrawable; 39 import com.android.launcher3.icons.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 private final ConstantState mConstantState; 52 private static final Rect sTmpRect = new Rect(); 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 76 // Create the actual drawable on the UI thread to avoid race conditions with 77 // FolderIcon draw pass 78 try { 79 return MAIN_EXECUTOR.submit(() -> { 80 FolderIcon icon = launcher.findFolderIcon(folderId); 81 return icon == null ? null : createDrawableOnUiThread(icon, dragViewSize); 82 83 }).get(); 84 } catch (Exception e) { 85 Log.e(TAG, "Unable to create folder icon", e); 86 return null; 87 } 88 } 89 createDrawableOnUiThread(FolderIcon icon, Point dragViewSize)90 private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon, 91 Point dragViewSize) { 92 Preconditions.assertUIThread(); 93 94 icon.getPreviewBounds(sTmpRect); 95 96 PreviewBackground bg = icon.getFolderBackground(); 97 98 // assume square 99 assert (dragViewSize.x == dragViewSize.y); 100 final int previewSize = sTmpRect.width(); 101 102 final int margin = (dragViewSize.x - previewSize) / 2; 103 final float previewShiftX = -sTmpRect.left + margin; 104 final float previewShiftY = -sTmpRect.top + margin; 105 106 // Initialize badge, which consists of the outline stroke, shadow and dot; these 107 // must be rendered above the foreground 108 Bitmap badgeBmp = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y, 109 (canvas) -> { 110 canvas.save(); 111 canvas.translate(previewShiftX, previewShiftY); 112 bg.drawShadow(canvas); 113 bg.drawBackgroundStroke(canvas); 114 icon.drawDot(canvas); 115 canvas.restore(); 116 }); 117 118 // Initialize mask 119 Path mask = new Path(); 120 Matrix m = new Matrix(); 121 m.setTranslate(previewShiftX, previewShiftY); 122 bg.getClipPath().transform(m, mask); 123 124 Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y, 125 (canvas) -> { 126 canvas.save(); 127 canvas.translate(previewShiftX, previewShiftY); 128 icon.getPreviewItemManager().draw(canvas); 129 canvas.restore(); 130 }); 131 132 ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBmp, 0, 0); 133 ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, 0, 0); 134 135 return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask); 136 } 137 138 @Override getConstantState()139 public ConstantState getConstantState() { 140 return mConstantState; 141 } 142 143 private static class MyConstantState extends ConstantState { 144 private final ConstantState mBg; 145 private final ConstantState mFg; 146 private final ConstantState mBadge; 147 private final Path mMask; 148 MyConstantState(ConstantState bg, ConstantState fg, ConstantState badge, Path mask)149 MyConstantState(ConstantState bg, ConstantState fg, ConstantState badge, Path mask) { 150 mBg = bg; 151 mFg = fg; 152 mBadge = badge; 153 mMask = mask; 154 } 155 156 @Override newDrawable()157 public Drawable newDrawable() { 158 return new FolderAdaptiveIcon(mBg.newDrawable(), mFg.newDrawable(), 159 mBadge.newDrawable(), mMask); 160 } 161 162 @Override getChangingConfigurations()163 public int getChangingConfigurations() { 164 return mBg.getChangingConfigurations() & mFg.getChangingConfigurations() 165 & mBadge.getChangingConfigurations(); 166 } 167 } 168 } 169