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.Matrix;
24 import android.graphics.Path;
25 import android.graphics.Point;
26 import android.graphics.Rect;
27 import android.graphics.drawable.AdaptiveIconDrawable;
28 import android.graphics.drawable.ColorDrawable;
29 import android.graphics.drawable.Drawable;
30 import android.os.Build;
31 import android.util.Log;
32 
33 import androidx.annotation.Nullable;
34 
35 import com.android.launcher3.Launcher;
36 import com.android.launcher3.folder.FolderIcon;
37 import com.android.launcher3.folder.PreviewBackground;
38 import com.android.launcher3.graphics.ShiftedBitmapDrawable;
39 import com.android.launcher3.icons.BitmapRenderer;
40 import com.android.launcher3.util.Preconditions;
41 
42 /**
43  * {@link AdaptiveIconDrawable} representation of a {@link FolderIcon}
44  */
45 @TargetApi(Build.VERSION_CODES.O)
46 public class FolderAdaptiveIcon extends AdaptiveIconDrawable {
47     private static final String TAG = "FolderAdaptiveIcon";
48 
49     private final Drawable mBadge;
50     private final Path mMask;
51     private final ConstantState mConstantState;
52     private static final Rect sTmpRect = new Rect();
53 
FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask)54     private FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask) {
55         super(bg, fg);
56         mBadge = badge;
57         mMask = mask;
58 
59         mConstantState = new MyConstantState(bg.getConstantState(), fg.getConstantState(),
60                 badge.getConstantState(), mask);
61     }
62 
63     @Override
getIconMask()64     public Path getIconMask() {
65         return mMask;
66     }
67 
getBadge()68     public Drawable getBadge() {
69         return mBadge;
70     }
71 
createFolderAdaptiveIcon( Launcher launcher, int folderId, Point dragViewSize)72     public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon(
73             Launcher launcher, int folderId, Point dragViewSize) {
74         Preconditions.assertNonUiThread();
75 
76         // Create the actual drawable on the UI thread to avoid race conditions with
77         // FolderIcon draw pass
78         try {
79             return MAIN_EXECUTOR.submit(() -> {
80                 FolderIcon icon = launcher.findFolderIcon(folderId);
81                 return icon == null ? null : createDrawableOnUiThread(icon, dragViewSize);
82 
83             }).get();
84         } catch (Exception e) {
85             Log.e(TAG, "Unable to create folder icon", e);
86             return null;
87         }
88     }
89 
createDrawableOnUiThread(FolderIcon icon, Point dragViewSize)90     private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon,
91                                                                Point dragViewSize) {
92         Preconditions.assertUIThread();
93 
94         icon.getPreviewBounds(sTmpRect);
95 
96         PreviewBackground bg = icon.getFolderBackground();
97 
98         // assume square
99         assert (dragViewSize.x == dragViewSize.y);
100         final int previewSize = sTmpRect.width();
101 
102         final int margin = (dragViewSize.x - previewSize) / 2;
103         final float previewShiftX = -sTmpRect.left + margin;
104         final float previewShiftY = -sTmpRect.top + margin;
105 
106         // Initialize badge, which consists of the outline stroke, shadow and dot; these
107         // must be rendered above the foreground
108         Bitmap badgeBmp = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
109                 (canvas) -> {
110                     canvas.save();
111                     canvas.translate(previewShiftX, previewShiftY);
112                     bg.drawShadow(canvas);
113                     bg.drawBackgroundStroke(canvas);
114                     icon.drawDot(canvas);
115                     canvas.restore();
116                 });
117 
118         // Initialize mask
119         Path mask = new Path();
120         Matrix m = new Matrix();
121         m.setTranslate(previewShiftX, previewShiftY);
122         bg.getClipPath().transform(m, mask);
123 
124         Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
125                 (canvas) -> {
126                     canvas.save();
127                     canvas.translate(previewShiftX, previewShiftY);
128                     icon.getPreviewItemManager().draw(canvas);
129                     canvas.restore();
130                 });
131 
132         ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBmp, 0, 0);
133         ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, 0, 0);
134 
135         return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask);
136     }
137 
138     @Override
getConstantState()139     public ConstantState getConstantState() {
140         return mConstantState;
141     }
142 
143     private static class MyConstantState extends ConstantState {
144         private final ConstantState mBg;
145         private final ConstantState mFg;
146         private final ConstantState mBadge;
147         private final Path mMask;
148 
MyConstantState(ConstantState bg, ConstantState fg, ConstantState badge, Path mask)149         MyConstantState(ConstantState bg, ConstantState fg, ConstantState badge, Path mask) {
150             mBg = bg;
151             mFg = fg;
152             mBadge = badge;
153             mMask = mask;
154         }
155 
156         @Override
newDrawable()157         public Drawable newDrawable() {
158             return new FolderAdaptiveIcon(mBg.newDrawable(), mFg.newDrawable(),
159                     mBadge.newDrawable(), mMask);
160         }
161 
162         @Override
getChangingConfigurations()163         public int getChangingConfigurations() {
164             return mBg.getChangingConfigurations() & mFg.getChangingConfigurations()
165                     & mBadge.getChangingConfigurations();
166         }
167     }
168 }
169