/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settingslib.notification; import android.annotation.ColorInt; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.util.IconDrawableFactory; import android.util.Log; import com.android.launcher3.icons.BaseIconFactory; import com.android.settingslib.R; import com.android.settingslib.Utils; /** * Factory for creating normalized conversation icons. * We are not using Launcher's IconFactory because conversation rendering only runs on the UI * thread, so there is no need to manage a pool across multiple threads. Launcher's rendering * also includes shadows, which are only appropriate on top of wallpaper, not embedded in UI. */ public class ConversationIconFactory extends BaseIconFactory { // Geometry of the various parts of the design. All values are 1dp on a 56x56dp icon grid. // Space is left around the "head" (main avatar) for // ........ // .HHHHHH. // .HHHrrrr // .HHHrBBr // ....rrrr // This is trying to recreate the view layout in notification_template_material_conversation.xml private static final float HEAD_SIZE = 52f; private static final float BADGE_SIZE = 12f; private static final float BADGE_CENTER = 46f; private static final float CIRCLE_MARGIN = 36f; private static final float BADGE_ORIGIN = HEAD_SIZE - BADGE_SIZE; // 40f private static final float BASE_ICON_SIZE = 56f; private static final float OUT_CIRCLE_DIA = (BASE_ICON_SIZE - CIRCLE_MARGIN); // 20f private static final float INN_CIRCLE_DIA = (float) Math.sqrt(2 * BADGE_SIZE * BADGE_SIZE) ; private static final float OUT_CIRCLE_RAD = OUT_CIRCLE_DIA / 2; private static final float INN_CIRCLE_RAD = INN_CIRCLE_DIA / 2; // Android draws strokes centered on the radius, so our actual radius is an avg of the outside // and inside of the ring stroke private static final float CIRCLE_RADIUS = INN_CIRCLE_RAD + ((OUT_CIRCLE_RAD - INN_CIRCLE_RAD) / 2); private static final float RING_STROKE_WIDTH = (OUT_CIRCLE_DIA - INN_CIRCLE_DIA) / 2; final LauncherApps mLauncherApps; final PackageManager mPackageManager; final IconDrawableFactory mIconDrawableFactory; private int mImportantConversationColor; public ConversationIconFactory(Context context, LauncherApps la, PackageManager pm, IconDrawableFactory iconDrawableFactory, int iconSizePx) { super(context, context.getResources().getConfiguration().densityDpi, iconSizePx); mLauncherApps = la; mPackageManager = pm; mIconDrawableFactory = iconDrawableFactory; mImportantConversationColor = context.getResources().getColor( R.color.important_conversation, null); } /** * Returns the conversation info drawable */ public Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) { return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi); } /** * Get the {@link Drawable} that represents the app icon, badged with the work profile icon * if appropriate. */ public Drawable getAppBadge(String packageName, int userId) { Drawable badge = null; try { final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser( packageName, PackageManager.GET_META_DATA, userId); badge = Utils.getBadgedIcon(mContext, appInfo); } catch (PackageManager.NameNotFoundException e) { badge = mPackageManager.getDefaultActivityIcon(); } return badge; } /** * Returns a {@link Drawable} for the entire conversation. The shortcut icon will be badged * with the launcher icon of the app specified by packageName. */ public Drawable getConversationDrawable(ShortcutInfo info, String packageName, int uid, boolean important) { return getConversationDrawable(getBaseIconDrawable(info), packageName, uid, important); } /** * Returns a {@link Drawable} for the entire conversation. The drawable will be badged * with the launcher icon of the app specified by packageName. */ public Drawable getConversationDrawable(Drawable baseIcon, String packageName, int uid, boolean important) { return new ConversationIconDrawable(baseIcon, getAppBadge(packageName, UserHandle.getUserId(uid)), mIconBitmapSize, mImportantConversationColor, important); } /** * Custom Drawable that overlays a badge drawable (e.g. notification small icon or app icon) on * a base icon (conversation/person avatar), plus decorations indicating conversation * importance. */ public static class ConversationIconDrawable extends Drawable { private Drawable mBaseIcon; private Drawable mBadgeIcon; private int mIconSize; private Paint mRingPaint; private boolean mShowRing; private Paint mPaddingPaint; public ConversationIconDrawable(Drawable baseIcon, Drawable badgeIcon, int iconSize, @ColorInt int ringColor, boolean showImportanceRing) { mBaseIcon = baseIcon; mBadgeIcon = badgeIcon; mIconSize = iconSize; mShowRing = showImportanceRing; mRingPaint = new Paint(); mRingPaint.setStyle(Paint.Style.STROKE); mRingPaint.setColor(ringColor); mPaddingPaint = new Paint(); mPaddingPaint.setStyle(Paint.Style.FILL_AND_STROKE); mPaddingPaint.setColor(Color.WHITE); } /** * Show or hide the importance ring. */ public void setImportant(boolean important) { if (important != mShowRing) { mShowRing = important; invalidateSelf(); } } @Override public int getIntrinsicWidth() { return mIconSize; } @Override public int getIntrinsicHeight() { return mIconSize; } // Similar to badgeWithDrawable, but relying on the bounds of each underlying drawable @Override public void draw(Canvas canvas) { final Rect bounds = getBounds(); // scale to our internal grid final float scale = bounds.width() / BASE_ICON_SIZE; final int ringStrokeWidth = (int) (RING_STROKE_WIDTH * scale); final int headSize = (int) (HEAD_SIZE * scale); final int badgePadding = (int) (BADGE_ORIGIN * scale); final int badgeCenter = (int) (BADGE_CENTER * scale); mPaddingPaint.setStrokeWidth(ringStrokeWidth); final float radius = (int) (CIRCLE_RADIUS * scale); // stroke outside if (mBaseIcon != null) { mBaseIcon.setBounds(0, 0, headSize , headSize); mBaseIcon.draw(canvas); } else { Log.w("ConversationIconFactory", "ConversationIconDrawable has null base icon"); } if (mBadgeIcon != null) { canvas.drawCircle(badgeCenter, badgeCenter, radius, mPaddingPaint); mBadgeIcon.setBounds( badgePadding, badgePadding, headSize, headSize); mBadgeIcon.draw(canvas); } else { Log.w("ConversationIconFactory", "ConversationIconDrawable has null badge icon"); } if (mShowRing) { mRingPaint.setStrokeWidth(ringStrokeWidth); canvas.drawCircle(badgeCenter, badgeCenter, radius, mRingPaint); } } @Override public void setAlpha(int alpha) { // unimplemented } @Override public void setColorFilter(ColorFilter colorFilter) { // unimplemented } @Override public int getOpacity() { return 0; } } }