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 Drawable badgeShadow = overlayableRes.getDrawable( 110 com.android.internal.R.drawable.ic_corp_icon_badge_shadow); 111 112 Drawable badgeColor = overlayableRes.getDrawable( 113 com.android.internal.R.drawable.ic_corp_icon_badge_color) 114 .getConstantState().newDrawable().mutate(); 115 116 Drawable badgeForeground = overlayableRes.getDrawable(foregroundRes); 117 badgeForeground.setTint(backgroundColor); 118 119 Drawable[] drawables = base == null 120 ? new Drawable[] {badgeShadow, badgeColor, badgeForeground } 121 : new Drawable[] {base, badgeShadow, badgeColor, badgeForeground }; 122 return new LayerDrawable(drawables); 123 } 124 125 /** 126 * A drawable which draws a shadow bitmap behind a drawable 127 */ 128 private static class ShadowDrawable extends DrawableWrapper { 129 130 final MyConstantState mState; 131 ShadowDrawable(Bitmap shadow, Drawable dr)132 public ShadowDrawable(Bitmap shadow, Drawable dr) { 133 super(dr); 134 mState = new MyConstantState(shadow, dr.getConstantState()); 135 } 136 ShadowDrawable(MyConstantState state)137 ShadowDrawable(MyConstantState state) { 138 super(state.mChildState.newDrawable()); 139 mState = state; 140 } 141 142 @Override getConstantState()143 public ConstantState getConstantState() { 144 return mState; 145 } 146 147 @Override draw(Canvas canvas)148 public void draw(Canvas canvas) { 149 Rect bounds = getBounds(); 150 canvas.drawBitmap(mState.mShadow, null, bounds, mState.mPaint); 151 canvas.save(); 152 // Ratio of child drawable size to shadow bitmap size 153 float factor = 1 / (1 + 2 * ICON_SIZE_BLUR_FACTOR + ICON_SIZE_KEY_SHADOW_DELTA_FACTOR); 154 155 canvas.translate( 156 bounds.width() * factor * 157 (ICON_SIZE_BLUR_FACTOR + ICON_SIZE_KEY_SHADOW_DELTA_FACTOR / 2), 158 bounds.height() * factor * ICON_SIZE_BLUR_FACTOR); 159 canvas.scale(factor, factor); 160 super.draw(canvas); 161 canvas.restore(); 162 } 163 164 private static class MyConstantState extends ConstantState { 165 166 final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 167 final Bitmap mShadow; 168 final ConstantState mChildState; 169 MyConstantState(Bitmap shadow, ConstantState childState)170 MyConstantState(Bitmap shadow, ConstantState childState) { 171 mShadow = shadow; 172 mChildState = childState; 173 } 174 175 @Override newDrawable()176 public Drawable newDrawable() { 177 return new ShadowDrawable(this); 178 } 179 180 @Override getChangingConfigurations()181 public int getChangingConfigurations() { 182 return mChildState.getChangingConfigurations(); 183 } 184 } 185 } 186 } 187