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