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 */
16package android.util;
17
18import android.content.Context;
19import android.content.res.Resources;
20import android.graphics.Bitmap;
21import android.graphics.Canvas;
22import android.graphics.Color;
23import android.graphics.Paint;
24import android.graphics.Rect;
25import android.graphics.drawable.AdaptiveIconDrawable;
26import android.graphics.drawable.Drawable;
27import android.graphics.drawable.DrawableWrapper;
28import android.graphics.drawable.LayerDrawable;
29
30/**
31 * Utility class to handle icon treatments (e.g., shadow generation) for the Launcher icons.
32 * @hide
33 */
34public 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
48    public LauncherIcons(Context context) {
49        mRes = context.getResources();
50        mIconSize = mRes.getDimensionPixelSize(android.R.dimen.app_icon_size);
51    }
52
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
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
100    public Drawable getBadgeDrawable(int foregroundRes, int backgroundColor) {
101        return getBadgedDrawable(null, foregroundRes, backgroundColor);
102    }
103
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
114        Drawable badgeForeground = sysRes.getDrawable(foregroundRes);
115        badgeForeground.setTint(backgroundColor);
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
130        public ShadowDrawable(Bitmap shadow, Drawable dr) {
131            super(dr);
132            mState = new MyConstantState(shadow, dr.getConstantState());
133        }
134
135        ShadowDrawable(MyConstantState state) {
136            super(state.mChildState.newDrawable());
137            mState = state;
138        }
139
140        @Override
141        public ConstantState getConstantState() {
142            return mState;
143        }
144
145        @Override
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
168            MyConstantState(Bitmap shadow, ConstantState childState) {
169                mShadow = shadow;
170                mChildState = childState;
171            }
172
173            @Override
174            public Drawable newDrawable() {
175                return new ShadowDrawable(this);
176            }
177
178            @Override
179            public int getChangingConfigurations() {
180                return mChildState.getChangingConfigurations();
181            }
182        }
183    }
184}
185