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.Matrix;
25 import android.graphics.Path;
26 import android.graphics.Point;
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.R;
37 import com.android.launcher3.folder.FolderIcon;
38 import com.android.launcher3.folder.PreviewBackground;
39 import com.android.launcher3.graphics.ShiftedBitmapDrawable;
40 import com.android.launcher3.icons.BitmapRenderer;
41 import com.android.launcher3.util.Preconditions;
42 
43 /**
44  * {@link AdaptiveIconDrawable} representation of a {@link FolderIcon}
45  */
46 @TargetApi(Build.VERSION_CODES.O)
47 public class FolderAdaptiveIcon extends AdaptiveIconDrawable {
48     private static final String TAG = "FolderAdaptiveIcon";
49 
50     private final Drawable mBadge;
51     private final Path mMask;
52     private final ConstantState mConstantState;
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         int margin = launcher.getResources()
76                 .getDimensionPixelSize(R.dimen.blur_size_medium_outline);
77 
78         // Allocate various bitmaps on the background thread, because why not!
79         int width = dragViewSize.x - margin;
80         int height = dragViewSize.y - margin;
81         if (width <= 0 || height <= 0) {
82             return null;
83         }
84         final Bitmap badge = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
85 
86         // Create the actual drawable on the UI thread to avoid race conditions with
87         // FolderIcon draw pass
88         try {
89             return MAIN_EXECUTOR.submit(() -> {
90                 FolderIcon icon = launcher.findFolderIcon(folderId);
91                 return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize);
92             }).get();
93         } catch (Exception e) {
94             Log.e(TAG, "Unable to create folder icon", e);
95             return null;
96         }
97     }
98 
99     /**
100      * Initializes various bitmaps on the UI thread and returns the final drawable.
101      */
createDrawableOnUiThread(FolderIcon icon, Bitmap badgeBitmap, Point dragViewSize)102     private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon,
103             Bitmap badgeBitmap, Point dragViewSize) {
104         Preconditions.assertUIThread();
105         float margin = icon.getResources().getDimension(R.dimen.blur_size_medium_outline) / 2;
106 
107         Canvas c = new Canvas();
108         PreviewBackground bg = icon.getFolderBackground();
109 
110         // Initialize badge
111         c.setBitmap(badgeBitmap);
112         bg.drawShadow(c);
113         bg.drawBackgroundStroke(c);
114         icon.drawDot(c);
115 
116         // Initialize preview
117         final float sizeScaleFactor = 1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction();
118         final int previewWidth = (int) (dragViewSize.x * sizeScaleFactor);
119         final int previewHeight = (int) (dragViewSize.y * sizeScaleFactor);
120 
121         final float shiftFactor = AdaptiveIconDrawable.getExtraInsetFraction() / sizeScaleFactor;
122         final float previewShiftX = shiftFactor * previewWidth;
123         final float previewShiftY = shiftFactor * previewHeight;
124 
125         Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight,
126                 (canvas) -> {
127                     int count = canvas.save();
128                     canvas.translate(previewShiftX, previewShiftY);
129                     icon.getPreviewItemManager().draw(canvas);
130                     canvas.restoreToCount(count);
131                 });
132 
133         // Initialize mask
134         Path mask = new Path();
135         Matrix m = new Matrix();
136         m.setTranslate(margin, margin);
137         bg.getClipPath().transform(m, mask);
138 
139         ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBitmap, margin, margin);
140         ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap,
141                 margin - previewShiftX, margin - previewShiftY);
142 
143         return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask);
144     }
145 
146     @Override
getConstantState()147     public ConstantState getConstantState() {
148         return mConstantState;
149     }
150 
151     private static class MyConstantState extends ConstantState {
152         private final ConstantState mBg;
153         private final ConstantState mFg;
154         private final ConstantState mBadge;
155         private final Path mMask;
156 
MyConstantState(ConstantState bg, ConstantState fg, ConstantState badge, Path mask)157         MyConstantState(ConstantState bg, ConstantState fg, ConstantState badge, Path mask) {
158             mBg = bg;
159             mFg = fg;
160             mBadge = badge;
161             mMask = mask;
162         }
163 
164         @Override
newDrawable()165         public Drawable newDrawable() {
166             return new FolderAdaptiveIcon(mBg.newDrawable(), mFg.newDrawable(),
167                     mBadge.newDrawable(), mMask);
168         }
169 
170         @Override
getChangingConfigurations()171         public int getChangingConfigurations() {
172             return mBg.getChangingConfigurations() & mFg.getChangingConfigurations()
173                     & mBadge.getChangingConfigurations();
174         }
175     }
176 }
177