Merge changes I4794ed89,I0c273f4d,Ibd8609a8 into sc-dev

* changes:
  Flexible maximum number of bubbles to show
  Add shadows back to bubble avatar
  Update sizes for the bubble avatar
This commit is contained in:
Mady Mellor
2021-05-27 22:51:57 +00:00
committed by Android (Google) Code Review
18 changed files with 281 additions and 98 deletions

View File

@@ -25,8 +25,8 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bubble_view" android:id="@+id/bubble_view"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_width="@dimen/individual_bubble_size" android:layout_width="@dimen/bubble_size"
android:layout_height="@dimen/individual_bubble_size"/> android:layout_height="@dimen/bubble_size"/>
<TextView <TextView
android:id="@+id/bubble_view_name" android:id="@+id/bubble_view_name"

View File

@@ -101,12 +101,12 @@
<dimen name="bubble_flyout_avatar_message_space">6dp</dimen> <dimen name="bubble_flyout_avatar_message_space">6dp</dimen>
<!-- Padding between status bar and bubbles when displayed in expanded state --> <!-- Padding between status bar and bubbles when displayed in expanded state -->
<dimen name="bubble_padding_top">16dp</dimen> <dimen name="bubble_padding_top">16dp</dimen>
<!-- Max amount of space between bubbles when expanded. --> <!-- Space between bubbles when expanded. -->
<dimen name="bubble_max_spacing">8dp</dimen> <dimen name="bubble_spacing">3dp</dimen>
<!-- Size of individual bubbles. --> <!-- Size of the bubble. -->
<dimen name="individual_bubble_size">60dp</dimen> <dimen name="bubble_size">60dp</dimen>
<!-- Size of bubble bitmap. --> <!-- Size of the badge shown on the bubble. -->
<dimen name="bubble_bitmap_size">52dp</dimen> <dimen name="bubble_badge_size">24dp</dimen>
<!-- Extra padding added to the touchable rect for bubbles so they are easier to grab. --> <!-- Extra padding added to the touchable rect for bubbles so they are easier to grab. -->
<dimen name="bubble_touch_padding">12dp</dimen> <dimen name="bubble_touch_padding">12dp</dimen>
<!-- Size of the circle around the bubbles when they're in the dismiss target. --> <!-- Size of the circle around the bubbles when they're in the dismiss target. -->
@@ -120,6 +120,8 @@
<dimen name="bubble_expanded_default_height">180dp</dimen> <dimen name="bubble_expanded_default_height">180dp</dimen>
<!-- On large screens the width of the expanded view is restricted to this size. --> <!-- On large screens the width of the expanded view is restricted to this size. -->
<dimen name="bubble_expanded_view_tablet_width">412dp</dimen> <dimen name="bubble_expanded_view_tablet_width">412dp</dimen>
<!-- Inset to apply to the icon in the overflow button. -->
<dimen name="bubble_overflow_icon_inset">30dp</dimen>
<!-- Default (and minimum) height of bubble overflow --> <!-- Default (and minimum) height of bubble overflow -->
<dimen name="bubble_overflow_height">480dp</dimen> <dimen name="bubble_overflow_height">480dp</dimen>
<!-- Bubble overflow padding when there are no bubbles --> <!-- Bubble overflow padding when there are no bubbles -->
@@ -154,7 +156,7 @@
<!-- Interior padding of the message bubble --> <!-- Interior padding of the message bubble -->
<dimen name="bubble_message_padding">4dp</dimen> <dimen name="bubble_message_padding">4dp</dimen>
<!-- Offset between bubbles in their stacked position. --> <!-- Offset between bubbles in their stacked position. -->
<dimen name="bubble_stack_offset">10dp</dimen> <dimen name="bubble_stack_offset">12dp</dimen>
<!-- Offset between stack y and animation y for bubble swap. --> <!-- Offset between stack y and animation y for bubble swap. -->
<dimen name="bubble_swap_animation_offset">15dp</dimen> <dimen name="bubble_swap_animation_offset">15dp</dimen>
<!-- How far offscreen the bubble stack rests. There's some padding around the bubble so <!-- How far offscreen the bubble stack rests. There's some padding around the bubble so

View File

