Loading res/values/dimens.xml +2 −0 Original line number Diff line number Diff line Loading @@ -172,6 +172,8 @@ <!-- Icon badges (with notification counts) --> <dimen name="badge_size">24dp</dimen> <dimen name="badge_text_size">12dp</dimen> <dimen name="badge_small_padding">1dp</dimen> <dimen name="badge_large_padding">3dp</dimen> <dimen name="notification_icon_size">28dp</dimen> <dimen name="notification_footer_icon_size">24dp</dimen> <!-- (icon_size - secondary_icon_size) / 2 --> Loading src/com/android/launcher3/DeviceProfile.java +1 −3 Original line number Diff line number Diff line Loading @@ -196,9 +196,7 @@ public class DeviceProfile { hotseatBarBottomPaddingPx = 0; hotseatLandGutterPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_gutter_width); int badgeSize = res.getDimensionPixelSize(R.dimen.badge_size); int badgeTextSize = res.getDimensionPixelSize(R.dimen.badge_text_size); mBadgeRenderer = new BadgeRenderer(badgeSize, badgeTextSize); mBadgeRenderer = new BadgeRenderer(context); // Determine sizes. widthPx = width; Loading src/com/android/launcher3/badge/BadgeInfo.java +60 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,14 @@ package com.android.launcher3.badge; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.util.PackageUserKey; Loading @@ -29,12 +37,22 @@ public class BadgeInfo { /** Used to link this BadgeInfo to icons on the workspace and all apps */ private PackageUserKey mPackageUserKey; /** * The keys of the notifications that this badge represents. These keys can later be * used to retrieve {@link NotificationInfo}'s. */ private List<String> mNotificationKeys; /** This will only be initialized if the badge should display the notification icon. */ private NotificationInfo mNotificationInfo; /** * When retrieving the notification icon, we draw it into this shader, which can be clipped * as necessary when drawn in a badge. */ private Shader mNotificationIcon; public BadgeInfo(PackageUserKey packageUserKey) { mPackageUserKey = packageUserKey; mNotificationKeys = new ArrayList<>(); Loading Loading @@ -65,15 +83,55 @@ public class BadgeInfo { return mNotificationKeys.size(); } public void setNotificationToShow(@Nullable NotificationInfo notificationInfo) { mNotificationInfo = notificationInfo; mNotificationIcon = null; } public boolean hasNotificationToShow() { return mNotificationInfo != null; } /** * Returns a shader to set on a Paint that will draw the notification icon in a badge. * * The shader is cached until {@link #setNotificationToShow(NotificationInfo)} is called. */ public @Nullable Shader getNotificationIconForBadge(Context context, int badgeColor, int badgeSize, int badgePadding) { if (mNotificationInfo == null) { return null; } if (mNotificationIcon == null) { Drawable icon = mNotificationInfo.getIconForBackground(context, badgeColor) .getConstantState().newDrawable(); int iconSize = badgeSize - badgePadding * 2; icon.setBounds(0, 0, iconSize, iconSize); Bitmap iconBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(iconBitmap); canvas.translate(badgePadding, badgePadding); icon.draw(canvas); mNotificationIcon = new BitmapShader(iconBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); } return mNotificationIcon; } public boolean isIconLarge() { return mNotificationInfo != null && mNotificationInfo.isIconLarge(); } /** * Whether newBadge represents the same PackageUserKey as this badge, and icons with * this badge should be invalidated. So, for instance, if a badge has 3 notifications * and one of those notifications is updated, this method should return false because * the badge still says "3" and the contents of those notifications are only retrieved * upon long-click. This method always returns true when adding or removing notifications. * upon long-click. This method always returns true when adding or removing notifications, * or if the badge has a notification icon to show. */ public boolean shouldBeInvalidated(BadgeInfo newBadge) { return mPackageUserKey.equals(newBadge.mPackageUserKey) && getNotificationCount() != newBadge.getNotificationCount(); && (getNotificationCount() != newBadge.getNotificationCount() || hasNotificationToShow()); } } src/com/android/launcher3/badge/BadgeRenderer.java +55 −8 Original line number Diff line number Diff line Loading @@ -16,11 +16,16 @@ package com.android.launcher3.badge; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import com.android.launcher3.R; import com.android.launcher3.graphics.IconPalette; /** Loading @@ -31,15 +36,22 @@ public class BadgeRenderer { public int size; public int textSize; public IconDrawer largeIconDrawer; public IconDrawer smallIconDrawer; private final Context mContext; private final RectF mBackgroundRect = new RectF(); private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final int mTextHeight; public BadgeRenderer(int size, int textSize) { this.size = size; this.textSize = textSize; public BadgeRenderer(Context context) { mContext = context; Resources res = context.getResources(); size = res.getDimensionPixelSize(R.dimen.badge_size); textSize = res.getDimensionPixelSize(R.dimen.badge_text_size); largeIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_small_padding)); smallIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_large_padding)); mTextPaint.setTextAlign(Paint.Align.CENTER); mTextPaint.setTextSize(textSize); // Measure the text height. Loading @@ -61,6 +73,17 @@ public class BadgeRenderer { mBackgroundRect.set(iconBounds.right - size, iconBounds.top, iconBounds.right, iconBounds.top + size); canvas.drawOval(mBackgroundRect, mBackgroundPaint); IconDrawer iconDrawer = badgeInfo.isIconLarge() ? largeIconDrawer : smallIconDrawer; Shader icon = badgeInfo.getNotificationIconForBadge(mContext, palette.backgroundColor, size, iconDrawer.mPadding); if (icon != null) { // Draw the notification icon with padding. canvas.save(); canvas.translate(mBackgroundRect.left, mBackgroundRect.top); iconDrawer.drawIcon(icon, canvas); canvas.restore(); } else { // Draw the notification count. String notificationCount = String.valueOf(badgeInfo.getNotificationCount()); canvas.drawText(notificationCount, mBackgroundRect.centerX(), Loading @@ -68,3 +91,27 @@ public class BadgeRenderer { mTextPaint); } } /** Draws the notification icon with padding of a given size. */ private class IconDrawer { private final int mPadding; private final Bitmap mCircleClipBitmap; private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.FILTER_BITMAP_FLAG); public IconDrawer(int padding) { mPadding = padding; mCircleClipBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ALPHA_8); Canvas canvas = new Canvas(); canvas.setBitmap(mCircleClipBitmap); canvas.drawCircle(size / 2, size / 2, size / 2 - padding, mPaint); } public void drawIcon(Shader icon, Canvas canvas) { mPaint.setShader(icon); canvas.drawBitmap(mCircleClipBitmap, 0f, 0f, mPaint); mPaint.setShader(null); } } } src/com/android/launcher3/notification/NotificationInfo.java +26 −6 Original line number Diff line number Diff line Loading @@ -40,6 +40,11 @@ import com.android.launcher3.util.PackageUserKey; */ public class NotificationInfo implements View.OnClickListener { // TODO: use Notification constants directly. public static final int BADGE_ICON_NONE = 0; public static final int BADGE_ICON_SMALL = 1; public static final int BADGE_ICON_LARGE = 2; public final PackageUserKey packageUserKey; public final String notificationKey; public final CharSequence title; Loading @@ -48,9 +53,10 @@ public class NotificationInfo implements View.OnClickListener { public final boolean autoCancel; public final boolean dismissable; private final int mBadgeIcon; private final Drawable mIconDrawable; private boolean mShouldTintIcon; private int mIconColor; private boolean mIsIconLarge; /** * Extracts the data that we need from the StatusBarNotification. Loading @@ -61,17 +67,20 @@ public class NotificationInfo implements View.OnClickListener { Notification notification = statusBarNotification.getNotification(); title = notification.extras.getCharSequence(Notification.EXTRA_TITLE); text = notification.extras.getCharSequence(Notification.EXTRA_TEXT); mBadgeIcon = BADGE_ICON_LARGE; // TODO: get from the Notification // Load the icon. Since it is backed by ashmem, we won't copy the entire bitmap // into our process as long as we don't touch it and it exists in systemui. Icon icon = notification.getLargeIcon(); Icon icon = mBadgeIcon == BADGE_ICON_SMALL ? null : notification.getLargeIcon(); if (icon == null) { // Use the small icon. icon = notification.getSmallIcon(); mIconDrawable = icon.loadDrawable(context); mIconColor = statusBarNotification.getNotification().color; mShouldTintIcon = true; mIsIconLarge = false; } else { // Use the large icon. mIconDrawable = icon.loadDrawable(context); mShouldTintIcon = false; mIsIconLarge = true; } intent = notification.contentIntent; autoCancel = (notification.flags & Notification.FLAG_AUTO_CANCEL) != 0; Loading @@ -95,7 +104,8 @@ public class NotificationInfo implements View.OnClickListener { } public Drawable getIconForBackground(Context context, int background) { if (!mShouldTintIcon) { if (mIsIconLarge) { // Only small icons should be tinted. return mIconDrawable; } mIconColor = IconPalette.resolveContrastColor(context, mIconColor, background); Loading @@ -104,7 +114,17 @@ public class NotificationInfo implements View.OnClickListener { // get it set and invalidated properly. icon.setTintList(null); icon.setTint(mIconColor); mShouldTintIcon = false; return icon; } public boolean isIconLarge() { return mIsIconLarge; } public boolean shouldShowIconInBadge() { // If the icon we're using for this notification matches what the Notification // specified should show in the badge, then return true. return mIsIconLarge && mBadgeIcon == BADGE_ICON_LARGE || !mIsIconLarge && mBadgeIcon == BADGE_ICON_SMALL; } } Loading
res/values/dimens.xml +2 −0 Original line number Diff line number Diff line Loading @@ -172,6 +172,8 @@ <!-- Icon badges (with notification counts) --> <dimen name="badge_size">24dp</dimen> <dimen name="badge_text_size">12dp</dimen> <dimen name="badge_small_padding">1dp</dimen> <dimen name="badge_large_padding">3dp</dimen> <dimen name="notification_icon_size">28dp</dimen> <dimen name="notification_footer_icon_size">24dp</dimen> <!-- (icon_size - secondary_icon_size) / 2 --> Loading
src/com/android/launcher3/DeviceProfile.java +1 −3 Original line number Diff line number Diff line Loading @@ -196,9 +196,7 @@ public class DeviceProfile { hotseatBarBottomPaddingPx = 0; hotseatLandGutterPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_gutter_width); int badgeSize = res.getDimensionPixelSize(R.dimen.badge_size); int badgeTextSize = res.getDimensionPixelSize(R.dimen.badge_text_size); mBadgeRenderer = new BadgeRenderer(badgeSize, badgeTextSize); mBadgeRenderer = new BadgeRenderer(context); // Determine sizes. widthPx = width; Loading
src/com/android/launcher3/badge/BadgeInfo.java +60 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,14 @@ package com.android.launcher3.badge; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.util.PackageUserKey; Loading @@ -29,12 +37,22 @@ public class BadgeInfo { /** Used to link this BadgeInfo to icons on the workspace and all apps */ private PackageUserKey mPackageUserKey; /** * The keys of the notifications that this badge represents. These keys can later be * used to retrieve {@link NotificationInfo}'s. */ private List<String> mNotificationKeys; /** This will only be initialized if the badge should display the notification icon. */ private NotificationInfo mNotificationInfo; /** * When retrieving the notification icon, we draw it into this shader, which can be clipped * as necessary when drawn in a badge. */ private Shader mNotificationIcon; public BadgeInfo(PackageUserKey packageUserKey) { mPackageUserKey = packageUserKey; mNotificationKeys = new ArrayList<>(); Loading Loading @@ -65,15 +83,55 @@ public class BadgeInfo { return mNotificationKeys.size(); } public void setNotificationToShow(@Nullable NotificationInfo notificationInfo) { mNotificationInfo = notificationInfo; mNotificationIcon = null; } public boolean hasNotificationToShow() { return mNotificationInfo != null; } /** * Returns a shader to set on a Paint that will draw the notification icon in a badge. * * The shader is cached until {@link #setNotificationToShow(NotificationInfo)} is called. */ public @Nullable Shader getNotificationIconForBadge(Context context, int badgeColor, int badgeSize, int badgePadding) { if (mNotificationInfo == null) { return null; } if (mNotificationIcon == null) { Drawable icon = mNotificationInfo.getIconForBackground(context, badgeColor) .getConstantState().newDrawable(); int iconSize = badgeSize - badgePadding * 2; icon.setBounds(0, 0, iconSize, iconSize); Bitmap iconBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(iconBitmap); canvas.translate(badgePadding, badgePadding); icon.draw(canvas); mNotificationIcon = new BitmapShader(iconBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); } return mNotificationIcon; } public boolean isIconLarge() { return mNotificationInfo != null && mNotificationInfo.isIconLarge(); } /** * Whether newBadge represents the same PackageUserKey as this badge, and icons with * this badge should be invalidated. So, for instance, if a badge has 3 notifications * and one of those notifications is updated, this method should return false because * the badge still says "3" and the contents of those notifications are only retrieved * upon long-click. This method always returns true when adding or removing notifications. * upon long-click. This method always returns true when adding or removing notifications, * or if the badge has a notification icon to show. */ public boolean shouldBeInvalidated(BadgeInfo newBadge) { return mPackageUserKey.equals(newBadge.mPackageUserKey) && getNotificationCount() != newBadge.getNotificationCount(); && (getNotificationCount() != newBadge.getNotificationCount() || hasNotificationToShow()); } }
src/com/android/launcher3/badge/BadgeRenderer.java +55 −8 Original line number Diff line number Diff line Loading @@ -16,11 +16,16 @@ package com.android.launcher3.badge; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import com.android.launcher3.R; import com.android.launcher3.graphics.IconPalette; /** Loading @@ -31,15 +36,22 @@ public class BadgeRenderer { public int size; public int textSize; public IconDrawer largeIconDrawer; public IconDrawer smallIconDrawer; private final Context mContext; private final RectF mBackgroundRect = new RectF(); private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final int mTextHeight; public BadgeRenderer(int size, int textSize) { this.size = size; this.textSize = textSize; public BadgeRenderer(Context context) { mContext = context; Resources res = context.getResources(); size = res.getDimensionPixelSize(R.dimen.badge_size); textSize = res.getDimensionPixelSize(R.dimen.badge_text_size); largeIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_small_padding)); smallIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_large_padding)); mTextPaint.setTextAlign(Paint.Align.CENTER); mTextPaint.setTextSize(textSize); // Measure the text height. Loading @@ -61,6 +73,17 @@ public class BadgeRenderer { mBackgroundRect.set(iconBounds.right - size, iconBounds.top, iconBounds.right, iconBounds.top + size); canvas.drawOval(mBackgroundRect, mBackgroundPaint); IconDrawer iconDrawer = badgeInfo.isIconLarge() ? largeIconDrawer : smallIconDrawer; Shader icon = badgeInfo.getNotificationIconForBadge(mContext, palette.backgroundColor, size, iconDrawer.mPadding); if (icon != null) { // Draw the notification icon with padding. canvas.save(); canvas.translate(mBackgroundRect.left, mBackgroundRect.top); iconDrawer.drawIcon(icon, canvas); canvas.restore(); } else { // Draw the notification count. String notificationCount = String.valueOf(badgeInfo.getNotificationCount()); canvas.drawText(notificationCount, mBackgroundRect.centerX(), Loading @@ -68,3 +91,27 @@ public class BadgeRenderer { mTextPaint); } } /** Draws the notification icon with padding of a given size. */ private class IconDrawer { private final int mPadding; private final Bitmap mCircleClipBitmap; private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.FILTER_BITMAP_FLAG); public IconDrawer(int padding) { mPadding = padding; mCircleClipBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ALPHA_8); Canvas canvas = new Canvas(); canvas.setBitmap(mCircleClipBitmap); canvas.drawCircle(size / 2, size / 2, size / 2 - padding, mPaint); } public void drawIcon(Shader icon, Canvas canvas) { mPaint.setShader(icon); canvas.drawBitmap(mCircleClipBitmap, 0f, 0f, mPaint); mPaint.setShader(null); } } }
src/com/android/launcher3/notification/NotificationInfo.java +26 −6 Original line number Diff line number Diff line Loading @@ -40,6 +40,11 @@ import com.android.launcher3.util.PackageUserKey; */ public class NotificationInfo implements View.OnClickListener { // TODO: use Notification constants directly. public static final int BADGE_ICON_NONE = 0; public static final int BADGE_ICON_SMALL = 1; public static final int BADGE_ICON_LARGE = 2; public final PackageUserKey packageUserKey; public final String notificationKey; public final CharSequence title; Loading @@ -48,9 +53,10 @@ public class NotificationInfo implements View.OnClickListener { public final boolean autoCancel; public final boolean dismissable; private final int mBadgeIcon; private final Drawable mIconDrawable; private boolean mShouldTintIcon; private int mIconColor; private boolean mIsIconLarge; /** * Extracts the data that we need from the StatusBarNotification. Loading @@ -61,17 +67,20 @@ public class NotificationInfo implements View.OnClickListener { Notification notification = statusBarNotification.getNotification(); title = notification.extras.getCharSequence(Notification.EXTRA_TITLE); text = notification.extras.getCharSequence(Notification.EXTRA_TEXT); mBadgeIcon = BADGE_ICON_LARGE; // TODO: get from the Notification // Load the icon. Since it is backed by ashmem, we won't copy the entire bitmap // into our process as long as we don't touch it and it exists in systemui. Icon icon = notification.getLargeIcon(); Icon icon = mBadgeIcon == BADGE_ICON_SMALL ? null : notification.getLargeIcon(); if (icon == null) { // Use the small icon. icon = notification.getSmallIcon(); mIconDrawable = icon.loadDrawable(context); mIconColor = statusBarNotification.getNotification().color; mShouldTintIcon = true; mIsIconLarge = false; } else { // Use the large icon. mIconDrawable = icon.loadDrawable(context); mShouldTintIcon = false; mIsIconLarge = true; } intent = notification.contentIntent; autoCancel = (notification.flags & Notification.FLAG_AUTO_CANCEL) != 0; Loading @@ -95,7 +104,8 @@ public class NotificationInfo implements View.OnClickListener { } public Drawable getIconForBackground(Context context, int background) { if (!mShouldTintIcon) { if (mIsIconLarge) { // Only small icons should be tinted. return mIconDrawable; } mIconColor = IconPalette.resolveContrastColor(context, mIconColor, background); Loading @@ -104,7 +114,17 @@ public class NotificationInfo implements View.OnClickListener { // get it set and invalidated properly. icon.setTintList(null); icon.setTint(mIconColor); mShouldTintIcon = false; return icon; } public boolean isIconLarge() { return mIsIconLarge; } public boolean shouldShowIconInBadge() { // If the icon we're using for this notification matches what the Notification // specified should show in the badge, then return true. return mIsIconLarge && mBadgeIcon == BADGE_ICON_LARGE || !mIsIconLarge && mBadgeIcon == BADGE_ICON_SMALL; } }