Support notifications with 0 count (show as dots)

- Show number if number > 0
- Show icon if number == 0 and a notification specified an icon to show
- Show a dot otherwise
- In cases of multiple notifications, stack a second badge behind the
  first (visuals will be updated in future CL, as well as support
  stacked dots)
- Folders always show dot if any app within has a badge.

Change-Id: I0a89059b0e0a0d174fe739c9da4f75fa18c0edfa
This commit is contained in:
Tony Wickham
2017-04-26 18:13:56 -07:00
committed by Tony
parent b85f5dfcba
commit 0530e8c608
8 changed files with 96 additions and 45 deletions

View File

@@ -473,7 +473,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver {
}
private boolean hasBadge() {
return (mBadgeInfo != null && mBadgeInfo.getNotificationCount() > 0);
return mBadgeInfo != null;
}
public void getIconBounds(Rect outBounds) {

View File

@@ -44,12 +44,17 @@ public class BadgeRenderer {
private static final float CHAR_SIZE_PERCENTAGE = 0.12f;
private static final float TEXT_SIZE_PERCENTAGE = 0.26f;
private static final float OFFSET_PERCENTAGE = 0.02f;
private static final float STACK_OFFSET_PERCENTAGE_X = 0.05f;
private static final float STACK_OFFSET_PERCENTAGE_Y = 0.06f;
private static final float DOT_SCALE = 0.6f;
private final Context mContext;
private final int mSize;
private final int mCharSize;
private final int mTextHeight;
private final int mOffset;
private final int mStackOffsetX;
private final int mStackOffsetY;
private final IconDrawer mLargeIconDrawer;
private final IconDrawer mSmallIconDrawer;
private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -63,6 +68,8 @@ public class BadgeRenderer {
mSize = (int) (SIZE_PERCENTAGE * iconSizePx);
mCharSize = (int) (CHAR_SIZE_PERCENTAGE * iconSizePx);
mOffset = (int) (OFFSET_PERCENTAGE * iconSizePx);
mStackOffsetX = (int) (STACK_OFFSET_PERCENTAGE_X * iconSizePx);
mStackOffsetY = (int) (STACK_OFFSET_PERCENTAGE_Y * iconSizePx);
mTextPaint.setTextSize(iconSizePx * TEXT_SIZE_PERCENTAGE);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mLargeIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_small_padding));
@@ -91,7 +98,7 @@ public class BadgeRenderer {
? mLargeIconDrawer : mSmallIconDrawer;
Shader icon = badgeInfo == null ? null : badgeInfo.getNotificationIconForBadge(
mContext, palette.backgroundColor, mSize, iconDrawer.mPadding);
String notificationCount = icon != null || badgeInfo == null ? "0"
String notificationCount = badgeInfo == null ? "0"
: String.valueOf(badgeInfo.getNotificationCount());
int numChars = notificationCount.length();
int width = mSize + mCharSize * (numChars - 1);
@@ -105,21 +112,42 @@ public class BadgeRenderer {
// We draw the badge relative to its center.
int badgeCenterX = iconBounds.right - width / 2;
int badgeCenterY = iconBounds.top + mSize / 2;
boolean isText = badgeInfo != null && badgeInfo.getNotificationCount() != 0;
boolean isIcon = icon != null;
boolean isDot = !(isText || isIcon);
if (isDot) {
badgeScale *= DOT_SCALE;
}
int offsetX = Math.min(mOffset, spaceForOffset.x);
int offsetY = Math.min(mOffset, spaceForOffset.y);
canvas.translate(badgeCenterX + offsetX, badgeCenterY - offsetY);
canvas.scale(badgeScale, badgeScale);
// Draw the background and shadow.
// Prepare the background and shadow and possible stacking effect.
mBackgroundPaint.setColorFilter(palette.backgroundColorMatrixFilter);
int backgroundWithShadowSize = backgroundWithShadow.getHeight(); // Same as width.
canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
-backgroundWithShadowSize / 2, mBackgroundPaint);
if (icon != null) {
// Draw the notification icon with padding.
iconDrawer.drawIcon(icon, canvas);
} else {
// Draw the notification count.
boolean shouldStack = !isDot && badgeInfo != null
&& badgeInfo.getNotificationKeys().size() > 1;
if (shouldStack) {
int offsetDiffX = mStackOffsetX - mOffset;
int offsetDiffY = mStackOffsetY - mOffset;
canvas.translate(offsetDiffX, offsetDiffY);
canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
-backgroundWithShadowSize / 2, mBackgroundPaint);
canvas.translate(-offsetDiffX, -offsetDiffY);
}
if (isText) {
canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
-backgroundWithShadowSize / 2, mBackgroundPaint);
canvas.drawText(notificationCount, 0, mTextHeight / 2, mTextPaint);
} else if (isIcon) {
canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
-backgroundWithShadowSize / 2, mBackgroundPaint);
iconDrawer.drawIcon(icon, canvas);
} else if (isDot) {
mBackgroundPaint.setColorFilter(palette.saturatedBackgroundColorMatrixFilter);
canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
-backgroundWithShadowSize / 2, mBackgroundPaint);
}
canvas.restore();
}