@@ -23,15 +23,19 @@ import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter; import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Rect; import android.graphics.Rect;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.PathParser; import android.util.PathParser;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.ImageView; import android.widget.ImageView;
import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.animation.Interpolators;
import java.util.EnumSet; import java.util.EnumSet;
@@ -104,6 +108,19 @@ public class BadgedImageView extends ImageView {
setFocusable(true); setFocusable(true);
setClickable(true); setClickable(true);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
BadgedImageView.this.getOutline(outline);
}
});
}
private void getOutline(Outline outline) {
final int bubbleSize = mPositioner.getBubbleSize();
final int normalizedSize = IconNormalizer.getNormalizedCircleSize(bubbleSize);
final int inset = (bubbleSize - normalizedSize) / 2;
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
} }
public void initialize(BubblePositioner positioner) { public void initialize(BubblePositioner positioner) {
@@ -111,7 +128,7 @@ public class BadgedImageView extends ImageView {
Path iconPath = PathParser.createPathFromPathData( Path iconPath = PathParser.createPathFromPathData(
getResources().getString(com.android.internal.R.string.config_icon_mask)); getResources().getString(com.android.internal.R.string.config_icon_mask));
mDotRenderer = new DotRenderer(mPositioner.getBubbleBitmapSize(), mDotRenderer = new DotRenderer(mPositioner.getBubbleSize(),
iconPath, DEFAULT_PATH_SIZE); iconPath, DEFAULT_PATH_SIZE);
} }
@@ -192,7 +209,7 @@ public class BadgedImageView extends ImageView {
* @param iconPath The new icon path to use when calculating dot position. * @param iconPath The new icon path to use when calculating dot position.
*/ */
void drawDot(Path iconPath) { void drawDot(Path iconPath) {
mDotRenderer = new DotRenderer(mPositioner.getBubbleBitmapSize(), mDotRenderer = new DotRenderer(mPositioner.getBubbleSize(),
iconPath, DEFAULT_PATH_SIZE); iconPath, DEFAULT_PATH_SIZE);
invalidate(); invalidate();
} }

View File

@@ -722,6 +722,7 @@ public class BubbleController {
|| !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) { || !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) {
mDensityDpi = newConfig.densityDpi; mDensityDpi = newConfig.densityDpi;
mScreenBounds.set(newConfig.windowConfiguration.getBounds()); mScreenBounds.set(newConfig.windowConfiguration.getBounds());
mBubbleData.onMaxBubblesChanged();
mBubbleIconFactory = new BubbleIconFactory(mContext); mBubbleIconFactory = new BubbleIconFactory(mContext);
mStackView.onDisplaySizeChanged(); mStackView.onDisplaySizeChanged();
} }

View File

@@ -141,9 +141,11 @@ public class BubbleData {
private final BubbleOverflow mOverflow; private final BubbleOverflow mOverflow;
private boolean mShowingOverflow; private boolean mShowingOverflow;
private boolean mExpanded; private boolean mExpanded;
private final int mMaxBubbles; private int mMaxBubbles;
private int mMaxOverflowBubbles; private int mMaxOverflowBubbles;
private boolean mNeedsTrimming;
// State tracked during an operation -- keeps track of what listener events to dispatch. // State tracked during an operation -- keeps track of what listener events to dispatch.
private Update mStateChange; private Update mStateChange;
@@ -180,7 +182,7 @@ public class BubbleData {
mOverflowBubbles = new ArrayList<>(); mOverflowBubbles = new ArrayList<>();
mPendingBubbles = new HashMap<>(); mPendingBubbles = new HashMap<>();
mStateChange = new Update(mBubbles, mOverflowBubbles); mStateChange = new Update(mBubbles, mOverflowBubbles);
mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered); mMaxBubbles = mPositioner.getMaxBubbles();
mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow); mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow);
} }
@@ -194,6 +196,16 @@ public class BubbleData {
mCancelledListener = listener; mCancelledListener = listener;
} }
public void onMaxBubblesChanged() {
mMaxBubbles = mPositioner.getMaxBubbles();
if (!mExpanded) {
trim();
dispatchPendingChanges();
} else {
mNeedsTrimming = true;
}
}
public boolean hasBubbles() { public boolean hasBubbles() {
return !mBubbles.isEmpty(); return !mBubbles.isEmpty();
} }
@@ -455,13 +467,19 @@ public class BubbleData {
private void trim() { private void trim() {
if (mBubbles.size() > mMaxBubbles) { if (mBubbles.size() > mMaxBubbles) {
int numtoRemove = mBubbles.size() - mMaxBubbles;
ArrayList<Bubble> toRemove = new ArrayList<>();
mBubbles.stream() mBubbles.stream()
// sort oldest first (ascending lastActivity) // sort oldest first (ascending lastActivity)
.sorted(Comparator.comparingLong(Bubble::getLastActivity)) .sorted(Comparator.comparingLong(Bubble::getLastActivity))
// skip the selected bubble // skip the selected bubble
.filter((b) -> !b.equals(mSelectedBubble)) .filter((b) -> !b.equals(mSelectedBubble))
.findFirst() .forEachOrdered((b) -> {
.ifPresent((b) -> doRemove(b.getKey(), Bubbles.DISMISS_AGED)); if (toRemove.size() < numtoRemove) {
toRemove.add(b);
}
});
toRemove.forEach((b) -> doRemove(b.getKey(), Bubbles.DISMISS_AGED));
} }
} }
@@ -770,6 +788,10 @@ public class BubbleData {
} }
} }
} }
if (mNeedsTrimming) {
mNeedsTrimming = false;
trim();
}
mExpanded = shouldExpand; mExpanded = shouldExpand;
mStateChange.expanded = shouldExpand; mStateChange.expanded = shouldExpand;
mStateChange.expandedChanged = true; mStateChange.expandedChanged = true;

View File

