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 package android.util;
17 
18 import android.content.Context;
19 import android.content.res.Resources;
20 import android.graphics.Bitmap;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.Paint;
24 import android.graphics.Rect;
25 import android.graphics.drawable.AdaptiveIconDrawable;
26 import android.graphics.drawable.Drawable;
27 import android.graphics.drawable.DrawableWrapper;
28 import android.graphics.drawable.LayerDrawable;
29 
30 /**
31  * Utility class to handle icon treatments (e.g., shadow generation) for the Launcher icons.
32  * @hide
33  */
34 public final class LauncherIcons {
35 
36     // Percent of actual icon size
37     private static final float ICON_SIZE_BLUR_FACTOR = 0.5f/48;
38     // Percent of actual icon size
39     private static final float ICON_SIZE_KEY_SHADOW_DELTA_FACTOR = 1f/48;
40 
41     private static final int KEY_SHADOW_ALPHA = 61;
42     private static final int AMBIENT_SHADOW_ALPHA = 30;
43 
44     private final SparseArray<Bitmap> mShadowCache = new SparseArray<>();
45     private final int mIconSize;
46     private final Resources mRes;
47 
LauncherIcons(Context context)48     public LauncherIcons(Context context) {
49         mRes = context.getResources();
50         mIconSize = mRes.getDimensionPixelSize(android.R.dimen.app_icon_size);
51     }
52 
wrapIconDrawableWithShadow(Drawable drawable)53     public Drawable wrapIconDrawableWithShadow(Drawable drawable) {
54         if (!(drawable instanceof AdaptiveIconDrawable)) {
55             return drawable;
56         }
57         Bitmap shadow = getShadowBitmap((AdaptiveIconDrawable) drawable);
58         return new ShadowDrawable(shadow, drawable);
59     }
60 
getShadowBitmap(AdaptiveIconDrawable d)61     private Bitmap getShadowBitmap(AdaptiveIconDrawable d) {
62         int shadowSize = Math.max(mIconSize, d.getIntrinsicHeight());
63         synchronized (mShadowCache) {
64             Bitmap shadow = mShadowCache.get(shadowSize);
65             if (shadow != null) {
66                 return shadow;
67             }
68         }
69 
70         d.setBounds(0, 0, shadowSize, shadowSize);
71 
72         float blur = ICON_SIZE_BLUR_FACTOR * shadowSize;
73         float keyShadowDistance = ICON_SIZE_KEY_SHADOW_DELTA_FACTOR * shadowSize;
74 
75         int bitmapSize = (int) (shadowSize + 2 * blur + keyShadowDistance);
76         Bitmap shadow = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888);
77 
78         Canvas canvas = new Canvas(shadow);
79         canvas.translate(blur + keyShadowDistance / 2, blur);
80 
81         Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
82         paint.setColor(Color.TRANSPARENT);
83 
84         // Draw ambient shadow
85         paint.setShadowLayer(blur, 0, 0, AMBIENT_SHADOW_ALPHA << 24);
86         canvas.drawPath(d.getIconMask(), paint);
87 
88         // Draw key shadow
89         canvas.translate(0, keyShadowDistance);
90         paint.setShadowLayer(blur, 0, 0, KEY_SHADOW_ALPHA << 24);
91         canvas.drawPath(d.getIconMask(), paint);
92 
93         canvas.setBitmap(null);
94         synchronized (mShadowCache) {
95             mShadowCache.put(shadowSize, shadow);
96         }
97         return shadow;
98     }
99 
getBadgeDrawable(int foregroundRes, int backgroundColor)100     public Drawable getBadgeDrawable(int foregroundRes, int backgroundColor) {
101         return getBadgedDrawable(null, foregroundRes, backgroundColor);
102     }
103 
getBadgedDrawable(Drawable base, int foregroundRes, int backgroundColor)104     public Drawable getBadgedDrawable(Drawable base, int foregroundRes, int backgroundColor) {
105         Resources sysRes = Resources.getSystem();
106 
107         Drawable badgeShadow = sysRes.getDrawable(
108                 com.android.internal.R.drawable.ic_corp_icon_badge_shadow);
109 
110         Drawable badgeColor = sysRes.getDrawable(
111                 com.android.internal.R.drawable.ic_corp_icon_badge_color)
112                 .getConstantState().newDrawable().mutate();
113         badgeColor.setTint(backgroundColor);
114 
115         Drawable badgeForeground = sysRes.getDrawable(foregroundRes);
116 
117         Drawable[] drawables = base == null
118                 ? new Drawable[] {badgeShadow, badgeColor, badgeForeground }
119                 : new Drawable[] {base, badgeShadow, badgeColor, badgeForeground };
120         return new LayerDrawable(drawables);
121     }
122 
123     /**
124      * A drawable which draws a shadow bitmap behind a drawable
125      */
126     private static class ShadowDrawable extends DrawableWrapper {
127 
128         final MyConstantState mState;
129 
ShadowDrawable(Bitmap shadow, Drawable dr)130         public ShadowDrawable(Bitmap shadow, Drawable dr) {
131             super(dr);
132             mState = new MyConstantState(shadow, dr.getConstantState());
133         }
134 
ShadowDrawable(MyConstantState state)135         ShadowDrawable(MyConstantState state) {
136             super(state.mChildState.newDrawable());
137             mState = state;
138         }
139 
140         @Override
getConstantState()141         public ConstantState getConstantState() {
142             return mState;
143         }
144 
145         @Override
draw(Canvas canvas)146         public void draw(Canvas canvas) {
147             Rect bounds = getBounds();
148             canvas.drawBitmap(mState.mShadow, null, bounds, mState.mPaint);
149             canvas.save();
150             // Ratio of child drawable size to shadow bitmap size
151             float factor = 1 / (1 + 2 * ICON_SIZE_BLUR_FACTOR + ICON_SIZE_KEY_SHADOW_DELTA_FACTOR);
152 
153             canvas.translate(
154                     bounds.width() * factor *
155                             (ICON_SIZE_BLUR_FACTOR + ICON_SIZE_KEY_SHADOW_DELTA_FACTOR / 2),
156                     bounds.height() * factor * ICON_SIZE_BLUR_FACTOR);
157             canvas.scale(factor, factor);
158             super.draw(canvas);
159             canvas.restore();
160         }
161 
162         private static class MyConstantState extends ConstantState {
163 
164             final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
165             final Bitmap mShadow;
166             final ConstantState mChildState;
167 
MyConstantState(Bitmap shadow, ConstantState childState)168             MyConstantState(Bitmap shadow, ConstantState childState) {
169                 mShadow = shadow;
170                 mChildState = childState;
171             }
172 
173             @Override
newDrawable()174             public Drawable newDrawable() {
175                 return new ShadowDrawable(this);
176             }
177 
178             @Override
getChangingConfigurations()179             public int getChangingConfigurations() {
180                 return mChildState.getChangingConfigurations();
181             }
182         }
183     }
184 }
185