• 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.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