@@ -60,6 +60,7 @@ import android.widget.LinearLayout;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R; import com.android.wm.shell.R;
import com.android.wm.shell.TaskView; import com.android.wm.shell.TaskView;
import com.android.wm.shell.common.AlphaOptimizedButton; import com.android.wm.shell.common.AlphaOptimizedButton;
@@ -726,9 +727,13 @@ public class BubbleExpandedView extends LinearLayout {
setPadding((int) paddingLeft, (int) paddingTop, (int) paddingRight, 0); setPadding((int) paddingLeft, (int) paddingTop, (int) paddingRight, 0);
final float expandedViewY = mPositioner.getExpandedViewY(); final float expandedViewY = mPositioner.getExpandedViewY();
// TODO: I don't understand why it works but it does - why normalized in portrait
// & not in landscape? Am I missing ~2dp in the portrait expandedViewY calculation?
final float normalizedSize = IconNormalizer.getNormalizedCircleSize(
mPositioner.getBubbleSize());
final float bubbleCenter = showVertically final float bubbleCenter = showVertically
? bubblePosition + (mPositioner.getBubbleSize() / 2f) - expandedViewY ? bubblePosition + (mPositioner.getBubbleSize() / 2f) - expandedViewY
: bubblePosition + (mPositioner.getBubbleBitmapSize() / 2f) - mPointerWidth; : bubblePosition + (normalizedSize / 2f) - mPointerWidth;
// Post because we need the width of the view // Post because we need the width of the view
post(() -> { post(() -> {
float pointerY; float pointerY;

View File

@@ -72,7 +72,6 @@ public class BubbleFlyoutView extends FrameLayout {
private final int mFlyoutSpaceFromBubble; private final int mFlyoutSpaceFromBubble;
private final int mPointerSize; private final int mPointerSize;
private int mBubbleSize; private int mBubbleSize;
private int mBubbleBitmapSize;
private final int mFlyoutElevation; private final int mFlyoutElevation;
private final int mBubbleElevation; private final int mBubbleElevation;
@@ -337,10 +336,9 @@ public class BubbleFlyoutView extends FrameLayout {
boolean hideDot, boolean hideDot,
BubblePositioner positioner) { BubblePositioner positioner) {
mBubbleBitmapSize = positioner.getBubbleBitmapSize();
mBubbleSize = positioner.getBubbleSize(); mBubbleSize = positioner.getBubbleSize();
mOriginalDotSize = SIZE_PERCENTAGE * mBubbleBitmapSize; mOriginalDotSize = SIZE_PERCENTAGE * mBubbleSize;
mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f; mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f;
mNewDotSize = mNewDotRadius * 2f; mNewDotSize = mNewDotRadius * 2f;

View File

@@ -48,9 +48,8 @@ public class BubbleIconFactory extends BaseIconFactory {
public BubbleIconFactory(Context context) { public BubbleIconFactory(Context context) {
super(context, context.getResources().getConfiguration().densityDpi, super(context, context.getResources().getConfiguration().densityDpi,
context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size)); context.getResources().getDimensionPixelSize(R.dimen.bubble_size));
mBadgeSize = mContext.getResources().getDimensionPixelSize( mBadgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size);
com.android.launcher3.icons.R.dimen.profile_badge_size);
} }
/** /**

View File

@@ -30,12 +30,6 @@ import android.view.LayoutInflater
import android.widget.FrameLayout import android.widget.FrameLayout
import com.android.wm.shell.R import com.android.wm.shell.R
/**
* The icon in the bubble overflow is scaled down, this is the percent of the normal bubble bitmap
* size to use.
*/
const val ICON_BITMAP_SIZE_PERCENT = 0.46f
class BubbleOverflow( class BubbleOverflow(
private val context: Context, private val context: Context,
private val positioner: BubblePositioner private val positioner: BubblePositioner
@@ -44,10 +38,9 @@ class BubbleOverflow(
private lateinit var bitmap: Bitmap private lateinit var bitmap: Bitmap
private lateinit var dotPath: Path private lateinit var dotPath: Path
private var bitmapSize = 0
private var iconBitmapSize = 0
private var dotColor = 0 private var dotColor = 0
private var showDot = false private var showDot = false
private var overflowIconInset = 0
private val inflater: LayoutInflater = LayoutInflater.from(context) private val inflater: LayoutInflater = LayoutInflater.from(context)
private var expandedView: BubbleExpandedView? private var expandedView: BubbleExpandedView?
@@ -55,8 +48,6 @@ class BubbleOverflow(
init { init {
updateResources() updateResources()
bitmapSize = positioner.bubbleBitmapSize
iconBitmapSize = (bitmapSize * ICON_BITMAP_SIZE_PERCENT).toInt()
expandedView = null expandedView = null
overflowBtn = null overflowBtn = null
} }
@@ -80,10 +71,10 @@ class BubbleOverflow(
} }
fun updateResources() { fun updateResources() {
bitmapSize = positioner.bubbleBitmapSize overflowIconInset = context.resources.getDimensionPixelSize(
iconBitmapSize = (bitmapSize * ICON_BITMAP_SIZE_PERCENT).toInt() R.dimen.bubble_overflow_icon_inset)
val bubbleSize = positioner.bubbleSize overflowBtn?.layoutParams = FrameLayout.LayoutParams(positioner.bubbleSize,
overflowBtn?.layoutParams = FrameLayout.LayoutParams(bubbleSize, bubbleSize) positioner.bubbleSize)
expandedView?.updateDimensions() expandedView?.updateDimensions()
} }
@@ -103,8 +94,7 @@ class BubbleOverflow(
val iconFactory = BubbleIconFactory(context) val iconFactory = BubbleIconFactory(context)
// Update bitmap // Update bitmap
val fg = InsetDrawable(overflowBtn?.drawable, val fg = InsetDrawable(overflowBtn?.drawable, overflowIconInset)
bitmapSize - iconBitmapSize /* inset */)
bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable( bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(
ColorDrawable(colorAccent), fg), ColorDrawable(colorAccent), fg),
null /* user */, true /* shrinkNonAdaptiveIcons */).icon null /* user */, true /* shrinkNonAdaptiveIcons */).icon

View File

@@ -56,20 +56,20 @@ public class BubblePositioner {
public static final int TASKBAR_POSITION_LEFT = 1; public static final int TASKBAR_POSITION_LEFT = 1;
public static final int TASKBAR_POSITION_BOTTOM = 2; public static final int TASKBAR_POSITION_BOTTOM = 2;
/** /** When the bubbles are collapsed in a stack only some of them are shown, this is how many. **/
* The bitmap in the bubble is slightly smaller than the overall size of the bubble. public static final int NUM_VISIBLE_WHEN_RESTING = 2;
* This is the percentage to scale the image down based on the overall bubble size.
*/
private static final float BUBBLE_BITMAP_SIZE_PERCENT = 0.86f;
private Context mContext; private Context mContext;
private WindowManager mWindowManager; private WindowManager mWindowManager;
private Rect mPositionRect; private Rect mPositionRect;
private @Surface.Rotation int mRotation = Surface.ROTATION_0; private @Surface.Rotation int mRotation = Surface.ROTATION_0;
private Insets mInsets; private Insets mInsets;
private int mDefaultMaxBubbles;
private int mMaxBubbles;
private int mBubbleSize; private int mBubbleSize;
private int mBubbleBitmapSize; private int mBubbleBadgeSize;
private int mSpacingBetweenBubbles;
private int mExpandedViewLargeScreenWidth; private int mExpandedViewLargeScreenWidth;
private int mExpandedViewPadding; private int mExpandedViewPadding;
private int mPointerMargin; private int mPointerMargin;
@@ -150,19 +150,47 @@ public class BubblePositioner {
mPositionRect.bottom -= mInsets.bottom; mPositionRect.bottom -= mInsets.bottom;
Resources res = mContext.getResources(); Resources res = mContext.getResources();
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
mBubbleBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_bitmap_size); mBubbleBadgeSize = res.getDimensionPixelSize(R.dimen.bubble_badge_size);
mSpacingBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
mDefaultMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
mExpandedViewLargeScreenWidth = res.getDimensionPixelSize( mExpandedViewLargeScreenWidth = res.getDimensionPixelSize(
R.dimen.bubble_expanded_view_tablet_width); R.dimen.bubble_expanded_view_tablet_width);
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width); mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height); mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin); mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
mMaxBubbles = calculateMaxBubbles();
if (mShowingInTaskbar) { if (mShowingInTaskbar) {
adjustForTaskbar(); adjustForTaskbar();
} }
} }
/**
* @return the maximum number of bubbles that can fit on the screen when expanded. If the
* screen size / screen density is too small to support the default maximum number, then
* the number will be adjust to something lower to ensure everything is presented nicely.
*/
private int calculateMaxBubbles() {
// Use the shortest edge.
// In portrait the bubbles should align with the expanded view so subtract its padding.
// We always show the overflow so subtract one bubble size.
int padding = showBubblesVertically() ? 0 : (mExpandedViewPadding * 2);
int availableSpace = Math.min(mPositionRect.width(), mPositionRect.height())
- padding
- mBubbleSize;
// Each of the bubbles have spacing because the overflow is at the end.
int howManyFit = availableSpace / (mBubbleSize + mSpacingBetweenBubbles);
if (howManyFit < mDefaultMaxBubbles) {
// Not enough space for the default.
return howManyFit;
}
return mDefaultMaxBubbles;
}
/** /**
* Taskbar insets appear as navigationBar insets, however, unlike navigationBar this should * Taskbar insets appear as navigationBar insets, however, unlike navigationBar this should
* not inset bubbles UI as bubbles floats above the taskbar. This adjust the available space * not inset bubbles UI as bubbles floats above the taskbar. This adjust the available space
@@ -225,20 +253,16 @@ public class BubblePositioner {
return isLandscape() || mShowingInTaskbar || mIsLargeScreen; return isLandscape() || mShowingInTaskbar || mIsLargeScreen;
} }
/** Size of the bubble account for badge & dot. */ /** Size of the bubble. */
public int getBubbleSize() { public int getBubbleSize() {
int bsize = (mShowingInTaskbar && mTaskbarIconSize > 0) return (mShowingInTaskbar && mTaskbarIconSize > 0)
? mTaskbarIconSize ? mTaskbarIconSize
: mBubbleSize; : mBubbleSize;
return bsize;
} }
/** Size of the bitmap within the bubble */ /** The maximum number of bubbles that can be displayed comfortably on screen. */
public int getBubbleBitmapSize() { public int getMaxBubbles() {
float size = (mShowingInTaskbar && mTaskbarIconSize > 0) return mMaxBubbles;
? (mTaskbarIconSize * BUBBLE_BITMAP_SIZE_PERCENT)
: mBubbleBitmapSize;
return (int) size;
} }
/** /**

View File

@@ -22,6 +22,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
@@ -242,7 +243,6 @@ public class BubbleStackView extends FrameLayout
@Nullable private RelativeStackPosition mRelativeStackPositionBeforeRotation; @Nullable private RelativeStackPosition mRelativeStackPositionBeforeRotation;
private int mMaxBubbles;
private int mBubbleSize; private int mBubbleSize;
private int mBubbleElevation; private int mBubbleElevation;
private int mBubbleTouchPadding; private int mBubbleTouchPadding;
@@ -632,6 +632,7 @@ public class BubbleStackView extends FrameLayout
// First, see if the magnetized object consumes the event - if so, we shouldn't move the // First, see if the magnetized object consumes the event - if so, we shouldn't move the
// bubble since it's stuck to the target. // bubble since it's stuck to the target.
if (!passEventToMagnetizedObject(ev)) { if (!passEventToMagnetizedObject(ev)) {
updateBubbleShadows(true /* showForAllBubbles */);
if (mBubbleData.isExpanded() || mPositioner.showingInTaskbar()) { if (mBubbleData.isExpanded() || mPositioner.showingInTaskbar()) {
mExpandedAnimationController.dragBubbleOut( mExpandedAnimationController.dragBubbleOut(
v, viewInitialX + dx, viewInitialY + dy); v, viewInitialX + dx, viewInitialY + dy);
@@ -671,7 +672,7 @@ public class BubbleStackView extends FrameLayout
mStackAnimationController.flingStackThenSpringToEdge( mStackAnimationController.flingStackThenSpringToEdge(
viewInitialX + dx, velX, velY) <= 0; viewInitialX + dx, velX, velY) <= 0;
final boolean updateForCollapsedStack = oldOnLeft != mStackOnLeftOrWillBe; final boolean updateForCollapsedStack = oldOnLeft != mStackOnLeftOrWillBe;
updateBadgesAndZOrder(updateForCollapsedStack); updateBadges(updateForCollapsedStack);
logBubbleEvent(null /* no bubble associated with bubble stack move */, logBubbleEvent(null /* no bubble associated with bubble stack move */,
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED); FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
} }
@@ -762,8 +763,7 @@ public class BubbleStackView extends FrameLayout
mBubbleData = data; mBubbleData = data;
Resources res = getResources(); Resources res = getResources();
mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered); mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding); mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset); mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
@@ -783,9 +783,9 @@ public class BubbleStackView extends FrameLayout
mBubbleController.onAllBubblesAnimatedOut(); mBubbleController.onAllBubblesAnimatedOut();
} }
}; };
mStackAnimationController = new StackAnimationController( mStackAnimationController = new StackAnimationController(
floatingContentCoordinator, this::getBubbleCount, onBubbleAnimatedOut, mPositioner); floatingContentCoordinator, this::getBubbleCount, onBubbleAnimatedOut,
this::animateShadows /* onStackAnimationFinished */, mPositioner);
mExpandedAnimationController = new ExpandedAnimationController( mExpandedAnimationController = new ExpandedAnimationController(
mPositioner, mExpandedViewPadding, onBubbleAnimatedOut); mPositioner, mExpandedViewPadding, onBubbleAnimatedOut);
@@ -1505,13 +1505,12 @@ public class BubbleStackView extends FrameLayout
// Set the dot position to the opposite of the side the stack is resting on, since the stack // Set the dot position to the opposite of the side the stack is resting on, since the stack
// resting slightly off-screen would result in the dot also being off-screen. // resting slightly off-screen would result in the dot also being off-screen.
bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */); bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */);
bubble.getIconView().setOnClickListener(mBubbleClickListener); bubble.getIconView().setOnClickListener(mBubbleClickListener);
bubble.getIconView().setOnTouchListener(mBubbleTouchListener); bubble.getIconView().setOnTouchListener(mBubbleTouchListener);
mBubbleContainer.addView(bubble.getIconView(), 0, mBubbleContainer.addView(bubble.getIconView(), 0,
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(), new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
mPositioner.getBubbleSize())); mPositioner.getBubbleSize()));
updateBubbleShadows(false /* showForAllBubbles */);
animateInFlyoutForBubble(bubble); animateInFlyoutForBubble(bubble);
requestUpdate(); requestUpdate();
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__POSTED); logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
@@ -1566,7 +1565,8 @@ public class BubbleStackView extends FrameLayout
}; };
if (mIsExpanded || isExpansionAnimating()) { if (mIsExpanded || isExpansionAnimating()) {
reorder.run(); reorder.run();
updateBadgesAndZOrder(false /* setBadgeForCollapsedStack */); updateBadges(false /* setBadgeForCollapsedStack */);
updateZOrder();
} else if (!isExpansionAnimating()) { } else if (!isExpansionAnimating()) {
List<View> bubbleViews = bubbles.stream() List<View> bubbleViews = bubbles.stream()
.map(b -> b.getIconView()).collect(Collectors.toList()); .map(b -> b.getIconView()).collect(Collectors.toList());
@@ -1803,7 +1803,8 @@ public class BubbleStackView extends FrameLayout
} }
beforeExpandedViewAnimation(); beforeExpandedViewAnimation();
updateBadgesAndZOrder(false /* setBadgeForCollapsedStack */); updateZOrder();
updateBadges(false /* setBadgeForCollapsedStack */);
mBubbleContainer.setActiveController(mExpandedAnimationController); mBubbleContainer.setActiveController(mExpandedAnimationController);
updateOverflowVisibility(); updateOverflowVisibility();
updatePointerPosition(); updatePointerPosition();
@@ -2014,7 +2015,8 @@ public class BubbleStackView extends FrameLayout
mExpandedBubble)); mExpandedBubble));
} }
updateOverflowVisibility(); updateOverflowVisibility();
updateBadgesAndZOrder(true /* setBadgeForCollapsedStack */); updateZOrder();
updateBadges(true /* setBadgeForCollapsedStack */);
afterExpandedViewAnimation(); afterExpandedViewAnimation();
if (previouslySelected != null) { if (previouslySelected != null) {
previouslySelected.setTaskViewVisibility(false); previouslySelected.setTaskViewVisibility(false);
@@ -2707,14 +2709,56 @@ public class BubbleStackView extends FrameLayout
} }
/** /**
* Sets the appropriate Z-order, badge, and dot position for each bubble in the stack. * Updates whether each of the bubbles should show shadows. When collapsed & resting, only the
* Animate dot and badge changes. * visible bubbles (top 2) will show a shadow. When the stack is being dragged, everything
* shows a shadow. When an individual bubble is dragged out, it should show a shadow.
*/ */
private void updateBadgesAndZOrder(boolean setBadgeForCollapsedStack) { private void updateBubbleShadows(boolean showForAllBubbles) {
int bubbleCount = getBubbleCount();
for (int i = 0; i < bubbleCount; i++) {
final float z = (mPositioner.getMaxBubbles() * mBubbleElevation) - i;
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
boolean isDraggedOut = mMagnetizedObject != null
&& mMagnetizedObject.getUnderlyingObject().equals(bv);
if (showForAllBubbles || isDraggedOut) {
bv.setZ(z);
} else {
final float tz = i < NUM_VISIBLE_WHEN_RESTING ? z : 0f;
bv.setZ(tz);
}
}
}
/**
* When the bubbles are flung and then rest, the shadows stack up for the bubbles hidden
* beneath the top two bubbles, to avoid this we animate the Z translations once the stack
* is resting so that they fade away nicely.
*/
private void animateShadows() {
int bubbleCount = getBubbleCount();
for (int i = 0; i < bubbleCount; i++) {
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
boolean fullShadow = i < NUM_VISIBLE_WHEN_RESTING;
if (!fullShadow) {
bv.animate().translationZ(0).start();
}
}
}
private void updateZOrder() {
int bubbleCount = getBubbleCount();
for (int i = 0; i < bubbleCount; i++) {
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
bv.setZ(i < NUM_VISIBLE_WHEN_RESTING
? (mPositioner.getMaxBubbles() * mBubbleElevation) - i
: 0f);
}
}
private void updateBadges(boolean setBadgeForCollapsedStack) {
int bubbleCount = getBubbleCount(); int bubbleCount = getBubbleCount();
for (int i = 0; i < bubbleCount; i++) { for (int i = 0; i < bubbleCount; i++) {
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i); BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
bv.setZ((mMaxBubbles * mBubbleElevation) - i);
if (mIsExpanded) { if (mIsExpanded) {
// If we're not displaying vertically, we always show the badge on the left. // If we're not displaying vertically, we always show the badge on the left.
boolean onLeft = mPositioner.showBubblesVertically() && !mStackOnLeftOrWillBe; boolean onLeft = mPositioner.showBubblesVertically() && !mStackOnLeftOrWillBe;

View File

@@ -99,7 +99,7 @@ class StackEducationView constructor(context: Context) : LinearLayout(context) {
post { post {
with(view) { with(view) {
val bubbleSize = context.resources.getDimensionPixelSize( val bubbleSize = context.resources.getDimensionPixelSize(
R.dimen.individual_bubble_size) R.dimen.bubble_size)
translationY = stackPosition.y + bubbleSize / 2 - getHeight() / 2 translationY = stackPosition.y + bubbleSize / 2 - getHeight() / 2
} }
animate() animate()

View File

@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles.animation; package com.android.wm.shell.bubbles.animation;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.PointF; import android.graphics.PointF;
@@ -209,16 +211,8 @@ public class ExpandedAnimationController
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mBubbleSizePx = mPositioner.getBubbleSize(); mBubbleSizePx = mPositioner.getBubbleSize();
mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered); mBubblesMaxRendered = mPositioner.getMaxBubbles();
mBubblesMaxSpace = res.getDimensionPixelSize(R.dimen.bubble_max_spacing); mSpaceBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
final float availableSpace = mPositioner.isLandscape()
? mPositioner.getAvailableRect().height()
: mPositioner.getAvailableRect().width();
final float spaceForMaxBubbles = (mExpandedViewPadding * 2)
+ (mBubblesMaxRendered + 1) * mBubbleSizePx;
float spaceBetweenBubbles =
(availableSpace - spaceForMaxBubbles) / mBubblesMaxRendered;
mSpaceBetweenBubbles = Math.min(spaceBetweenBubbles, mBubblesMaxSpace);
} }
/** /**
@@ -289,7 +283,8 @@ public class ExpandedAnimationController
path.lineTo(stackedX, expandedY); path.lineTo(stackedX, expandedY);
// Then, draw a line down to the stack position. // Then, draw a line down to the stack position.
path.lineTo(stackedX, mCollapsePoint.y + Math.min(index, 1) * mStackOffsetPx); path.lineTo(stackedX, mCollapsePoint.y
+ Math.min(index, NUM_VISIBLE_WHEN_RESTING - 1) * mStackOffsetPx);
} }
// The lead bubble should be the bubble with the longest distance to travel when we're // The lead bubble should be the bubble with the longest distance to travel when we're

View File

@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles.animation; package com.android.wm.shell.bubbles.animation;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.PointF; import android.graphics.PointF;
@@ -182,9 +184,7 @@ public class StackAnimationController extends
private int mMaxBubbles; private int mMaxBubbles;
/** Default bubble elevation. */ /** Default bubble elevation. */
private int mElevation; private int mElevation;
/** Diameter of the bubble icon. */ /** Diameter of the bubble. */
private int mBubbleBitmapSize;
/** Width of the bubble (icon and padding). */
private int mBubbleSize; private int mBubbleSize;
/** /**
* The amount of space to add between the bubbles and certain UI elements, such as the top of * The amount of space to add between the bubbles and certain UI elements, such as the top of
@@ -257,14 +257,21 @@ public class StackAnimationController extends
*/ */
private Runnable mOnBubbleAnimatedOutAction; private Runnable mOnBubbleAnimatedOutAction;
/**
* Callback to run whenever the stack is finished being flung somewhere.
*/
private Runnable mOnStackAnimationFinished;
public StackAnimationController( public StackAnimationController(
FloatingContentCoordinator floatingContentCoordinator, FloatingContentCoordinator floatingContentCoordinator,
IntSupplier bubbleCountSupplier, IntSupplier bubbleCountSupplier,
Runnable onBubbleAnimatedOutAction, Runnable onBubbleAnimatedOutAction,
Runnable onStackAnimationFinished,
BubblePositioner positioner) { BubblePositioner positioner) {
mFloatingContentCoordinator = floatingContentCoordinator; mFloatingContentCoordinator = floatingContentCoordinator;
mBubbleCountSupplier = bubbleCountSupplier; mBubbleCountSupplier = bubbleCountSupplier;
mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction; mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
mOnStackAnimationFinished = onStackAnimationFinished;
mPositioner = positioner; mPositioner = positioner;
} }
@@ -302,7 +309,7 @@ public class StackAnimationController extends
return true; // Default to left, which is where it starts by default. return true; // Default to left, which is where it starts by default.
} }
float stackCenter = mStackPosition.x + mBubbleBitmapSize / 2; float stackCenter = mStackPosition.x + mBubbleSize / 2;
float screenCenter = mLayout.getWidth() / 2; float screenCenter = mLayout.getWidth() / 2;
return stackCenter < screenCenter; return stackCenter < screenCenter;
} }
@@ -346,7 +353,7 @@ public class StackAnimationController extends
* @return The X value that the stack will end up at after the fling/spring. * @return The X value that the stack will end up at after the fling/spring.
*/ */
public float flingStackThenSpringToEdge(float x, float velX, float velY) { public float flingStackThenSpringToEdge(float x, float velX, float velY) {
final boolean stackOnLeftSide = x - mBubbleBitmapSize / 2 < mLayout.getWidth() / 2; final boolean stackOnLeftSide = x - mBubbleSize / 2 < mLayout.getWidth() / 2;
final boolean stackShouldFlingLeft = stackOnLeftSide final boolean stackShouldFlingLeft = stackOnLeftSide
? velX < ESCAPE_VELOCITY ? velX < ESCAPE_VELOCITY
@@ -674,6 +681,10 @@ public class StackAnimationController extends
mPositioner.setRestingPosition(mStackPosition); mPositioner.setRestingPosition(mStackPosition);
} }
if (mOnStackAnimationFinished != null) {
mOnStackAnimationFinished.run();
}
if (after != null) { if (after != null) {
for (Runnable callback : after) { for (Runnable callback : after) {
callback.run(); callback.run();
@@ -718,7 +729,7 @@ public class StackAnimationController extends
} else { } else {
// We only show the first two bubbles in the stack & the rest hide behind them // We only show the first two bubbles in the stack & the rest hide behind them
// so they don't need an offset. // so they don't need an offset.
return index > 1 ? 0f : mStackOffset; return index > (NUM_VISIBLE_WHEN_RESTING - 1) ? 0f : mStackOffset;
} }
} else { } else {
return 0f; return 0f;
@@ -827,7 +838,8 @@ public class StackAnimationController extends
private void moveToFinalIndex(View view, int newIndex, private void moveToFinalIndex(View view, int newIndex,
Runnable finishReorder) { Runnable finishReorder) {
final ViewPropertyAnimator animator = view.animate() final ViewPropertyAnimator animator = view.animate()
.translationY(getStackPosition().y + Math.min(newIndex, 1) * mStackOffset) .translationY(getStackPosition().y
+ Math.min(newIndex, NUM_VISIBLE_WHEN_RESTING - 1) * mStackOffset)
.setDuration(BUBBLE_SWAP_DURATION) .setDuration(BUBBLE_SWAP_DURATION)
.withEndAction(() -> { .withEndAction(() -> {
view.setTag(R.id.reorder_animator_tag, null); view.setTag(R.id.reorder_animator_tag, null);
@@ -836,8 +848,9 @@ public class StackAnimationController extends
view.setTag(R.id.reorder_animator_tag, animator); view.setTag(R.id.reorder_animator_tag, animator);
} }
// TODO: do we need this & BubbleStackView#updateBadgesAndZOrder?
private void updateBadgesAndZOrder(View v, int index) { private void updateBadgesAndZOrder(View v, int index) {
v.setZ((mMaxBubbles * mElevation) - index); v.setZ(index < NUM_VISIBLE_WHEN_RESTING ? (mMaxBubbles * mElevation) - index : 0f);
BadgedImageView bv = (BadgedImageView) v; BadgedImageView bv = (BadgedImageView) v;
if (index == 0) { if (index == 0) {
bv.showDotAndBadge(!isStackOnLeftSide()); bv.showDotAndBadge(!isStackOnLeftSide());
@@ -857,7 +870,6 @@ public class StackAnimationController extends
mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered); mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
mElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); mElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
mBubbleSize = mPositioner.getBubbleSize(); mBubbleSize = mPositioner.getBubbleSize();
mBubbleBitmapSize = mPositioner.getBubbleBitmapSize();
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen); mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
} }
@@ -942,8 +954,9 @@ public class StackAnimationController extends
final float yOffset = getOffsetForChainedPropertyAnimation( final float yOffset = getOffsetForChainedPropertyAnimation(
DynamicAnimation.TRANSLATION_Y, 0); DynamicAnimation.TRANSLATION_Y, 0);
for (int i = 0; i < mLayout.getChildCount(); i++) { for (int i = 0; i < mLayout.getChildCount(); i++) {
mLayout.getChildAt(i).setTranslationX(pos.x + (Math.min(i, 1) * xOffset)); float index = Math.min(i, NUM_VISIBLE_WHEN_RESTING - 1);
mLayout.getChildAt(i).setTranslationY(pos.y + (Math.min(i, 1) * yOffset)); mLayout.getChildAt(i).setTranslationX(pos.x + (index * xOffset));
mLayout.getChildAt(i).setTranslationY(pos.y + (index * yOffset));
} }
} }
} }

View File

@@ -19,9 +19,12 @@ package com.android.wm.shell.bubbles;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.Truth.assertWithMessage;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.verifyZeroInteractions;
@@ -90,6 +93,7 @@ public class BubbleDataTest extends ShellTestCase {
private Bubble mBubbleDismissed; private Bubble mBubbleDismissed;
private BubbleData mBubbleData; private BubbleData mBubbleData;
private TestableBubblePositioner mPositioner;
@Mock @Mock
private TimeSource mTimeSource; private TimeSource mTimeSource;
@@ -150,9 +154,9 @@ public class BubbleDataTest extends ShellTestCase {
mMainExecutor); mMainExecutor);
mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener, mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener,
mMainExecutor); mMainExecutor);
TestableBubblePositioner positioner = new TestableBubblePositioner(mContext, mPositioner = new TestableBubblePositioner(mContext,
mock(WindowManager.class)); mock(WindowManager.class));
mBubbleData = new BubbleData(getContext(), mBubbleLogger, positioner, mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner,
mMainExecutor); mMainExecutor);
// Used by BubbleData to set lastAccessedTime // Used by BubbleData to set lastAccessedTime
@@ -810,6 +814,61 @@ public class BubbleDataTest extends ShellTestCase {
assertEquals(1, mBubbleData.getOverflowBubbles().size()); assertEquals(1, mBubbleData.getOverflowBubbles().size());
} }
@Test
public void test_onMaxBubblesChanged_notExpanded() {
mBubbleData.setListener(mListener);
mPositioner.setMaxBubbles(5);
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
sendUpdatedEntryAtTime(mEntryA3, 3000);
sendUpdatedEntryAtTime(mEntryB1, 4000);
sendUpdatedEntryAtTime(mEntryB2, 5000);
mBubbleData.setExpanded(false);
reset(mListener);
mPositioner.setMaxBubbles(3);
mBubbleData.onMaxBubblesChanged();
verifyUpdateReceived();
BubbleData.Update update = mUpdateCaptor.getValue();
assertThat(update.removedBubbles.get(0)).isEqualTo(
Pair.create(mBubbleA1, Bubbles.DISMISS_AGED));
assertThat(update.removedBubbles.get(1)).isEqualTo(
Pair.create(mBubbleA2, Bubbles.DISMISS_AGED));
assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA1.getKey()));
assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA2.getKey()));
}
@Test
public void test_onMaxBubblesChanged_expanded() {
mBubbleData.setListener(mListener);
mPositioner.setMaxBubbles(5);
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
sendUpdatedEntryAtTime(mEntryA3, 3000);
sendUpdatedEntryAtTime(mEntryB1, 4000);
sendUpdatedEntryAtTime(mEntryB2, 5000);
mBubbleData.setExpanded(true);
reset(mListener);
mPositioner.setMaxBubbles(3);
mBubbleData.onMaxBubblesChanged();
verify(mListener, never()).applyUpdate(any());
mBubbleData.setExpanded(false);
verifyUpdateReceived();
BubbleData.Update update = mUpdateCaptor.getValue();
assertThat(update.removedBubbles.get(0)).isEqualTo(
Pair.create(mBubbleA1, Bubbles.DISMISS_AGED));
assertThat(update.removedBubbles.get(1)).isEqualTo(
Pair.create(mBubbleA2, Bubbles.DISMISS_AGED));
assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA1.getKey()));
assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA2.getKey()));
}
private void verifyUpdateReceived() { private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture()); verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener); reset(mListener);

