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