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