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

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

Merge "Move BubbleView methods into BadgedImageView; remove BubbleView"

parents 8c370fa7 b8aaf97e
Loading
Loading
Loading
Loading
+4 −12
Original line number Diff line number Diff line
@@ -14,16 +14,8 @@
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License
  -->
<com.android.systemui.bubbles.BubbleView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:id="@+id/bubble_view">

<com.android.systemui.bubbles.BadgedImageView
        android:id="@+id/bubble_image"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/bubble_view"
    android:layout_width="@dimen/individual_bubble_size"
        android:layout_height="@dimen/individual_bubble_size"
        android:clipToPadding="false"/>

</com.android.systemui.bubbles.BubbleView>
    android:layout_height="@dimen/individual_bubble_size"/>
+200 −36
Original line number Diff line number Diff line
@@ -15,35 +15,61 @@
 */
package com.android.systemui.bubbles;

import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.pm.LauncherApps;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.util.AttributeSet;
import android.util.PathParser;
import android.widget.ImageView;

import com.android.internal.graphics.ColorUtils;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.DotRenderer;
import com.android.systemui.Interpolators;
import com.android.systemui.R;

/**
 * View that circle crops its contents and supports displaying a coloured dot on a top corner.
 * View that displays an adaptive icon with an app-badge and a dot.
 *
 * Dot = a small colored circle that indicates whether this bubble has an unread update.
 * Badge = the icon associated with the app that created this bubble, this will show work profile
 * badge if appropriate.
 */
public class BadgedImageView extends ImageView {

    private Rect mTempBounds = new Rect();
    /** Same value as Launcher3 dot code */
    private static final float WHITE_SCRIM_ALPHA = 0.54f;
    /** Same as value in Launcher3 IconShape */
    private static final int DEFAULT_PATH_SIZE = 100;

    static final int DOT_STATE_DEFAULT = 0;
    static final int DOT_STATE_SUPPRESSED_FOR_FLYOUT = 1;
    static final int DOT_STATE_ANIMATING = 2;

    // Flyout gets shown before the dot
    private int mCurrentDotState = DOT_STATE_SUPPRESSED_FOR_FLYOUT;

    private Bubble mBubble;
    private BubbleIconFactory mBubbleIconFactory;

    private int mIconBitmapSize;
    private DotRenderer mDotRenderer;
    private DotRenderer.DrawParams mDrawParams;
    private int mIconBitmapSize;
    private boolean mOnLeft;

    private int mDotColor;
    private float mDotScale = 0f;
    private boolean mShowDot;
    private boolean mOnLeft;
    private boolean mDotDrawn;

    /** Same as value in Launcher3 IconShape */
    static final int DEFAULT_PATH_SIZE = 100;
    private Rect mTempBounds = new Rect();

