Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a799bed8 authored by Tony Wickham's avatar Tony Wickham
Browse files

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
parent e1fa0145
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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 -->
+1 −3
Original line number Diff line number Diff line
@@ -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;
+60 −2
Original line number Diff line number Diff line
@@ -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<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<>();
@@ -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());
    }
}
+55 −8
Original line number Diff line number Diff line
@@ -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,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(),
@@ -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);
        }
    }
}
+26 −6
Original line number Diff line number Diff line
@@ -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;
    }
}
Loading