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

Commit ab190a80 authored by Mady Mellor's avatar Mady Mellor Committed by Android (Google) Code Review
Browse files

Merge "Bubble icon improvements"

parents 235a9b30 05e860be
Loading
Loading
Loading
Loading
+5 −12
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package android.app;

import static android.annotation.Dimension.DP;
import static android.graphics.drawable.Icon.TYPE_BITMAP;

import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;

@@ -8741,26 +8740,20 @@ public class Notification implements Parcelable
             * If your app produces multiple bubbles, the image should be unique for each of them.
             * </p>
             *
             * <p>The shape of a bubble icon is adaptive and can match the device theme.
             * <p>The shape of a bubble icon is adaptive and will match the device theme.
             *
             * If your icon is bitmap-based, you should create it using
             * {@link Icon#createWithAdaptiveBitmap(Bitmap)}, otherwise this method will throw.
             *
             * If your icon is not bitmap-based, you should expect that the icon will be tinted.
             * Ideally your icon should be constructed via
             * {@link Icon#createWithAdaptiveBitmap(Bitmap)}, otherwise, the icon will be shrunk
             * and placed on an adaptive shape.
             * </p>
             *
             * @throws IllegalArgumentException if icon is null or a non-adaptive bitmap
             * @throws IllegalArgumentException if icon is null.
             */
            @NonNull
            public BubbleMetadata.Builder setIcon(@NonNull Icon icon) {
                if (icon == null) {
                    throw new IllegalArgumentException("Bubbles require non-null icon");
                }
                if (icon.getType() == TYPE_BITMAP) {
                    throw new IllegalArgumentException("When using bitmap based icons, Bubbles "
                            + "require TYPE_ADAPTIVE_BITMAP, please use"
                            + " Icon#createWithAdaptiveBitmap instead");
                }
                mIcon = icon;
                return this;
            }
+0 −2
Original line number Diff line number Diff line
@@ -1120,8 +1120,6 @@
    <dimen name="bubble_touch_padding">12dp</dimen>
    <!-- Size of the circle around the bubbles when they're in the dismiss target. -->
    <dimen name="bubble_dismiss_encircle_size">52dp</dimen>
    <!-- How much to inset the icon in the circle -->
    <dimen name="bubble_icon_inset">16dp</dimen>
    <!-- Padding around the view displayed when the bubble is expanded -->
    <dimen name="bubble_expanded_view_padding">4dp</dimen>
    <!-- This should be at least the size of bubble_expanded_view_padding; it is used to include
+4 −1
Original line number Diff line number Diff line
@@ -134,6 +134,10 @@ class Bubble {
        return mAppName;
    }

    public Drawable getUserBadgedAppIcon() {
        return mUserBadgedAppIcon;
    }

    boolean isInflated() {
        return mInflated;
    }
@@ -165,7 +169,6 @@ class Bubble {
        mIconView = (BubbleView) inflater.inflate(
                R.layout.bubble_view, stackView, false /* attachToRoot */);
        mIconView.setBubble(this);
        mIconView.setAppIcon(mUserBadgedAppIcon);

        mExpandedView = (BubbleExpandedView) inflater.inflate(
                R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
+1 −1
Original line number Diff line number Diff line
@@ -1459,7 +1459,7 @@ public class BubbleStackView extends FrameLayout {
            mFlyout.setupFlyoutStartingAsDot(
                    updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
                    mStackAnimationController.isStackOnLeftSide(),
                    bubble.getIconView().getBadgeColor() /* dotColor */,
                    bubble.getIconView().getDotColor() /* dotColor */,
                    expandFlyoutAfterDelay /* onLayoutComplete */,
                    mFlyoutOnHide,
                    bubble.getIconView().getDotCenter());
+50 −79
Original line number Diff line number Diff line
@@ -24,17 +24,16 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.InsetDrawable;
import android.util.AttributeSet;
import android.util.PathParser;
import android.widget.FrameLayout;

import com.android.internal.graphics.ColorUtils;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.ColorExtractor;
import com.android.launcher3.icons.ShadowGenerator;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -45,17 +44,13 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 */
public class BubbleView extends FrameLayout {

    private static final int DARK_ICON_ALPHA = 180;
    private static final double ICON_MIN_CONTRAST = 4.1;
    private static final int DEFAULT_BACKGROUND_COLOR = Color.LTGRAY;
    // Same value as Launcher3 badge code
    private static final float WHITE_SCRIM_ALPHA = 0.54f;
    private Context mContext;

    private BadgedImageView mBadgedImageView;
    private int mBadgeColor;
    private int mIconInset;
    private Drawable mUserBadgedAppIcon;
    private int mDotColor;
    private ColorExtractor mColorExtractor;

    // mBubbleIconFactory cannot be static because it depends on Context.
    private BubbleIconFactory mBubbleIconFactory;
@@ -79,13 +74,13 @@ public class BubbleView extends FrameLayout {
    public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mContext = context;
        mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mBadgedImageView = findViewById(R.id.bubble_image);
        mColorExtractor = new ColorExtractor();
    }

    @Override
@@ -105,6 +100,13 @@ public class BubbleView extends FrameLayout {
        mBubble = bubble;
    }

    /**
     * @param factory Factory for creating normalized bubble icons.
     */
    public void setBubbleIconFactory(BubbleIconFactory factory) {
        mBubbleIconFactory = factory;
    }

    /**
     * The {@link NotificationEntry} associated with this view, if one exists.
     */
@@ -129,17 +131,6 @@ public class BubbleView extends FrameLayout {
        updateViews();
    }

    /**
     * @param factory Factory for creating normalized bubble icons.
     */
    public void setBubbleIconFactory(BubbleIconFactory factory) {
        mBubbleIconFactory = factory;
    }

    public void setAppIcon(Drawable appIcon) {
        mUserBadgedAppIcon = appIcon;
    }

    /** Changes the dot's visibility to match the bubble view's state. */
    void updateDotVisibility(boolean animate) {
        updateDotVisibility(animate, null /* after */);
@@ -154,9 +145,17 @@ public class BubbleView extends FrameLayout {
        updateDotVisibility(animate);
    }

    boolean isDotShowing() {
        return mBubble.showBubbleDot() && !mSuppressDot;
    }

    int getDotColor() {
        return mDotColor;
    }

    /** Sets the position of the 'new' dot, animating it out and back in if requested. */
    void setDotPosition(boolean onLeft, boolean animate) {
        if (animate && onLeft != mBadgedImageView.getDotOnLeft() && shouldShowDot()) {
        if (animate && onLeft != mBadgedImageView.getDotOnLeft() && isDotShowing()) {
            animateDot(false /* showDot */, () -> {
                mBadgedImageView.setDotOnLeft(onLeft);
                animateDot(true /* showDot */, null);
@@ -180,7 +179,7 @@ public class BubbleView extends FrameLayout {
     * after animation if requested.
     */
    private void updateDotVisibility(boolean animate, Runnable after) {
        final boolean showDot = shouldShowDot();
        final boolean showDot = isDotShowing();
        if (animate) {
            animateDot(showDot, after);
        } else {
@@ -218,42 +217,21 @@ public class BubbleView extends FrameLayout {
        if (mBubble == null || mBubbleIconFactory == null) {
            return;
        }
        // Update icon.
        Notification.BubbleMetadata metadata = mBubble.getEntry().getBubbleMetadata();
        Notification n = mBubble.getEntry().getSbn().getNotification();
        Icon ic = metadata.getIcon();
        boolean needsTint = ic.getType() != Icon.TYPE_ADAPTIVE_BITMAP;

        Drawable iconDrawable = ic.loadDrawable(mContext);
        if (needsTint) {
            iconDrawable = buildIconWithTint(iconDrawable, n.color);
        }
        Bitmap bubbleIcon = mBubbleIconFactory.createBadgedIconBitmap(iconDrawable,
                null /* user */,
                true /* shrinkNonAdaptiveIcons */).icon;

        // Give it a shadow
        Bitmap userBadgedBitmap = mBubbleIconFactory.createIconBitmap(mUserBadgedAppIcon,
                1f, mBubbleIconFactory.getBadgeSize());
        Canvas c = new Canvas();
        ShadowGenerator shadowGenerator = new ShadowGenerator(mBubbleIconFactory.getBadgeSize());
        c.setBitmap(userBadgedBitmap);
        shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);

        mBubbleIconFactory.badgeWithDrawable(bubbleIcon,
                new BitmapDrawable(mContext.getResources(), userBadgedBitmap));
        mBadgedImageView.setImageBitmap(bubbleIcon);
        Drawable bubbleDrawable = getBubbleDrawable(mContext);
        BitmapInfo badgeBitmapInfo = getBadgedBitmap();
        BitmapInfo bubbleBitmapInfo = getBubbleBitmap(bubbleDrawable, badgeBitmapInfo);
        mBadgedImageView.setImageBitmap(bubbleBitmapInfo.icon);

        // Update badge.
        int badgeColor = determineDominateColor(iconDrawable, n.color);
        mBadgeColor = badgeColor;
        mBadgedImageView.setDotColor(badgeColor);
        mDotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, Color.WHITE, WHITE_SCRIM_ALPHA);
        mBadgedImageView.setDotColor(mDotColor);

        // Update dot.
        Path iconPath = PathParser.createPathFromPathData(
                getResources().getString(com.android.internal.R.string.config_icon_mask));
        Matrix matrix = new Matrix();
        float scale = mBubbleIconFactory.getNormalizer().getScale(iconDrawable,
        float scale = mBubbleIconFactory.getNormalizer().getScale(bubbleDrawable,
                null /* outBounds */, null /* path */, null /* outMaskShape */);
        float radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f;
        matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
@@ -261,41 +239,34 @@ public class BubbleView extends FrameLayout {
        iconPath.transform(matrix);
        mBadgedImageView.drawDot(iconPath);

        animateDot(shouldShowDot(), null /* after */);
        animateDot(isDotShowing(), null /* after */);
    }

    boolean shouldShowDot() {
        return mBubble.showBubbleDot() && !mSuppressDot;
    Drawable getBubbleDrawable(Context context) {
        Notification.BubbleMetadata metadata = getEntry().getBubbleMetadata();
        Icon ic = metadata.getIcon();
        return ic.loadDrawable(context);
    }

    int getBadgeColor() {
        return mBadgeColor;
    }
    BitmapInfo getBadgedBitmap() {
        Bitmap userBadgedBitmap = mBubbleIconFactory.createIconBitmap(
                mBubble.getUserBadgedAppIcon(), 1f, mBubbleIconFactory.getBadgeSize());

    private AdaptiveIconDrawable buildIconWithTint(Drawable iconDrawable, int backgroundColor) {
        iconDrawable = checkTint(iconDrawable, backgroundColor);
        InsetDrawable foreground = new InsetDrawable(iconDrawable, mIconInset);
        ColorDrawable background = new ColorDrawable(backgroundColor);
        return new AdaptiveIconDrawable(background, foreground);
        Canvas c = new Canvas();
        ShadowGenerator shadowGenerator = new ShadowGenerator(mBubbleIconFactory.getBadgeSize());
        c.setBitmap(userBadgedBitmap);
        shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);
        BitmapInfo bitmapInfo = mBubbleIconFactory.createIconBitmap(userBadgedBitmap);
        return bitmapInfo;
    }

    private Drawable checkTint(Drawable iconDrawable, int backgroundColor) {
        backgroundColor = ColorUtils.setAlphaComponent(backgroundColor, 255 /* alpha */);
        if (backgroundColor == Color.TRANSPARENT) {
            // ColorUtils throws exception when background is translucent.
            backgroundColor = DEFAULT_BACKGROUND_COLOR;
        }
        iconDrawable.setTint(Color.WHITE);
        double contrastRatio = ColorUtils.calculateContrast(Color.WHITE, backgroundColor);
        if (contrastRatio < ICON_MIN_CONTRAST) {
            int dark = ColorUtils.setAlphaComponent(Color.BLACK, DARK_ICON_ALPHA);
            iconDrawable.setTint(dark);
        }
        return iconDrawable;
    }
    BitmapInfo getBubbleBitmap(Drawable bubble, BitmapInfo badge) {
        BitmapInfo bubbleIconInfo = mBubbleIconFactory.createBadgedIconBitmap(bubble,
                null /* user */,
                true /* shrinkNonAdaptiveIcons */);

    private int determineDominateColor(Drawable d, int defaultTint) {
        // XXX: should we pull from the drawable, app icon, notif tint?
        return ColorUtils.blendARGB(defaultTint, Color.WHITE, WHITE_SCRIM_ALPHA);
        mBubbleIconFactory.badgeWithDrawable(bubbleIconInfo.icon,
                new BitmapDrawable(mContext.getResources(), badge.icon));
        return bubbleIconInfo;
    }
}