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
This commit is contained in:
Mady Mellor
2019-11-26 10:28:00 -08:00
parent 5d8707ade7
commit b8aaf97e04
14 changed files with 368 additions and 481 deletions

View File

@@ -14,16 +14,8 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<com.android.systemui.bubbles.BubbleView
<com.android.systemui.bubbles.BadgedImageView
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"
android:layout_width="@dimen/individual_bubble_size"
android:layout_height="@dimen/individual_bubble_size"
android:clipToPadding="false"/>
</com.android.systemui.bubbles.BubbleView>
android:id="@+id/bubble_view"
android:layout_width="@dimen/individual_bubble_size"
android:layout_height="@dimen/individual_bubble_size"/>

View File

@@ -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.
* <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);
}
}
}

View File

@@ -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());

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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) */);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<Bubble> 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);
}

View File

@@ -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));
}

View File

@@ -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.
* <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 {@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;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);