blob: ebdfbeade99d21560228e545c9d95f00c1452269 [file] [log] [blame]
/*
* 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;
}
}
}