View File

@@ -57,7 +57,6 @@ public class BubbleFlyoutViewTest extends ShellTestCase {
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
when(mPositioner.getBubbleBitmapSize()).thenReturn(40);
when(mPositioner.getBubbleSize()).thenReturn(60); when(mPositioner.getBubbleSize()).thenReturn(60);
mFlyoutMessage = new Bubble.FlyoutMessage(); mFlyoutMessage = new Bubble.FlyoutMessage();

View File

@@ -22,7 +22,10 @@ import android.graphics.Insets;
import android.graphics.Rect; import android.graphics.Rect;
import android.view.WindowManager; import android.view.WindowManager;
import com.android.wm.shell.R;
public class TestableBubblePositioner extends BubblePositioner { public class TestableBubblePositioner extends BubblePositioner {
private int mMaxBubbles;
public TestableBubblePositioner(Context context, public TestableBubblePositioner(Context context,
WindowManager windowManager) { WindowManager windowManager) {
@@ -31,5 +34,15 @@ public class TestableBubblePositioner extends BubblePositioner {
updateInternal(Configuration.ORIENTATION_PORTRAIT, updateInternal(Configuration.ORIENTATION_PORTRAIT,
Insets.of(0, 0, 0, 0), Insets.of(0, 0, 0, 0),
new Rect(0, 0, 500, 1000)); new Rect(0, 0, 500, 1000));
mMaxBubbles = context.getResources().getInteger(R.integer.bubbles_max_rendered);
}
public void setMaxBubbles(int max) {
mMaxBubbles = max;
}
@Override
public int getMaxBubbles() {
return mMaxBubbles;
} }
} }

View File

@@ -69,7 +69,7 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase
public int getAsInt() { public int getAsInt() {
return mLayout.getChildCount(); return mLayout.getChildCount();
} }
}, mock(Runnable.class))); }, mock(Runnable.class), mock(Runnable.class)));
mLayout.setActiveController(mStackController); mLayout.setActiveController(mStackController);
addOneMoreThanBubbleLimitBubbles(); addOneMoreThanBubbleLimitBubbles();
mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset); mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset);
@@ -307,10 +307,12 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase
TestableStackController( TestableStackController(
FloatingContentCoordinator floatingContentCoordinator, FloatingContentCoordinator floatingContentCoordinator,
IntSupplier bubbleCountSupplier, IntSupplier bubbleCountSupplier,
Runnable onBubbleAnimatedOutAction) { Runnable onBubbleAnimatedOutAction,
Runnable onStackAnimationFinished) {
super(floatingContentCoordinator, super(floatingContentCoordinator,
bubbleCountSupplier, bubbleCountSupplier,
onBubbleAnimatedOutAction, onBubbleAnimatedOutAction,
onStackAnimationFinished,
new TestableBubblePositioner(mContext, mock(WindowManager.class))); new TestableBubblePositioner(mContext, mock(WindowManager.class)));
} }