View File

@@ -19,14 +19,14 @@ package com.android.launcher3.badge;
import com.android.launcher3.Utilities;
/**
* Subclass of BadgeInfo that only contains the badge count,
* which is the sum of all the Folder's items' counts.
* Subclass of BadgeInfo that only contains the badge count, which is
* the sum of all the Folder's items' notifications (each counts as 1).
*/
public class FolderBadgeInfo extends BadgeInfo {
private static final int MIN_COUNT = 0;
private int mTotalNotificationCount;
private int mNumNotifications;
public FolderBadgeInfo() {
super(null);
@@ -36,22 +36,27 @@ public class FolderBadgeInfo extends BadgeInfo {
if (badgeToAdd == null) {
return;
}
mTotalNotificationCount += badgeToAdd.getNotificationCount();
mTotalNotificationCount = Utilities.boundToRange(
mTotalNotificationCount, MIN_COUNT, BadgeInfo.MAX_COUNT);
mNumNotifications += badgeToAdd.getNotificationKeys().size();
mNumNotifications = Utilities.boundToRange(
mNumNotifications, MIN_COUNT, BadgeInfo.MAX_COUNT);
}
public void subtractBadgeInfo(BadgeInfo badgeToSubtract) {
if (badgeToSubtract == null) {
return;
}
mTotalNotificationCount -= badgeToSubtract.getNotificationCount();
mTotalNotificationCount = Utilities.boundToRange(
mTotalNotificationCount, MIN_COUNT, BadgeInfo.MAX_COUNT);
mNumNotifications -= badgeToSubtract.getNotificationKeys().size();
mNumNotifications = Utilities.boundToRange(
mNumNotifications, MIN_COUNT, BadgeInfo.MAX_COUNT);
}
@Override
public int getNotificationCount() {
return mTotalNotificationCount;
// This forces the folder badge to always show up as a dot.
return 0;
}
public boolean hasBadge() {
return mNumNotifications > 0;
}
}

View File

@@ -403,17 +403,15 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
public void setBadgeInfo(FolderBadgeInfo badgeInfo) {
updateBadgeScale(mBadgeInfo.getNotificationCount(), badgeInfo.getNotificationCount());
updateBadgeScale(mBadgeInfo.hasBadge(), badgeInfo.hasBadge());
mBadgeInfo = badgeInfo;
}
/**
* Sets mBadgeScale to 1 or 0, animating if oldCount or newCount is 0
* Sets mBadgeScale to 1 or 0, animating if wasBadged or isBadged is false
* (the badge is being added or removed).
*/
private void updateBadgeScale(int oldCount, int newCount) {
boolean wasBadged = oldCount > 0;
boolean isBadged = newCount > 0;
private void updateBadgeScale(boolean wasBadged, boolean isBadged) {
float newBadgeScale = isBadged ? 1f : 0f;
// Animate when a badge is first added or when it is removed.
if ((wasBadged ^ isBadged) && isShown()) {
@@ -879,7 +877,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
mBackground.drawBackgroundStroke(canvas);
}
if ((mBadgeInfo != null && mBadgeInfo.getNotificationCount() > 0) || mBadgeScale > 0) {
if ((mBadgeInfo != null && mBadgeInfo.hasBadge()) || mBadgeScale > 0) {
int offsetX = mBackground.getOffsetX();
int offsetY = mBackground.getOffsetY();
int previewSize = (int) (mBackground.previewSize * mBackground.mScale);
@@ -1046,20 +1044,20 @@ public class FolderIcon extends FrameLayout implements FolderListener {
@Override
public void onAdd(ShortcutInfo item) {
int oldCount = mBadgeInfo.getNotificationCount();
boolean wasBadged = mBadgeInfo.hasBadge();
mBadgeInfo.addBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item));
int newCount = mBadgeInfo.getNotificationCount();
updateBadgeScale(oldCount, newCount);
boolean isBadged = mBadgeInfo.hasBadge();
updateBadgeScale(wasBadged, isBadged);
invalidate();
requestLayout();
}
@Override
public void onRemove(ShortcutInfo item) {
int oldCount = mBadgeInfo.getNotificationCount();
boolean wasBadged = mBadgeInfo.hasBadge();
mBadgeInfo.subtractBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item));
int newCount = mBadgeInfo.getNotificationCount();
updateBadgeScale(oldCount, newCount);
boolean isBadged = mBadgeInfo.hasBadge();
updateBadgeScale(wasBadged, isBadged);
invalidate();
requestLayout();
}

View File

@@ -43,6 +43,7 @@ public class IconPalette {
public final int dominantColor;
public final int backgroundColor;
public final ColorMatrixColorFilter backgroundColorMatrixFilter;
public final ColorMatrixColorFilter saturatedBackgroundColorMatrixFilter;
public final int textColor;
public final int secondaryColor;
@@ -52,6 +53,9 @@ public class IconPalette {
ColorMatrix backgroundColorMatrix = new ColorMatrix();
Themes.setColorScaleOnMatrix(backgroundColor, backgroundColorMatrix);
backgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
// Get slightly more saturated background color.
Themes.setColorScaleOnMatrix(getMutedColor(dominantColor, 0.54f), backgroundColorMatrix);
saturatedBackgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
textColor = getTextColorForBackground(backgroundColor);
secondaryColor = getLowContrastColor(backgroundColor);
}
@@ -173,7 +177,11 @@ public class IconPalette {
}
private static int getMutedColor(int color) {
int whiteScrim = ColorUtils.setAlphaComponent(Color.WHITE, (int) (255 * 0.87f));
return getMutedColor(color, 0.87f);
}
private static int getMutedColor(int color, float whiteScrimAlpha) {
int whiteScrim = ColorUtils.setAlphaComponent(Color.WHITE, (int) (255 * whiteScrimAlpha));
return ColorUtils.compositeColors(whiteScrim, color);
}

View File

@@ -37,7 +37,7 @@ public class NotificationKeyData {
private NotificationKeyData(String notificationKey, String shortcutId, int count) {
this.notificationKey = notificationKey;
this.shortcutId = shortcutId;
this.count = Math.max(1, count);
this.count = count;
}
public static NotificationKeyData fromNotification(StatusBarNotification sbn) {

View File

@@ -562,6 +562,11 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
}
private void updateNotificationHeader() {
if (true) {
// For now, don't show any number in the popup.
// TODO: determine whether a number makes sense, and if not, remove associated code.
return;
}
ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
BadgeInfo badgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo);
if (mNotificationItemView != null && badgeInfo != null) {
@@ -578,7 +583,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
}
ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
BadgeInfo badgeInfo = updatedBadges.get(PackageUserKey.fromItemInfo(originalInfo));
if (badgeInfo == null || badgeInfo.getNotificationCount() == 0) {
if (badgeInfo == null || badgeInfo.getNotificationKeys().size() == 0) {
AnimatorSet removeNotification = LauncherAnimUtils.createAnimatorSet();
final int duration = getResources().getInteger(
R.integer.config_removeNotificationViewDuration);

View File

@@ -84,7 +84,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan
badgeShouldBeRefreshed = shouldBeFilteredOut
? badgeInfo.removeNotificationKey(notificationKey)
: badgeInfo.addOrUpdateNotificationKey(notificationKey);
if (badgeInfo.getNotificationCount() == 0) {
if (badgeInfo.getNotificationKeys().size() == 0) {
mPackageUserToBadgeInfos.remove(postedPackageUserKey);
}
}
@@ -97,7 +97,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan
NotificationKeyData notificationKey) {
BadgeInfo oldBadgeInfo = mPackageUserToBadgeInfos.get(removedPackageUserKey);
if (oldBadgeInfo != null && oldBadgeInfo.removeNotificationKey(notificationKey)) {
if (oldBadgeInfo.getNotificationCount() == 0) {
if (oldBadgeInfo.getNotificationKeys().size() == 0) {
mPackageUserToBadgeInfos.remove(removedPackageUserKey);
}
updateLauncherIconBadges(Utilities.singletonHashSet(removedPackageUserKey));
@@ -187,14 +187,21 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan
boolean hadNotificationToShow = badgeInfo.hasNotificationToShow();
NotificationInfo notificationInfo = null;
NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
if (notificationListener != null && badgeInfo.getNotificationKeys().size() == 1) {
String onlyNotificationKey = badgeInfo.getNotificationKeys().get(0).notificationKey;
StatusBarNotification[] activeNotifications = notificationListener
.getActiveNotifications(new String[] {onlyNotificationKey});
if (activeNotifications.length == 1) {
notificationInfo = new NotificationInfo(mLauncher, activeNotifications[0]);
if (!notificationInfo.shouldShowIconInBadge()) {
notificationInfo = null;
if (notificationListener != null && badgeInfo.getNotificationKeys().size() >= 1) {
// Look for the most recent notification that has an icon that should be shown in badge.
for (NotificationKeyData notificationKeyData : badgeInfo.getNotificationKeys()) {
String notificationKey = notificationKeyData.notificationKey;
StatusBarNotification[] activeNotifications = notificationListener
.getActiveNotifications(new String[]{notificationKey});
if (activeNotifications.length == 1) {
notificationInfo = new NotificationInfo(mLauncher, activeNotifications[0]);
if (notificationInfo.shouldShowIconInBadge()) {
// Found an appropriate icon.
break;
} else {
// Keep looking.
notificationInfo = null;
}
}
}
}