From b8aaf97e041ccbdd6f8559034efa5e57df232236 Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Tue, 26 Nov 2019 10:28:00 -0800 Subject: [PATCH] Move BubbleView methods into BadgedImageView; remove BubbleView * Removed BubbleView * Moved the icon / badge logic into BubbleIconFactory * Moved everything else into BadgedImageView * Introduced dot states in BadgedImageView which hopefully makes that easier to read * Altered Bubble#setShowDot to also animate the dot visibility * Altered BubbleFlyoutView to be able to animate the dot away for DND scenario Test: atest SystemUITests (existing bubble tests pass) Bug: 144719337 Bug: 145245204 (fixes the bit where tapping on bubble in expanded state doesn't animate the dot away but jump cuts instead) Change-Id: I8cebf3e7f93db1920ede95eb6f7392560270767f --- packages/SystemUI/res/layout/bubble_view.xml | 16 +- .../systemui/bubbles/BadgedImageView.java | 246 ++++++++++++--- .../com/android/systemui/bubbles/Bubble.java | 45 +-- .../systemui/bubbles/BubbleController.java | 26 +- .../android/systemui/bubbles/BubbleData.java | 9 +- .../systemui/bubbles/BubbleExpandedView.java | 2 +- .../systemui/bubbles/BubbleFlyoutView.java | 23 +- .../systemui/bubbles/BubbleIconFactory.java | 32 +- .../systemui/bubbles/BubbleStackView.java | 113 +++---- .../systemui/bubbles/BubbleTouchHandler.java | 6 +- .../android/systemui/bubbles/BubbleView.java | 280 ------------------ .../bubbles/BubbleControllerTest.java | 34 ++- .../systemui/bubbles/BubbleDataTest.java | 8 +- .../bubbles/BubbleFlyoutViewTest.java | 9 +- 14 files changed, 368 insertions(+), 481 deletions(-) delete mode 100644 packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml index e2dea45e3406a..78f7cffab6500 100644 --- a/packages/SystemUI/res/layout/bubble_view.xml +++ b/packages/SystemUI/res/layout/bubble_view.xml @@ -14,16 +14,8 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> - - - - - + android:id="@+id/bubble_view" + android:layout_width="@dimen/individual_bubble_size" + android:layout_height="@dimen/individual_bubble_size"/> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java index c0053d194ff65..a6a3ce06324f9 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java @@ -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 int mDotColor; - private float mDotScale = 0f; - private boolean mShowDot; private boolean mOnLeft; - /** Same as value in Launcher3 IconShape */ - static final int DEFAULT_PATH_SIZE = 100; + private int mDotColor; + private float mDotScale = 0f; + private boolean mDotDrawn; + + 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,15 +109,28 @@ 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); } + /** + * Sets the dot state, does not animate changes. + */ + void setDotState(int state) { + mCurrentDotState = state; + if (state == DOT_STATE_SUPPRESSED_FOR_FLYOUT || state == DOT_STATE_DEFAULT) { + mDotScale = mBubble.showDot() ? 1f : 0f; + invalidate(); + } + } + + /** + * Whether the dot should be hidden based on current dot state. + */ + private boolean isDotHidden() { + return (mCurrentDotState == DOT_STATE_DEFAULT && !mBubble.showDot()) + || mCurrentDotState == DOT_STATE_SUPPRESSED_FOR_FLYOUT; + } + /** * Set whether the dot should appear on left or right side of the view. */ @@ -98,29 +139,10 @@ public class BadgedImageView extends ImageView { invalidate(); } - boolean getDotOnLeft() { - return mOnLeft; - } - - /** - * Set whether the dot should show or not. - */ - void setShowDot(boolean showDot) { - mShowDot = showDot; - invalidate(); - } - - /** - * @return whether the dot is being displayed. - */ - boolean isShowingDot() { - return mShowDot; - } - /** * 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. */ @@ -149,11 +178,146 @@ public class BadgedImageView extends ImageView { if (mOnLeft) { dotPosition = mDotRenderer.getLeftDotPosition(); } else { - dotPosition = mDotRenderer.getRightDotPosition(); + dotPosition = mDotRenderer.getRightDotPosition(); } getDrawingRect(mTempBounds); float dotCenterX = mTempBounds.width() * dotPosition[0]; float dotCenterY = mTempBounds.height() * dotPosition[1]; return new float[]{dotCenterX, dotCenterY}; } + + /** + * Populates this view with a bubble. + *

