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