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