+ * 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); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 24ee969526cc2..ef2868bfbaddf 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -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()); } + void setBubbleIconFactory(BubbleIconFactory factory) { + mBubbleIconFactory = factory; + } + boolean isInflated() { return mInflated; } - void updateDotVisibility() { - if (mIconView != null) { - mIconView.updateDotVisibility(true /* animate */); - } - } - - 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()); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index ed21e141c12dc..1025321ef63ec 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -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); @@ -765,7 +759,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); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 2ca993bd200aa..034bff345d717 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -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) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 512b38e895bbf..a69ff004d4bb9 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -624,7 +624,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList action, mStackView.getNormalizedXPosition(), mStackView.getNormalizedYPosition(), - bubble.showInShadeWhenBubble(), + bubble.showInShade(), bubble.isOngoing(), false /* isAppForeground (unused) */); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java index 58f3f2211d814..78e98eb72fa3f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java @@ -206,7 +206,7 @@ public class BubbleFlyoutView extends FrameLayout { void setupFlyoutStartingAsDot( CharSequence updateMessage, PointF stackPos, float parentWidth, boolean arrowPointingLeft, int dotColor, @Nullable Runnable onLayoutComplete, - @Nullable Runnable onHide, float[] dotCenter) { + @Nullable Runnable onHide, float[] dotCenter, boolean hideDot) { mArrowPointingLeft = arrowPointingLeft; mDotColor = dotColor; mOnHide = onHide; @@ -242,12 +242,14 @@ public class BubbleFlyoutView extends FrameLayout { // Calculate the difference in size between the flyout and the 'dot' so that we can // transform into the dot later. - mFlyoutToDotWidthDelta = getWidth() - mNewDotSize; - mFlyoutToDotHeightDelta = getHeight() - mNewDotSize; + final float newDotSize = hideDot ? 0f : mNewDotSize; + mFlyoutToDotWidthDelta = getWidth() - newDotSize; + mFlyoutToDotHeightDelta = getHeight() - newDotSize; // Calculate the translation values needed to be in the correct 'new dot' position. - final float dotPositionX = stackPos.x + mDotCenter[0] - (mOriginalDotSize / 2f); - final float dotPositionY = stackPos.y + mDotCenter[1] - (mOriginalDotSize / 2f); + final float adjustmentForScaleAway = hideDot ? 0f : (mOriginalDotSize / 2f); + final float dotPositionX = stackPos.x + mDotCenter[0] - adjustmentForScaleAway; + final float dotPositionY = stackPos.y + mDotCenter[1] - adjustmentForScaleAway; final float distanceFromFlyoutLeftToDotCenterX = mRestingTranslationX - dotPositionX; final float distanceFromLayoutTopToDotCenterY = restingTranslationY - dotPositionY; @@ -319,8 +321,7 @@ public class BubbleFlyoutView extends FrameLayout { // percentage. final float width = getWidth() - (mFlyoutToDotWidthDelta * mPercentTransitionedToDot); final float height = getHeight() - (mFlyoutToDotHeightDelta * mPercentTransitionedToDot); - final float interpolatedRadius = mNewDotRadius * mPercentTransitionedToDot - + mCornerRadius * (1 - mPercentTransitionedToDot); + final float interpolatedRadius = getInterpolatedRadius(); // Translate the flyout background towards the collapsed 'dot' state. mBgTranslationX = mTranslationXWhenDot * mPercentTransitionedToDot; @@ -387,8 +388,7 @@ public class BubbleFlyoutView extends FrameLayout { if (!mTriangleOutline.isEmpty()) { // Draw the rect into the outline as a path so we can merge the triangle path into it. final Path rectPath = new Path(); - final float interpolatedRadius = mNewDotRadius * mPercentTransitionedToDot - + mCornerRadius * (1 - mPercentTransitionedToDot); + final float interpolatedRadius = getInterpolatedRadius(); rectPath.addRoundRect(mBgRect, interpolatedRadius, interpolatedRadius, Path.Direction.CW); outline.setConvexPath(rectPath); @@ -420,4 +420,9 @@ public class BubbleFlyoutView extends FrameLayout { outline.mPath.transform(outlineMatrix); } } + + private float getInterpolatedRadius() { + return mNewDotRadius * mPercentTransitionedToDot + + mCornerRadius * (1 - mPercentTransitionedToDot); + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java index a1c77c0af6bc2..9ff033cb34111 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java @@ -16,8 +16,14 @@ package com.android.systemui.bubbles; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import com.android.launcher3.icons.BaseIconFactory; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.ShadowGenerator; import com.android.systemui.R; /** @@ -26,13 +32,37 @@ import com.android.systemui.R; * so there is no need to manage a pool across multiple threads. */ public class BubbleIconFactory extends BaseIconFactory { + protected BubbleIconFactory(Context context) { super(context, context.getResources().getConfiguration().densityDpi, context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size)); } - public int getBadgeSize() { + int getBadgeSize() { return mContext.getResources().getDimensionPixelSize( com.android.launcher3.icons.R.dimen.profile_badge_size); } + + BitmapInfo getBadgedBitmap(Bubble b) { + Bitmap userBadgedBitmap = createIconBitmap( + b.getUserBadgedAppIcon(), 1f, getBadgeSize()); + + Canvas c = new Canvas(); + ShadowGenerator shadowGenerator = new ShadowGenerator(getBadgeSize()); + c.setBitmap(userBadgedBitmap); + shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c); + BitmapInfo bitmapInfo = createIconBitmap(userBadgedBitmap); + return bitmapInfo; + } + + BitmapInfo getBubbleBitmap(Drawable bubble, BitmapInfo badge) { + BitmapInfo bubbleIconInfo = createBadgedIconBitmap(bubble, + null /* user */, + true /* shrinkNonAdaptiveIcons */); + + badgeWithDrawable(bubbleIconInfo.icon, + new BitmapDrawable(mContext.getResources(), badge.icon)); + return bubbleIconInfo; + } + } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 4a1bbe48efb01..29de2f0496900 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -19,6 +19,8 @@ package com.android.systemui.bubbles; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static com.android.systemui.bubbles.BadgedImageView.DOT_STATE_DEFAULT; +import static com.android.systemui.bubbles.BadgedImageView.DOT_STATE_SUPPRESSED_FOR_FLYOUT; import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; @@ -169,7 +171,7 @@ public class BubbleStackView extends FrameLayout { * Callback to run after the flyout hides. Also called if a new flyout is shown before the * previous one animates out. */ - private Runnable mFlyoutOnHide; + private Runnable mAfterFlyoutHidden; /** Layout change listener that moves the stack to the nearest valid position on rotation. */ private OnLayoutChangeListener mOrientationChangedListener; @@ -673,18 +675,6 @@ public class BubbleStackView extends FrameLayout { } } - /** - * Updates the visibility of the 'dot' indicating an update on the bubble. - * - * @param key the {@link NotificationEntry#key} associated with the bubble. - */ - public void updateDotVisibility(String key) { - Bubble b = mBubbleData.getBubbleWithKey(key); - if (b != null) { - b.updateDotVisibility(); - } - } - /** * Sets the listener to notify when the bubble stack is expanded. */ @@ -707,9 +697,9 @@ public class BubbleStackView extends FrameLayout { } /** - * The {@link BubbleView} that is expanded, null if one does not exist. + * The {@link BadgedImageView} that is expanded, null if one does not exist. */ - BubbleView getExpandedBubbleView() { + BadgedImageView getExpandedBubbleView() { return mExpandedBubble != null ? mExpandedBubble.getIconView() : null; } @@ -731,7 +721,7 @@ public class BubbleStackView extends FrameLayout { Bubble bubbleToExpand = mBubbleData.getBubbleWithKey(key); if (bubbleToExpand != null) { setSelectedBubble(bubbleToExpand); - bubbleToExpand.setShowInShadeWhenBubble(false); + bubbleToExpand.setShowInShade(false); setExpanded(true); } } @@ -746,8 +736,8 @@ public class BubbleStackView extends FrameLayout { mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); } + bubble.setBubbleIconFactory(mBubbleIconFactory); bubble.inflate(mInflater, this); - bubble.getIconView().setBubbleIconFactory(mBubbleIconFactory); bubble.getIconView().updateViews(); // Set the dot position to the opposite of the side the stack is resting on, since the stack @@ -884,7 +874,7 @@ public class BubbleStackView extends FrameLayout { if (isIntersecting(mBubbleContainer, x, y)) { // Could be tapping or dragging a bubble while expanded for (int i = 0; i < mBubbleContainer.getChildCount(); i++) { - BubbleView view = (BubbleView) mBubbleContainer.getChildAt(i); + BadgedImageView view = (BadgedImageView) mBubbleContainer.getChildAt(i); if (isIntersecting(view, x, y)) { return view; } @@ -1028,9 +1018,9 @@ public class BubbleStackView extends FrameLayout { } /** Return the BubbleView at the given index from the bubble container. */ - public BubbleView getBubbleAt(int i) { + public BadgedImageView getBubbleAt(int i) { return mBubbleContainer.getChildCount() > i - ? (BubbleView) mBubbleContainer.getChildAt(i) + ? (BadgedImageView) mBubbleContainer.getChildAt(i) : null; } @@ -1382,16 +1372,6 @@ public class BubbleStackView extends FrameLayout { : 0f); } - /** Updates the dot visibility, this is used in response to a zen mode config change. */ - void updateDots() { - int bubbsCount = mBubbleContainer.getChildCount(); - for (int i = 0; i < bubbsCount; i++) { - BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); - // If nothing changed the animation won't happen - bv.updateDotVisibility(true /* animate */); - } - } - /** * Calculates the y position of the expanded view when it is expanded. */ @@ -1405,37 +1385,40 @@ public class BubbleStackView extends FrameLayout { @VisibleForTesting void animateInFlyoutForBubble(Bubble bubble) { final CharSequence updateMessage = bubble.getUpdateMessage(getContext()); - if (!bubble.showFlyoutForBubble()) { - // In case flyout was suppressed for this update, reset now. - bubble.setSuppressFlyout(false); - return; - } + final BadgedImageView bubbleView = bubble.getIconView(); if (updateMessage == null + || !bubble.showFlyout() || isExpanded() || mIsExpansionAnimating || mIsGestureInProgress || mBubbleToExpandAfterFlyoutCollapse != null - || bubble.getIconView() == null) { + || bubbleView == null) { + if (bubbleView != null) { + bubbleView.setDotState(DOT_STATE_DEFAULT); + } // Skip the message if none exists, we're expanded or animating expansion, or we're // about to expand a bubble from the previous tapped flyout, or if bubble view is null. return; } + mFlyoutDragDeltaX = 0f; clearFlyoutOnHide(); - mFlyoutOnHide = () -> { - resetDot(bubble); - if (mBubbleToExpandAfterFlyoutCollapse == null) { - return; + mAfterFlyoutHidden = () -> { + // Null it out to ensure it runs once. + mAfterFlyoutHidden = null; + + if (mBubbleToExpandAfterFlyoutCollapse != null) { + // User tapped on the flyout and we should expand + mBubbleData.setSelectedBubble(mBubbleToExpandAfterFlyoutCollapse); + mBubbleData.setExpanded(true); + mBubbleToExpandAfterFlyoutCollapse = null; } - mBubbleData.setSelectedBubble(mBubbleToExpandAfterFlyoutCollapse); - mBubbleData.setExpanded(true); - mBubbleToExpandAfterFlyoutCollapse = null; + bubbleView.setDotState(DOT_STATE_DEFAULT); }; mFlyout.setVisibility(INVISIBLE); - // Temporarily suppress the dot while the flyout is visible. - bubble.getIconView().setSuppressDot( - true /* suppressDot */, false /* animate */); + // Don't show the dot when we're animating the flyout + bubbleView.setDotState(DOT_STATE_SUPPRESSED_FOR_FLYOUT); // Start flyout expansion. Post in case layout isn't complete and getWidth returns 0. post(() -> { @@ -1461,8 +1444,9 @@ public class BubbleStackView extends FrameLayout { mStackAnimationController.isStackOnLeftSide(), bubble.getIconView().getDotColor() /* dotColor */, expandFlyoutAfterDelay /* onLayoutComplete */, - mFlyoutOnHide, - bubble.getIconView().getDotCenter()); + mAfterFlyoutHidden, + bubble.getIconView().getDotCenter(), + !bubble.showDot()); mFlyout.bringToFront(); }); mFlyout.removeCallbacks(mHideFlyout); @@ -1470,24 +1454,6 @@ public class BubbleStackView extends FrameLayout { logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT); } - private void resetDot(Bubble bubble) { - final boolean suppressDot = !bubble.showBubbleDot(); - // If we're going to suppress the dot, make it visible first so it'll - // visibly animate away. - - if (suppressDot) { - bubble.getIconView().setSuppressDot( - false /* suppressDot */, false /* animate */); - } - // Reset dot suppression. If we're not suppressing due to DND, then - // stop suppressing it with no animation (since the flyout has - // transformed into the dot). If we are suppressing due to DND, animate - // it away. - bubble.getIconView().setSuppressDot( - suppressDot /* suppressDot */, - suppressDot /* animate */); - } - /** Hide the flyout immediately and cancel any pending hide runnables. */ private void hideFlyoutImmediate() { clearFlyoutOnHide(); @@ -1498,11 +1464,11 @@ public class BubbleStackView extends FrameLayout { private void clearFlyoutOnHide() { mFlyout.removeCallbacks(mAnimateInFlyout); - if (mFlyoutOnHide == null) { + if (mAfterFlyoutHidden == null) { return; } - mFlyoutOnHide.run(); - mFlyoutOnHide = null; + mAfterFlyoutHidden.run(); + mAfterFlyoutHidden = null; } @Override @@ -1599,8 +1565,7 @@ public class BubbleStackView extends FrameLayout { private void updateBubbleZOrdersAndDotPosition(boolean animate) { int bubbleCount = mBubbleContainer.getChildCount(); for (int i = 0; i < bubbleCount; i++) { - BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); - bv.updateDotVisibility(true /* animate */); + BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i); bv.setZ((mMaxBubbles * mBubbleElevation) - i); // If the dot is on the left, and so is the stack, we need to change the dot position. if (bv.getDotPositionOnLeft() == mStackOnLeftOrWillBe) { @@ -1705,7 +1670,7 @@ public class BubbleStackView extends FrameLayout { action, getNormalizedXPosition(), getNormalizedYPosition(), - bubble.showInShadeWhenBubble(), + bubble.showInShade(), bubble.isOngoing(), false /* isAppForeground (unused) */); } @@ -1727,8 +1692,8 @@ public class BubbleStackView extends FrameLayout { List bubbles = new ArrayList<>(); for (int i = 0; i < mBubbleContainer.getChildCount(); i++) { View child = mBubbleContainer.getChildAt(i); - if (child instanceof BubbleView) { - String key = ((BubbleView) child).getKey(); + if (child instanceof BadgedImageView) { + String key = ((BadgedImageView) child).getKey(); Bubble bubble = mBubbleData.getBubbleWithKey(key); bubbles.add(bubble); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index 4240e06a8800e..44e013a34f548 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -95,7 +95,7 @@ class BubbleTouchHandler implements View.OnTouchListener { return false; } - if (!(mTouchedView instanceof BubbleView) + if (!(mTouchedView instanceof BadgedImageView) && !(mTouchedView instanceof BubbleStackView) && !(mTouchedView instanceof BubbleFlyoutView)) { // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge @@ -187,7 +187,7 @@ class BubbleTouchHandler implements View.OnTouchListener { mStack.onFlyoutDragFinished(rawX - mTouchDown.x /* deltaX */, velX); } else if (shouldDismiss) { final String individualBubbleKey = - isStack ? null : ((BubbleView) mTouchedView).getKey(); + isStack ? null : ((BadgedImageView) mTouchedView).getKey(); mStack.magnetToStackIfNeededThenAnimateDismissal(mTouchedView, velX, velY, () -> { if (isStack) { @@ -214,7 +214,7 @@ class BubbleTouchHandler implements View.OnTouchListener { // Toggle expansion mBubbleData.setExpanded(!mBubbleData.isExpanded()); } else { - final String key = ((BubbleView) mTouchedView).getKey(); + final String key = ((BadgedImageView) mTouchedView).getKey(); mBubbleData.setSelectedBubble(mBubbleData.getBubbleWithKey(key)); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java deleted file mode 100644 index 79807b388c8ce..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.bubbles; - -import android.annotation.Nullable; -import android.app.Notification; -import android.content.Context; -import android.content.pm.LauncherApps; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.Path; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; -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; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; - -/** - * A floating object on the screen that can post message updates. - */ -public class BubbleView extends FrameLayout { - - // Same value as Launcher3 badge code - private static final float WHITE_SCRIM_ALPHA = 0.54f; - private Context mContext; - - private BadgedImageView mBadgedImageView; - private int mDotColor; - private ColorExtractor mColorExtractor; - - // mBubbleIconFactory cannot be static because it depends on Context. - private BubbleIconFactory mBubbleIconFactory; - - private boolean mSuppressDot; - - private Bubble mBubble; - - public BubbleView(Context context) { - this(context, null); - } - - public BubbleView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public BubbleView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - mContext = context; - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mBadgedImageView = findViewById(R.id.bubble_image); - mColorExtractor = new ColorExtractor(); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - } - - /** - * Populates this view with a bubble. - *

- * 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 {@link NotificationEntry} associated with this view, if one exists. - */ - @Nullable - public NotificationEntry getEntry() { - return mBubble != null ? mBubble.getEntry() : null; - } - - /** - * The key for the {@link NotificationEntry} 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; - updateViews(); - } - - /** Changes the dot's visibility to match the bubble view's state. */ - void updateDotVisibility(boolean animate) { - updateDotVisibility(animate, null /* after */); - } - - /** - * Sets whether or not to hide the dot even if we'd otherwise show it. This is used while the - * flyout is visible or animating, to hide the dot until the flyout visually transforms into it. - */ - void setSuppressDot(boolean suppressDot, boolean animate) { - mSuppressDot = suppressDot; - 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() && isDotShowing()) { - animateDot(false /* showDot */, () -> { - mBadgedImageView.setDotOnLeft(onLeft); - animateDot(true /* showDot */, null); - }); - } else { - mBadgedImageView.setDotOnLeft(onLeft); - } - } - - float[] getDotCenter() { - float[] unscaled = mBadgedImageView.getDotCenter(); - return new float[]{unscaled[0], unscaled[1]}; - } - - boolean getDotPositionOnLeft() { - return mBadgedImageView.getDotOnLeft(); - } - - /** - * Changes the dot's visibility to match the bubble view's state, running the provided callback - * after animation if requested. - */ - private void updateDotVisibility(boolean animate, Runnable after) { - final boolean showDot = isDotShowing(); - if (animate) { - animateDot(showDot, after); - } else { - mBadgedImageView.setShowDot(showDot); - mBadgedImageView.setDotScale(showDot ? 1f : 0f); - } - } - - /** - * Animates the badge to show or hide. - */ - private void animateDot(boolean showDot, Runnable after) { - if (mBadgedImageView.isShowingDot() == showDot) { - return; - } - // Do NOT wait until after animation ends to setShowDot - // to avoid overriding more recent showDot states. - mBadgedImageView.setShowDot(showDot); - mBadgedImageView.clearAnimation(); - mBadgedImageView.animate().setDuration(200) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .setUpdateListener((valueAnimator) -> { - float fraction = valueAnimator.getAnimatedFraction(); - fraction = showDot ? fraction : 1f - fraction; - mBadgedImageView.setDotScale(fraction); - }).withEndAction(() -> { - mBadgedImageView.setDotScale(showDot ? 1f : 0f); - if (after != null) { - after.run(); - } - }).start(); - } - - void updateViews() { - if (mBubble == null || mBubbleIconFactory == null) { - return; - } - - Drawable bubbleDrawable = getBubbleDrawable(mContext); - BitmapInfo badgeBitmapInfo = getBadgedBitmap(); - BitmapInfo bubbleBitmapInfo = getBubbleBitmap(bubbleDrawable, badgeBitmapInfo); - mBadgedImageView.setImageBitmap(bubbleBitmapInfo.icon); - - // Update badge. - 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(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); - mBadgedImageView.drawDot(iconPath); - - animateDot(isDotShowing(), null /* after */); - } - - 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 = getEntry().getBubbleMetadata(); - Icon ic = metadata.getIcon(); - return ic.loadDrawable(context); - } - } - - BitmapInfo getBadgedBitmap() { - Bitmap userBadgedBitmap = mBubbleIconFactory.createIconBitmap( - mBubble.getUserBadgedAppIcon(), 1f, mBubbleIconFactory.getBadgeSize()); - - 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; - } - - BitmapInfo getBubbleBitmap(Drawable bubble, BitmapInfo badge) { - BitmapInfo bubbleIconInfo = mBubbleIconFactory.createBadgedIconBitmap(bubble, - null /* user */, - true /* shrinkNonAdaptiveIcons */); - - mBubbleIconFactory.badgeWithDrawable(bubbleIconInfo.icon, - new BitmapDrawable(mContext.getResources(), badge.icon)); - return bubbleIconInfo; - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 8c9f75950eb43..4c707f45efc19 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -157,9 +157,14 @@ public class BubbleControllerTest extends SysuiTestCase { private SuperStatusBarViewFactory mSuperStatusBarViewFactory; private BubbleData mBubbleData; + private TestableLooper mTestableLooper; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + + mTestableLooper = TestableLooper.get(this); + mContext.addMockSystemService(FaceManager.class, mFaceManager); when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); @@ -262,7 +267,7 @@ public class BubbleControllerTest extends SysuiTestCase { mRow.getEntry().getKey())); // Make it look like dismissed notif - mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).setShowInShadeWhenBubble(false); + mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).setShowInShade(false); // Now remove the bubble mBubbleController.removeBubble( @@ -346,14 +351,14 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry()); + assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry().getKey())); // Switch which bubble is expanded mBubbleController.selectBubble(mRow.getEntry().getKey()); stackView.setExpandedBubble(mRow.getEntry().getKey()); - assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry()); + assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry().getKey())); @@ -377,7 +382,9 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry().getKey())); - assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showBubbleDot()); + + mTestableLooper.processAllMessages(); + assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); // Expand mBubbleController.expandStack(); @@ -388,7 +395,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry().getKey())); // Notif shouldn't show dot after expansion - assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showBubbleDot()); + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); } @Test @@ -401,10 +408,11 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry().getKey())); - assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showBubbleDot()); + + mTestableLooper.processAllMessages(); + assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); // Expand - BubbleStackView stackView = mBubbleController.getStackView(); mBubbleController.expandStack(); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); @@ -413,7 +421,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry().getKey())); // Notif shouldn't show dot after expansion - assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showBubbleDot()); + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); // Send update mEntryListener.onPreEntryUpdated(mRow.getEntry()); @@ -423,7 +431,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry().getKey())); // Notif shouldn't show dot after expansion - assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showBubbleDot()); + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); } @Test @@ -443,7 +451,7 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry()); + assertEquals(mRow2.getEntry(), stackView.getExpandedBubble().getEntry()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry().getKey())); @@ -453,7 +461,7 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); // Make sure first bubble is selected - assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry()); + assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); // Dismiss that one @@ -555,7 +563,9 @@ public class BubbleControllerTest extends SysuiTestCase { mEntryListener.onPendingEntryAdded(mRow.getEntry()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry().getKey())); - assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showBubbleDot()); + + mTestableLooper.processAllMessages(); + assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index a9be30ba82a63..95c7af31865b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -183,7 +183,7 @@ public class BubbleDataTest extends SysuiTestCase { // Verify verifyUpdateReceived(); BubbleData.Update update = mUpdateCaptor.getValue(); - assertThat(update.addedBubble.showFlyoutForBubble()).isFalse(); + assertThat(update.addedBubble.showFlyout()).isFalse(); } @Test @@ -199,7 +199,7 @@ public class BubbleDataTest extends SysuiTestCase { // Verify verifyUpdateReceived(); BubbleData.Update update = mUpdateCaptor.getValue(); - assertThat(update.addedBubble.showFlyoutForBubble()).isTrue(); + assertThat(update.addedBubble.showFlyout()).isTrue(); } @Test @@ -218,7 +218,7 @@ public class BubbleDataTest extends SysuiTestCase { // Verify BubbleData.Update update = mUpdateCaptor.getValue(); - assertThat(update.updatedBubble.showFlyoutForBubble()).isFalse(); + assertThat(update.updatedBubble.showFlyout()).isFalse(); } @Test @@ -239,7 +239,7 @@ public class BubbleDataTest extends SysuiTestCase { // Verify BubbleData.Update update = mUpdateCaptor.getValue(); - assertThat(update.updatedBubble.showFlyoutForBubble()).isTrue(); + assertThat(update.updatedBubble.showFlyout()).isTrue(); } // COLLAPSED / ADD diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java index a8961a85c4c70..376ecf7815ac5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java @@ -60,7 +60,8 @@ public class BubbleFlyoutViewTest extends SysuiTestCase { @Test public void testShowFlyout_isVisible() { mFlyout.setupFlyoutStartingAsDot( - "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter); + "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter, + false); mFlyout.setVisibility(View.VISIBLE); assertEquals("Hello", mFlyoutText.getText()); @@ -71,7 +72,8 @@ public class BubbleFlyoutViewTest extends SysuiTestCase { public void testFlyoutHide_runsCallback() { Runnable after = Mockito.mock(Runnable.class); mFlyout.setupFlyoutStartingAsDot( - "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, after, mDotCenter); + "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, after, mDotCenter, + false); mFlyout.hideFlyout(); verify(after).run(); @@ -80,7 +82,8 @@ public class BubbleFlyoutViewTest extends SysuiTestCase { @Test public void testSetCollapsePercent() { mFlyout.setupFlyoutStartingAsDot( - "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter); + "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter, + false); mFlyout.setVisibility(View.VISIBLE); mFlyout.setCollapsePercent(1f);