1 /* 2 * Copyright (C) 2023 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 17 package com.android.launcher3.icons; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.content.res.Resources.Theme; 22 import android.graphics.Bitmap; 23 import android.graphics.Canvas; 24 import android.graphics.Color; 25 import android.graphics.ColorFilter; 26 import android.graphics.ColorMatrix; 27 import android.graphics.ColorMatrixColorFilter; 28 import android.graphics.Matrix; 29 import android.graphics.Paint; 30 import android.graphics.Path; 31 import android.graphics.Rect; 32 import android.graphics.RectF; 33 import android.graphics.drawable.Drawable; 34 import android.graphics.drawable.DrawableWrapper; 35 36 import androidx.annotation.ColorInt; 37 import androidx.annotation.NonNull; 38 import androidx.annotation.Nullable; 39 import androidx.annotation.VisibleForTesting; 40 import androidx.core.graphics.ColorUtils; 41 42 /** 43 * A drawable used for drawing user badge. It draws a circle around the actual badge, 44 * and has support for theming. 45 */ 46 public class UserBadgeDrawable extends DrawableWrapper { 47 48 private static final float VIEWPORT_SIZE = 24; 49 private static final float CENTER = VIEWPORT_SIZE / 2; 50 51 private static final float BG_RADIUS = 11; 52 private static final float SHADOW_RADIUS = 11.5f; 53 private static final float SHADOW_OFFSET_Y = 0.25f; 54 55 @VisibleForTesting 56 static final int SHADOW_COLOR = 0x11000000; 57 58 private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 59 60 private final int mBaseColor; 61 private final int mBgColor; 62 private boolean mShouldDrawBackground = true; 63 @Nullable private Path mShape; 64 65 private Matrix mShapeMatrix = new Matrix(); 66 67 @VisibleForTesting 68 public final boolean mIsThemed; 69 UserBadgeDrawable(Context context, int badgeRes, int colorRes, boolean isThemed, @Nullable Path shape)70 public UserBadgeDrawable(Context context, int badgeRes, int colorRes, boolean isThemed, 71 @Nullable Path shape) { 72 super(context.getDrawable(badgeRes)); 73 mShape = shape; 74 mShapeMatrix = new Matrix(); 75 if (mShape != null) { 76 mShapeMatrix.setRectToRect(new RectF(0f, 0f, 100f, 100f), 77 new RectF(0f, 0f, CENTER * 2, CENTER * 2), 78 Matrix.ScaleToFit.CENTER); 79 mShape.transform(mShapeMatrix); 80 } 81 mIsThemed = isThemed; 82 if (isThemed) { 83 mutate(); 84 mBaseColor = context.getColor(R.color.themed_badge_icon_color); 85 mBgColor = context.getColor(R.color.themed_badge_icon_background_color); 86 } else { 87 mBaseColor = context.getColor(colorRes); 88 mBgColor = Color.WHITE; 89 } 90 setTint(mBaseColor); 91 } 92 UserBadgeDrawable(Drawable base, int bgColor, int baseColor, boolean shouldDrawBackground)93 private UserBadgeDrawable(Drawable base, int bgColor, int baseColor, 94 boolean shouldDrawBackground) { 95 super(base); 96 mIsThemed = false; 97 mBgColor = bgColor; 98 mBaseColor = baseColor; 99 mShouldDrawBackground = shouldDrawBackground; 100 } 101 102 @Override draw(@onNull Canvas canvas)103 public void draw(@NonNull Canvas canvas) { 104 if (mShouldDrawBackground) { 105 Rect b = getBounds(); 106 int saveCount = canvas.save(); 107 canvas.translate(b.left, b.top); 108 canvas.scale(b.width() / VIEWPORT_SIZE, b.height() / VIEWPORT_SIZE); 109 110 mPaint.setColor(blendDrawableAlpha(SHADOW_COLOR)); 111 if (mShape != null) { 112 canvas.drawPath(mShape, mPaint); 113 } else { 114 canvas.drawCircle(CENTER, CENTER + SHADOW_OFFSET_Y, SHADOW_RADIUS, mPaint); 115 } 116 mPaint.setColor(blendDrawableAlpha(mBgColor)); 117 if (mShape != null) { 118 canvas.drawPath(mShape, mPaint); 119 } else { 120 canvas.drawCircle(CENTER, CENTER, BG_RADIUS, mPaint); 121 } 122 canvas.restoreToCount(saveCount); 123 } 124 super.draw(canvas); 125 } 126 blendDrawableAlpha(@olorInt int color)127 private @ColorInt int blendDrawableAlpha(@ColorInt int color) { 128 int alpha = (int) (Color.valueOf(color).alpha() * getAlpha()); 129 return ColorUtils.setAlphaComponent(color, alpha); 130 } 131 132 @Override setColorFilter(@ullable ColorFilter filter)133 public void setColorFilter(@Nullable ColorFilter filter) { 134 if (filter == null) { 135 super.setTint(mBaseColor); 136 } else if (filter instanceof ColorMatrixColorFilter cf) { 137 ColorMatrix cm = new ColorMatrix(); 138 cf.getColorMatrix(cm); 139 140 ColorMatrix cm2 = new ColorMatrix(); 141 float[] base = cm2.getArray(); 142 base[0] = Color.red(mBaseColor) / 255f; 143 base[6] = Color.green(mBaseColor) / 255f; 144 base[12] = Color.blue(mBaseColor) / 255f; 145 base[18] = Color.alpha(mBaseColor) / 255f; 146 cm2.postConcat(cm); 147 148 super.setColorFilter(new ColorMatrixColorFilter(cm2)); 149 } else { 150 // fail safe 151 Paint p = new Paint(); 152 p.setColorFilter(filter); 153 Bitmap b = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); 154 new Canvas(b).drawPaint(p); 155 super.setTint(b.getPixel(0, 0)); 156 } 157 } 158 setShouldDrawBackground(boolean shouldDrawBackground)159 public void setShouldDrawBackground(boolean shouldDrawBackground) { 160 mutate(); 161 mShouldDrawBackground = shouldDrawBackground; 162 } 163 164 @Override getConstantState()165 public ConstantState getConstantState() { 166 return new MyConstantState( 167 getDrawable().getConstantState(), mBgColor, mBaseColor, mShouldDrawBackground); 168 } 169 170 private static class MyConstantState extends ConstantState { 171 172 private final ConstantState mBase; 173 private final int mBgColor; 174 private final int mBaseColor; 175 private final boolean mShouldDrawBackground; 176 MyConstantState(ConstantState base, int bgColor, int baseColor, boolean shouldDrawBackground)177 MyConstantState(ConstantState base, int bgColor, int baseColor, 178 boolean shouldDrawBackground) { 179 mBase = base; 180 mBgColor = bgColor; 181 mBaseColor = baseColor; 182 mShouldDrawBackground = shouldDrawBackground; 183 } 184 185 @Override getChangingConfigurations()186 public int getChangingConfigurations() { 187 return mBase.getChangingConfigurations(); 188 } 189 190 @Override 191 @NonNull newDrawable()192 public Drawable newDrawable() { 193 return new UserBadgeDrawable( 194 mBase.newDrawable(), mBgColor, mBaseColor, mShouldDrawBackground); 195 } 196 197 @Override 198 @NonNull newDrawable(Resources res)199 public Drawable newDrawable(Resources res) { 200 return new UserBadgeDrawable( 201 mBase.newDrawable(res), mBgColor, mBaseColor, mShouldDrawBackground); 202 } 203 204 @Override 205 @NonNull newDrawable(Resources res, Theme theme)206 public Drawable newDrawable(Resources res, Theme theme) { 207 return new UserBadgeDrawable( 208 mBase.newDrawable(res, theme), mBgColor, mBaseColor, mShouldDrawBackground); 209 } 210 } 211 } 212