    public BadgedImageView(Context context) {
        this(context, null);
@@ -63,17 +89,19 @@ public class BadgedImageView extends ImageView {
        mIconBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
        mDrawParams = new DotRenderer.DrawParams();

        TypedArray ta = context.obtainStyledAttributes(
                new int[]{android.R.attr.colorBackgroundFloating});
        ta.recycle();
        Path iconPath = PathParser.createPathFromPathData(
                getResources().getString(com.android.internal.R.string.config_icon_mask));
        mDotRenderer = new DotRenderer(mIconBitmapSize, iconPath, DEFAULT_PATH_SIZE);
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!mShowDot) {
        if (isDotHidden()) {
            mDotDrawn = false;
            return;
        }
        mDotDrawn = mDotScale > 0.1f;
        getDrawingRect(mTempBounds);

        mDrawParams.color = mDotColor;
@@ -81,46 +109,40 @@ public class BadgedImageView extends ImageView {
        mDrawParams.leftAlign = mOnLeft;
        mDrawParams.scale = mDotScale;

        if (mDotRenderer == null) {
            Path circlePath = new Path();
            float radius = DEFAULT_PATH_SIZE * 0.5f;
            circlePath.addCircle(radius /* x */, radius /* y */, radius, Path.Direction.CW);
            mDotRenderer = new DotRenderer(mIconBitmapSize, circlePath, DEFAULT_PATH_SIZE);
        }
        mDotRenderer.draw(canvas, mDrawParams);
    }

    /**
     * Set whether the dot should appear on left or right side of the view.
     * Sets the dot state, does not animate changes.
     */
    void setDotOnLeft(boolean onLeft) {
        mOnLeft = onLeft;
    void setDotState(int state) {
        mCurrentDotState = state;
        if (state == DOT_STATE_SUPPRESSED_FOR_FLYOUT || state == DOT_STATE_DEFAULT) {
            mDotScale = mBubble.showDot() ? 1f : 0f;
            invalidate();
        }

    boolean getDotOnLeft() {
        return mOnLeft;
    }

    /**
     * Set whether the dot should show or not.
     * Whether the dot should be hidden based on current dot state.
     */
    void setShowDot(boolean showDot) {
        mShowDot = showDot;
        invalidate();
    private boolean isDotHidden() {
        return (mCurrentDotState == DOT_STATE_DEFAULT && !mBubble.showDot())
                || mCurrentDotState == DOT_STATE_SUPPRESSED_FOR_FLYOUT;
    }

    /**
     * @return whether the dot is being displayed.
     * Set whether the dot should appear on left or right side of the view.
     */
    boolean isShowingDot() {
        return mShowDot;
    void setDotOnLeft(boolean onLeft) {
        mOnLeft = onLeft;
        invalidate();
    }

    /**
     * The colour to use for the dot.
     */
    public void setDotColor(int color) {
    void setDotColor(int color) {
        mDotColor = ColorUtils.setAlphaComponent(color, 255 /* alpha */);
        invalidate();
    }
@@ -128,7 +150,7 @@ public class BadgedImageView extends ImageView {
    /**
     * @param iconPath The new icon path to use when calculating dot position.
     */
    public void drawDot(Path iconPath) {
    void drawDot(Path iconPath) {
        mDotRenderer = new DotRenderer(mIconBitmapSize, iconPath, DEFAULT_PATH_SIZE);
        invalidate();
    }
@@ -141,6 +163,13 @@ public class BadgedImageView extends ImageView {
        invalidate();
    }

    /**
     * Whether decorations (badges or dots) are on the left.
     */
    boolean getDotOnLeft() {
        return mOnLeft;
    }

    /**
     * Return dot position relative to bubble view container bounds.
     */
@@ -156,4 +185,139 @@ public class BadgedImageView extends ImageView {
        float dotCenterY = mTempBounds.height() * dotPosition[1];
        return new float[]{dotCenterX, dotCenterY};
    }

    /**
     * Populates this view with a bubble.
     * <p>
     * This should only be called when a new bubble is being set on the view, updates to the
     * current bubble should use {@link #update(Bubble)}.
     *
     * @param bubble the bubble to display in this view.
     */
    public void setBubble(Bubble bubble) {
        mBubble = bubble;
    }

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

    /**
     * The key for the {@link Bubble} associated with this view, if one exists.
     */
    @Nullable
    public String getKey() {
        return (mBubble != null) ? mBubble.getKey() : null;
    }

    /**
     * Updates the UI based on the bubble, updates badge and animates messages as needed.
     */
    public void update(Bubble bubble) {
        mBubble = bubble;
        setDotState(DOT_STATE_SUPPRESSED_FOR_FLYOUT);
        updateViews();
    }

    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 != getDotOnLeft() && !isDotHidden()) {
            animateDot(false /* showDot */, () -> {
                setDotOnLeft(onLeft);
                animateDot(true /* showDot */, null);
            });
        } else {
            setDotOnLeft(onLeft);
        }
    }

    boolean getDotPositionOnLeft() {
        return getDotOnLeft();
    }

    /** Changes the dot's visibility to match the bubble view's state. */
    void animateDot() {
        if (mCurrentDotState == DOT_STATE_DEFAULT) {
            animateDot(mBubble.showDot(), null);
        }
    }

    /**
     * Animates the dot to show or hide.
     */
    private void animateDot(boolean showDot, Runnable after) {
        if (mDotDrawn == showDot) {
            // State is consistent, do nothing.
            return;
        }

        setDotState(DOT_STATE_ANIMATING);

        // Do NOT wait until after animation ends to setShowDot
        // to avoid overriding more recent showDot states.
        clearAnimation();
        animate().setDuration(200)
                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                .setUpdateListener((valueAnimator) -> {
                    float fraction = valueAnimator.getAnimatedFraction();
                    fraction = showDot ? fraction : 1f - fraction;
                    setDotScale(fraction);
                }).withEndAction(() -> {
                    setDotScale(showDot ? 1f : 0f);
                    setDotState(DOT_STATE_DEFAULT);
                    if (after != null) {
                        after.run();
                    }
                }).start();
    }

    void updateViews() {
        if (mBubble == null || mBubbleIconFactory == null) {
            return;
        }

        Drawable bubbleDrawable = getBubbleDrawable(mContext);
        BitmapInfo badgeBitmapInfo = mBubbleIconFactory.getBadgedBitmap(mBubble);
        BitmapInfo bubbleBitmapInfo = mBubbleIconFactory.getBubbleBitmap(bubbleDrawable,
                badgeBitmapInfo);
        setImageBitmap(bubbleBitmapInfo.icon);

        // Update badge.
        mDotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, Color.WHITE, WHITE_SCRIM_ALPHA);
        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(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 */,
                radius /* pivot y */);
        iconPath.transform(matrix);
        drawDot(iconPath);

        animateDot();
    }

    Drawable getBubbleDrawable(Context context) {
        if (mBubble.getShortcutInfo() != null && mBubble.usingShortcutInfo()) {
            LauncherApps launcherApps =
                    (LauncherApps) getContext().getSystemService(Context.LAUNCHER_APPS_SERVICE);
            int density = getContext().getResources().getConfiguration().densityDpi;
            return launcherApps.getShortcutIconDrawable(mBubble.getShortcutInfo(), density);
        } else {
            Notification.BubbleMetadata metadata = mBubble.getEntry().getBubbleMetadata();
            Icon ic = metadata.getIcon();
            return ic.loadDrawable(context);
        }
    }
}
+25 −20
Original line number Diff line number Diff line
@@ -64,8 +64,9 @@ class Bubble {
    private ShortcutInfo mShortcutInfo;

    private boolean mInflated;
    private BubbleView mIconView;
    private BadgedImageView mIconView;
    private BubbleExpandedView mExpandedView;
    private BubbleIconFactory mBubbleIconFactory;

    private long mLastUpdated;
    private long mLastAccessed;
@@ -146,7 +147,7 @@ class Bubble {
        return mAppName;
    }

    public Drawable getUserBadgedAppIcon() {
    Drawable getUserBadgedAppIcon() {
        return mUserBadgedAppIcon;
    }

@@ -165,17 +166,15 @@ class Bubble {
        return BubbleExperimentConfig.isShortcutIntent(getBubbleIntent());
    }

    boolean isInflated() {
        return mInflated;
    void setBubbleIconFactory(BubbleIconFactory factory) {
        mBubbleIconFactory = factory;
    }

    void updateDotVisibility() {
        if (mIconView != null) {
            mIconView.updateDotVisibility(true /* animate */);
        }
    boolean isInflated() {
        return mInflated;
    }

    BubbleView getIconView() {
    BadgedImageView getIconView() {
        return mIconView;
    }

@@ -193,8 +192,9 @@ class Bubble {
        if (mInflated) {
            return;
        }
        mIconView = (BubbleView) inflater.inflate(
        mIconView = (BadgedImageView) inflater.inflate(
                R.layout.bubble_view, stackView, false /* attachToRoot */);
        mIconView.setBubbleIconFactory(mBubbleIconFactory);
        mIconView.setBubble(this);

        mExpandedView = (BubbleExpandedView) inflater.inflate(
@@ -260,15 +260,15 @@ class Bubble {
     */
    void markAsAccessedAt(long lastAccessedMillis) {
        mLastAccessed = lastAccessedMillis;
        setShowInShadeWhenBubble(false);
        setShowBubbleDot(false);
        setShowInShade(false);
        setShowDot(false /* show */, true /* animate */);
    }

    /**
     * Whether this notification should be shown in the shade when it is also displayed as a
     * bubble.
     */
    boolean showInShadeWhenBubble() {
    boolean showInShade() {
        return !mEntry.isRowDismissed() && !shouldSuppressNotification()
                && (!mEntry.isClearable() || mShowInShadeWhenBubble);
    }
@@ -277,28 +277,33 @@ class Bubble {
     * Sets whether this notification should be shown in the shade when it is also displayed as a
     * bubble.
     */
    void setShowInShadeWhenBubble(boolean showInShade) {
    void setShowInShade(boolean showInShade) {
        mShowInShadeWhenBubble = showInShade;
    }

    /**
     * Sets whether the bubble for this notification should show a dot indicating updated content.
     */
    void setShowBubbleDot(boolean showDot) {
    void setShowDot(boolean showDot, boolean animate) {
        mShowBubbleUpdateDot = showDot;
        if (animate && mIconView != null) {
            mIconView.animateDot();
        } else if (mIconView != null) {
            mIconView.invalidate();
        }
    }

    /**
     * Whether the bubble for this notification should show a dot indicating updated content.
     */
    boolean showBubbleDot() {
    boolean showDot() {
        return mShowBubbleUpdateDot && !mEntry.shouldSuppressNotificationDot();
    }

    /**
     * Whether the flyout for the bubble should be shown.
     */
    boolean showFlyoutForBubble() {
    boolean showFlyout() {
        return !mSuppressFlyout && !mEntry.shouldSuppressPeek()
                && !mEntry.shouldSuppressNotificationList();
    }
@@ -470,9 +475,9 @@ class Bubble {
    public void dump(
            @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
        pw.print("key: "); pw.println(mKey);
        pw.print("  showInShade:   "); pw.println(showInShadeWhenBubble());
        pw.print("  showDot:       "); pw.println(showBubbleDot());
        pw.print("  showFlyout:    "); pw.println(showFlyoutForBubble());
        pw.print("  showInShade:   "); pw.println(showInShade());
        pw.print("  showDot:       "); pw.println(showDot());
        pw.print("  showFlyout:    "); pw.println(showFlyout());
        pw.print("  desiredHeight: "); pw.println(getDesiredHeightString());
        pw.print("  suppressNotif: "); pw.println(shouldSuppressNotification());
        pw.print("  autoExpand:    "); pw.println(shouldAutoExpand());
+10 −16
Original line number Diff line number Diff line
@@ -251,15 +251,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
        mZenModeController.addCallback(new ZenModeController.Callback() {
            @Override
            public void onZenChanged(int zen) {
                if (mStackView != null) {
                    mStackView.updateDots();
                for (Bubble b : mBubbleData.getBubbles()) {
                    b.setShowDot(b.showInShade(), true /* animate */);
                }
            }

            @Override
            public void onConfigChanged(ZenModeConfig config) {
                if (mStackView != null) {
                    mStackView.updateDots();
                for (Bubble b : mBubbleData.getBubbles()) {
                    b.setShowDot(b.showInShade(), true /* animate */);
                }
            }
        });
@@ -465,7 +465,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
     */
    public boolean isBubbleNotificationSuppressedFromShade(String key) {
        boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key)
                && !mBubbleData.getBubbleWithKey(key).showInShadeWhenBubble();
                && !mBubbleData.getBubbleWithKey(key).showInShade();
        NotificationEntry entry = mNotificationEntryManager.getActiveNotificationUnfiltered(key);
        String groupKey = entry != null ? entry.getSbn().getGroupKey() : null;
        boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey);
@@ -630,11 +630,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                Bubble bubble = mBubbleData.getBubbleWithKey(key);
                boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif;
                if (bubbleExtended) {
                    bubble.setShowInShadeWhenBubble(false);
                    bubble.setShowBubbleDot(false);
                    if (mStackView != null) {
                        mStackView.updateDotVisibility(entry.getKey());
                    }
                    bubble.setShowInShade(false);
                    bubble.setShowDot(false /* show */, true /* animate */);
                    mNotificationEntryManager.updateNotifications(
                            "BubbleController.onNotificationRemoveRequested");
                    return true;
@@ -660,11 +657,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                // As far as group manager is concerned, once a child is no longer shown
                // in the shade, it is essentially removed.
                mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
                bubbleChild.setShowInShadeWhenBubble(false);
                bubbleChild.setShowBubbleDot(false);
                if (mStackView != null) {
                    mStackView.updateDotVisibility(bubbleChild.getKey());
                }
                bubbleChild.setShowInShade(false);
                bubbleChild.setShowDot(false /* show */, true /* animate */);
            }
            // And since all children are removed, remove the summary.
            mNotificationGroupManager.onEntryRemoved(summary);
@@ -767,7 +761,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                // If the bubble is removed for user switching, leave the notification in place.
                if (reason != DISMISS_USER_CHANGED) {
                    if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
                            && !bubble.showInShadeWhenBubble()) {
                            && !bubble.showInShade()) {
                        // The bubble is gone & the notification is gone, time to actually remove it
                        mNotificationEntryManager.performRemoveNotification(
                                bubble.getEntry().getSbn(), UNDEFINED_DISMISS_REASON);
+4 −5
Original line number Diff line number Diff line
@@ -210,8 +210,8 @@ public class BubbleData {
            setSelectedBubbleInternal(bubble);
        }
        boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble;
        bubble.setShowInShadeWhenBubble(!isBubbleExpandedAndSelected && showInShade);
        bubble.setShowBubbleDot(!isBubbleExpandedAndSelected);
        bubble.setShowInShade(!isBubbleExpandedAndSelected && showInShade);
        bubble.setShowDot(!isBubbleExpandedAndSelected /* show */, true /* animate */);
        dispatchPendingChanges();
    }

@@ -303,9 +303,8 @@ public class BubbleData {
        if (notif.getRanking().visuallyInterruptive()) {
            return true;
        }
        final boolean suppressedFromShade = hasBubbleWithKey(notif.getKey())
                && !getBubbleWithKey(notif.getKey()).showInShadeWhenBubble();
        return suppressedFromShade;
        return hasBubbleWithKey(notif.getKey())
                && !getBubbleWithKey(notif.getKey()).showInShade();
    }

    private void doAdd(Bubble bubble) {
Loading