From: Tony Wickham Date: Tue, 31 Jan 2017 18:49:18 +0000 (-0800) Subject: Show notification icon in place of "1" in badge. X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=a799bed8038ae97ee8985bc4475756391f7f4f0c;p=android-x86%2Fpackages-apps-Launcher3.git Show notification icon in place of "1" in badge. - When notifications update, add the NotificationInfo to the BadgeInfo if there is only one for an app. BadgeRenderer will use the NotificationInfo to get the icon to draw. - When retrieving the icon from the NotificationInfo, we draw it into a shader (similar to MaskableIconDrawable), which is rendered by BadgeRenderer. - For now, we only use the notification icon if it is large. Bug: 34839959 Bug: 32410600 Bug: 33553066 Change-Id: I31851804008dd15bab75d2759441187830c3265e --- diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 2cf17ead7..f00a00d12 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -172,6 +172,8 @@ 24dp 12dp + 1dp + 3dp 28dp 24dp diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 34ce92336..c9e3d4f10 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -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; diff --git a/src/com/android/launcher3/badge/BadgeInfo.java b/src/com/android/launcher3/badge/BadgeInfo.java index 77355c75e..532396cdd 100644 --- a/src/com/android/launcher3/badge/BadgeInfo.java +++ b/src/com/android/launcher3/badge/BadgeInfo.java @@ -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; @@ -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 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<>(); @@ -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()); } } diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java index 787ee724e..4bb09546f 100644 --- a/src/com/android/launcher3/badge/BadgeRenderer.java +++ b/src/com/android/launcher3/badge/BadgeRenderer.java @@ -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; /** @@ -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. @@ -61,10 +73,45 @@ public class BadgeRenderer { mBackgroundRect.set(iconBounds.right - size, iconBounds.top, iconBounds.right, iconBounds.top + size); canvas.drawOval(mBackgroundRect, mBackgroundPaint); - String notificationCount = String.valueOf(badgeInfo.getNotificationCount()); - canvas.drawText(notificationCount, - mBackgroundRect.centerX(), - mBackgroundRect.centerY() + mTextHeight / 2, - mTextPaint); + 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(), + mBackgroundRect.centerY() + mTextHeight / 2, + 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); + } } } diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java index af5e817f1..6ec80753a 100644 --- a/src/com/android/launcher3/notification/NotificationInfo.java +++ b/src/com/android/launcher3/notification/NotificationInfo.java @@ -38,6 +38,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; @@ -46,9 +51,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. @@ -59,17 +65,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; @@ -91,7 +100,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); @@ -100,7 +110,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; + } } diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java index 3f9a58413..206bb31d4 100644 --- a/src/com/android/launcher3/notification/NotificationListener.java +++ b/src/com/android/launcher3/notification/NotificationListener.java @@ -24,7 +24,6 @@ import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.support.annotation.Nullable; import android.support.v4.util.Pair; -import android.util.Log; import com.android.launcher3.LauncherModel; import com.android.launcher3.config.FeatureFlags; diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java index 76a84b7c6..7fed608fe 100644 --- a/src/com/android/launcher3/notification/NotificationMainView.java +++ b/src/com/android/launcher3/notification/NotificationMainView.java @@ -20,7 +20,6 @@ import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.content.Context; -import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java index bb9c5206b..4f3b8a6b1 100644 --- a/src/com/android/launcher3/popup/PopupDataProvider.java +++ b/src/com/android/launcher3/popup/PopupDataProvider.java @@ -23,6 +23,7 @@ import android.util.Log; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.badge.BadgeInfo; +import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.util.ComponentKey; @@ -31,8 +32,10 @@ import com.android.launcher3.util.PackageUserKey; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; /** * Provides data for the popup menu that appears after long-clicking on apps. @@ -55,15 +58,17 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan @Override public void onNotificationPosted(PackageUserKey postedPackageUserKey, String notificationKey) { - BadgeInfo oldBadgeInfo = mPackageUserToBadgeInfos.get(postedPackageUserKey); - if (oldBadgeInfo == null) { + BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(postedPackageUserKey); + boolean notificationWasAdded; // As opposed to updated. + if (badgeInfo == null) { BadgeInfo newBadgeInfo = new BadgeInfo(postedPackageUserKey); newBadgeInfo.addNotificationKeyIfNotExists(notificationKey); mPackageUserToBadgeInfos.put(postedPackageUserKey, newBadgeInfo); - mLauncher.updateIconBadges(Collections.singleton(postedPackageUserKey)); - } else if (oldBadgeInfo.addNotificationKeyIfNotExists(notificationKey)) { - mLauncher.updateIconBadges(Collections.singleton(postedPackageUserKey)); + notificationWasAdded = true; + } else { + notificationWasAdded = badgeInfo.addNotificationKeyIfNotExists(notificationKey); } + updateLauncherIconBadges(Collections.singleton(postedPackageUserKey), notificationWasAdded); } @Override @@ -73,7 +78,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan if (oldBadgeInfo.getNotificationCount() == 0) { mPackageUserToBadgeInfos.remove(removedPackageUserKey); } - mLauncher.updateIconBadges(Collections.singleton(removedPackageUserKey)); + updateLauncherIconBadges(Collections.singleton(removedPackageUserKey)); PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher); if (openContainer != null) { @@ -112,7 +117,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan } if (!updatedBadges.isEmpty()) { - mLauncher.updateIconBadges(updatedBadges.keySet()); + updateLauncherIconBadges(updatedBadges.keySet()); } PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher); @@ -121,6 +126,58 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan } } + private void updateLauncherIconBadges(Set updatedBadges) { + updateLauncherIconBadges(updatedBadges, true); + } + + /** + * Updates the icons on launcher (workspace, folders, all apps) to refresh their badges. + * @param updatedBadges The packages whose badges should be refreshed (either a notification was + * added or removed, or the badge should show the notification icon). + * @param addedOrRemoved An optional parameter that will allow us to only refresh badges that + * updated (not added/removed) that have icons. If a badge updated + * but it doesn't have an icon, then the badge number doesn't change. + */ + private void updateLauncherIconBadges(Set updatedBadges, + boolean addedOrRemoved) { + Iterator iterator = updatedBadges.iterator(); + while (iterator.hasNext()) { + BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(iterator.next()); + if (badgeInfo != null && !updateBadgeIcon(badgeInfo) && !addedOrRemoved) { + // The notification icon isn't used, and the badge wasn't added or removed + // so there is no update to be made. + iterator.remove(); + } + } + if (!updatedBadges.isEmpty()) { + mLauncher.updateIconBadges(updatedBadges); + } + } + + /** + * Determines whether the badge should show a notification icon rather than a number, + * and sets that icon on the BadgeInfo if so. + * @param badgeInfo The badge to update with an icon (null if it shouldn't show one). + * @return Whether the badge icon potentially changed (true unless it stayed null). + */ + private boolean updateBadgeIcon(BadgeInfo badgeInfo) { + boolean hadNotificationToShow = badgeInfo.hasNotificationToShow(); + NotificationInfo notificationInfo = null; + NotificationListener notificationListener = NotificationListener.getInstance(); + if (notificationListener != null && badgeInfo.getNotificationKeys().size() == 1) { + StatusBarNotification[] activeNotifications = notificationListener + .getActiveNotifications(new String[] {badgeInfo.getNotificationKeys().get(0)}); + if (activeNotifications.length == 1) { + notificationInfo = new NotificationInfo(mLauncher, activeNotifications[0]); + if (!notificationInfo.shouldShowIconInBadge()) { + notificationInfo = null; + } + } + } + badgeInfo.setNotificationToShow(notificationInfo); + return hadNotificationToShow || badgeInfo.hasNotificationToShow(); + } + public void setDeepShortcutMap(MultiHashMap deepShortcutMapCopy) { mDeepShortcutMap = deepShortcutMapCopy; if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);