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.ColorFilter; 25 import android.graphics.Paint; 26 import android.graphics.Path; 27 import android.graphics.Path.Direction; 28 import android.graphics.Picture; 29 import android.graphics.PixelFormat; 30 import android.graphics.Point; 31 import android.graphics.Rect; 32 import android.graphics.drawable.AdaptiveIconDrawable; 33 import android.graphics.drawable.Drawable; 34 import android.os.Build; 35 import android.util.Log; 36 37 import androidx.annotation.Nullable; 38 import androidx.annotation.UiThread; 39 40 import com.android.launcher3.folder.FolderIcon; 41 import com.android.launcher3.icons.BitmapRenderer; 42 import com.android.launcher3.util.Preconditions; 43 import com.android.launcher3.views.ActivityContext; 44 45 /** 46 * {@link AdaptiveIconDrawable} representation of a {@link FolderIcon} 47 */ 48 @TargetApi(Build.VERSION_CODES.O) 49 public class FolderAdaptiveIcon extends AdaptiveIconDrawable { 50 private static final String TAG = "FolderAdaptiveIcon"; 51 52 private final Drawable mBadge; 53 private final Path mMask; 54 private final ConstantState mConstantState; 55 private static final Rect sTmpRect = new Rect(); 56 FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask)57 private FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask) { 58 super(bg, fg); 59 mBadge = badge; 60 mMask = mask; 61 62 mConstantState = new MyConstantState(bg.getConstantState(), fg.getConstantState(), 63 badge.getConstantState(), mask); 64 } 65 66 @Override getIconMask()67 public Path getIconMask() { 68 return mMask; 69 } 70 getBadge()71 public Drawable getBadge() { 72 return mBadge; 73 } 74 createFolderAdaptiveIcon( ActivityContext activity, int folderId, Point size)75 public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon( 76 ActivityContext activity, int folderId, Point size) { 77 Preconditions.assertNonUiThread(); 78 79 // assume square 80 if (size.x != size.y) { 81 return null; 82 } 83 int requestedSize = size.x; 84 85 // Only use the size actually needed for drawing the folder icon 86 int drawingSize = activity.getDeviceProfile().folderIconSizePx; 87 int foregroundSize = Math.max(requestedSize, drawingSize); 88 float shift = foregroundSize - requestedSize; 89 90 Picture background = new Picture(); 91 Picture foreground = new Picture(); 92 Picture badge = new Picture(); 93 94 Canvas bgCanvas = background.beginRecording(requestedSize, requestedSize); 95 Canvas badgeCanvas = badge.beginRecording(requestedSize, requestedSize); 96 97 Canvas fgCanvas = foreground.beginRecording(foregroundSize, foregroundSize); 98 fgCanvas.translate(shift, shift); 99 100 // Do not clip the folder drawing since the icon previews extend outside the background. 101 Path mask = new Path(); 102 mask.addRect(-shift, -shift, requestedSize + shift, requestedSize + shift, 103 Direction.CCW); 104 105 // Initialize the actual draw commands on the UI thread to avoid race conditions with 106 // FolderIcon draw pass 107 try { 108 MAIN_EXECUTOR.submit(() -> { 109 FolderIcon icon = activity.findFolderIcon(folderId); 110 if (icon == null) { 111 throw new IllegalArgumentException("Folder not found with id: " + folderId); 112 } 113 initLayersOnUiThread(icon, requestedSize, bgCanvas, fgCanvas, badgeCanvas); 114 }).get(); 115 } catch (Exception e) { 116 Log.e(TAG, "Unable to create folder icon", e); 117 return null; 118 } finally { 119 background.endRecording(); 120 foreground.endRecording(); 121 badge.endRecording(); 122 } 123 124 // Only convert foreground to a bitmap as it can contain multiple draw commands. Other 125 // layers either draw a nothing or a single draw call. 126 Bitmap fgBitmap = Bitmap.createBitmap(foreground); 127 Paint foregroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 128 129 // Do not use PictureDrawable as it moves the picture to the canvas bounds, whereas we want 130 // to draw it at (0,0) 131 return new FolderAdaptiveIcon( 132 new BitmapRendererDrawable(c -> c.drawPicture(background)), 133 new BitmapRendererDrawable( 134 c -> c.drawBitmap(fgBitmap, -shift, -shift, foregroundPaint)), 135 new BitmapRendererDrawable(c -> c.drawPicture(badge)), 136 mask); 137 } 138 139 @UiThread initLayersOnUiThread(FolderIcon icon, int size, Canvas backgroundCanvas, Canvas foregroundCanvas, Canvas badgeCanvas)140 private static void initLayersOnUiThread(FolderIcon icon, int size, 141 Canvas backgroundCanvas, Canvas foregroundCanvas, Canvas badgeCanvas) { 142 icon.getPreviewBounds(sTmpRect); 143 final int previewSize = sTmpRect.width(); 144 145 final int margin = (size - previewSize) / 2; 146 final float previewShiftX = -sTmpRect.left + margin; 147 final float previewShiftY = -sTmpRect.top + margin; 148 149 // Initialize badge, which consists of the outline stroke, shadow and dot; these 150 // must be rendered above the foreground 151 badgeCanvas.save(); 152 badgeCanvas.translate(previewShiftX, previewShiftY); 153 icon.drawDot(badgeCanvas); 154 badgeCanvas.restore(); 155 156 // Draw foreground 157 foregroundCanvas.save(); 158 foregroundCanvas.translate(previewShiftX, previewShiftY); 159 icon.getPreviewItemManager().draw(foregroundCanvas); 160 foregroundCanvas.restore(); 161 162 // Draw background 163 backgroundCanvas.save(); 164 backgroundCanvas.translate(previewShiftX, previewShiftY); 165 icon.getFolderBackground().drawBackground(backgroundCanvas); 166 backgroundCanvas.restore(); 167 } 168 169 @Override getConstantState()170 public ConstantState getConstantState() { 171 return mConstantState; 172 } 173 174 private static class MyConstantState extends ConstantState { 175 private final ConstantState mBg; 176 private final ConstantState mFg; 177 private final ConstantState mBadge; 178 private final Path mMask; 179 MyConstantState(ConstantState bg, ConstantState fg, ConstantState badge, Path mask)180 MyConstantState(ConstantState bg, ConstantState fg, ConstantState badge, Path mask) { 181 mBg = bg; 182 mFg = fg; 183 mBadge = badge; 184 mMask = mask; 185 } 186 187 @Override newDrawable()188 public Drawable newDrawable() { 189 return new FolderAdaptiveIcon(mBg.newDrawable(), mFg.newDrawable(), 190 mBadge.newDrawable(), mMask); 191 } 192 193 @Override getChangingConfigurations()194 public int getChangingConfigurations() { 195 return mBg.getChangingConfigurations() & mFg.getChangingConfigurations() 196 & mBadge.getChangingConfigurations(); 197 } 198 } 199 200 private static class BitmapRendererDrawable extends Drawable { 201 202 private final BitmapRenderer mRenderer; 203 BitmapRendererDrawable(BitmapRenderer renderer)204 BitmapRendererDrawable(BitmapRenderer renderer) { 205 mRenderer = renderer; 206 } 207 208 @Override draw(Canvas canvas)209 public void draw(Canvas canvas) { 210 mRenderer.draw(canvas); 211 } 212 213 @Override setAlpha(int i)214 public void setAlpha(int i) { } 215 216 @Override setColorFilter(ColorFilter colorFilter)217 public void setColorFilter(ColorFilter colorFilter) { } 218 219 @Override getOpacity()220 public int getOpacity() { 221 return PixelFormat.TRANSLUCENT; 222 } 223 224 @Override getConstantState()225 public ConstantState getConstantState() { 226 return new MyConstantState(mRenderer); 227 } 228 229 private static class MyConstantState extends ConstantState { 230 private final BitmapRenderer mRenderer; 231 MyConstantState(BitmapRenderer renderer)232 MyConstantState(BitmapRenderer renderer) { 233 mRenderer = renderer; 234 } 235 236 @Override newDrawable()237 public Drawable newDrawable() { 238 return new BitmapRendererDrawable(mRenderer); 239 } 240 241 @Override getChangingConfigurations()242 public int getChangingConfigurations() { 243 return 0; 244 } 245 } 246 } 247 } 248