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