• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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