diff --git a/packages/SystemUI/res/drawable/bubble_flyout.xml b/packages/SystemUI/res/drawable/bubble_flyout.xml
index 5406aaa65372f..afe5372d38d85 100644
--- a/packages/SystemUI/res/drawable/bubble_flyout.xml
+++ b/packages/SystemUI/res/drawable/bubble_flyout.xml
@@ -14,7 +14,6 @@
~ limitations under the License
-->
-
-
@@ -22,8 +21,10 @@
android:bottomLeftRadius="?android:attr/dialogCornerRadius"
android:topLeftRadius="?android:attr/dialogCornerRadius"
android:bottomRightRadius="?android:attr/dialogCornerRadius"
- android:topRightRadius="?android:attr/dialogCornerRadius"
- />
+ android:topRightRadius="?android:attr/dialogCornerRadius" />
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_flyout.xml b/packages/SystemUI/res/layout/bubble_flyout.xml
index 74c6c123479c2..0e4d2985e7753 100644
--- a/packages/SystemUI/res/layout/bubble_flyout.xml
+++ b/packages/SystemUI/res/layout/bubble_flyout.xml
@@ -15,19 +15,30 @@
-->
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/bubble_flyout_pointer_size"
+ android:paddingRight="@dimen/bubble_flyout_pointer_size">
-
+ android:layout_width="wrap_content"
+ android:background="@drawable/bubble_flyout"
+ android:paddingLeft="@dimen/bubble_flyout_padding_x"
+ android:paddingRight="@dimen/bubble_flyout_padding_x"
+ android:paddingTop="@dimen/bubble_flyout_padding_y"
+ android:paddingBottom="@dimen/bubble_flyout_padding_y"
+ android:translationZ="@dimen/bubble_flyout_elevation">
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index df6bc20bbad81..85a116a91ae67 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1064,16 +1064,20 @@
1dp
4dp
-
- 16dp
-
- 200dp
+
+ 16dp
+
+ 8dp
+
+ 6dp
+
+ 8dp
0dp
8dp
- 56dp
+ 52dp
16dp
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index d9fe47a94d5f0..7029931f72d6b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -22,16 +22,21 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.ShapeDrawable;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.StatsLog;
import android.view.Choreographer;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -56,6 +61,7 @@ import com.android.systemui.R;
import com.android.systemui.bubbles.animation.ExpandedAnimationController;
import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
import com.android.systemui.bubbles.animation.StackAnimationController;
+import com.android.systemui.recents.TriangleShape;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.math.BigDecimal;
@@ -73,6 +79,9 @@ public class BubbleStackView extends FrameLayout {
/** Duration of the flyout alpha animations. */
private static final int FLYOUT_ALPHA_ANIMATION_DURATION = 100;
+ /** Max width of the flyout, in terms of percent of the screen width. */
+ private static final float FLYOUT_MAX_WIDTH_PERCENT = .6f;
+
/** How long to wait, in milliseconds, before hiding the flyout. */
@VisibleForTesting
static final int FLYOUT_HIDE_AFTER = 5000;
@@ -126,13 +135,17 @@ public class BubbleStackView extends FrameLayout {
private FrameLayout mExpandedViewContainer;
- private View mFlyout;
+ private FrameLayout mFlyoutContainer;
+ private FrameLayout mFlyout;
private TextView mFlyoutText;
+ private ShapeDrawable mLeftFlyoutTriangle;
+ private ShapeDrawable mRightFlyoutTriangle;
/** Spring animation for the flyout. */
private SpringAnimation mFlyoutSpring;
/** Runnable that fades out the flyout and then sets it to GONE. */
private Runnable mHideFlyout =
- () -> mFlyout.animate().alpha(0f).withEndAction(() -> mFlyout.setVisibility(GONE));
+ () -> mFlyoutContainer.animate().alpha(0f).withEndAction(
+ () -> mFlyoutContainer.setVisibility(GONE));
/** Layout change listener that moves the stack to the nearest valid position on rotation. */
private OnLayoutChangeListener mMoveStackToValidPositionOnLayoutListener;
@@ -146,6 +159,9 @@ public class BubbleStackView extends FrameLayout {
private int mBubbleSize;
private int mBubblePadding;
+ private int mFlyoutPadding;
+ private int mFlyoutSpaceFromBubble;
+ private int mPointerSize;
private int mExpandedAnimateXDistance;
private int mExpandedAnimateYDistance;
private int mStatusBarHeight;
@@ -218,6 +234,9 @@ public class BubbleStackView extends FrameLayout {
Resources res = getResources();
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
+ mFlyoutSpaceFromBubble = res.getDimensionPixelSize(R.dimen.bubble_flyout_space_from_bubble);
+ mPointerSize = res.getDimensionPixelSize(R.dimen.bubble_flyout_pointer_size);
mExpandedAnimateXDistance =
res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
mExpandedAnimateYDistance =
@@ -244,7 +263,6 @@ public class BubbleStackView extends FrameLayout {
getResources().getInteger(R.integer.bubbles_max_rendered));
mBubbleContainer.setController(mStackAnimationController);
mBubbleContainer.setElevation(elevation);
- mBubbleContainer.setPadding(padding, 0, padding, 0);
mBubbleContainer.setClipChildren(false);
addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
@@ -254,16 +272,17 @@ public class BubbleStackView extends FrameLayout {
mExpandedViewContainer.setClipChildren(false);
addView(mExpandedViewContainer);
- mFlyout = mInflater.inflate(R.layout.bubble_flyout, this, false);
- mFlyout.setVisibility(GONE);
- mFlyout.animate()
+ mFlyoutContainer = (FrameLayout) mInflater.inflate(R.layout.bubble_flyout, this, false);
+ mFlyoutContainer.setVisibility(GONE);
+ mFlyoutContainer.setClipToPadding(false);
+ mFlyoutContainer.setClipChildren(false);
+ mFlyoutContainer.animate()
.setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
.setInterpolator(new AccelerateDecelerateInterpolator());
- addView(mFlyout);
- mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text);
-
- mFlyoutSpring = new SpringAnimation(mFlyout, DynamicAnimation.TRANSLATION_X);
+ mFlyout = mFlyoutContainer.findViewById(R.id.bubble_flyout);
+ addView(mFlyoutContainer);
+ setupFlyout();
mExpandedViewXAnim =
new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
@@ -592,7 +611,7 @@ public class BubbleStackView extends FrameLayout {
}
// Outside parts of view we care about.
return null;
- } else if (isIntersecting(mFlyout, x, y)) {
+ } else if (mFlyoutContainer.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
return mFlyout;
}
@@ -828,27 +847,52 @@ public class BubbleStackView extends FrameLayout {
if (updateMessage != null && !isExpanded() && !mIsExpansionAnimating && !mIsDragging) {
final PointF stackPos = mStackAnimationController.getStackPosition();
- mFlyout.setAlpha(0f);
- mFlyout.setVisibility(VISIBLE);
+ // Set the flyout TextView's max width in terms of percent, and then subtract out the
+ // padding so that the entire flyout view will be the desired width (rather than the
+ // TextView being the desired width + extra padding).
+ mFlyoutText.setMaxWidth(
+ (int) (getWidth() * FLYOUT_MAX_WIDTH_PERCENT) - mFlyoutPadding * 2);
+
+ mFlyoutContainer.setAlpha(0f);
+ mFlyoutContainer.setVisibility(VISIBLE);
mFlyoutText.setText(updateMessage);
- mFlyout.measure(WRAP_CONTENT, WRAP_CONTENT);
- post(() -> {
- final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+
+ final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+
+ if (onLeft) {
+ mLeftFlyoutTriangle.setAlpha(255);
+ mRightFlyoutTriangle.setAlpha(0);
+ } else {
+ mLeftFlyoutTriangle.setAlpha(0);
+ mRightFlyoutTriangle.setAlpha(255);
+ }
+
+ mFlyoutContainer.post(() -> {
+ // Multi line flyouts get top-aligned to the bubble.
+ if (mFlyoutText.getLineCount() > 1) {
+ mFlyoutContainer.setTranslationY(stackPos.y);
+ } else {
+ // Single line flyouts are vertically centered with respect to the bubble.
+ mFlyoutContainer.setTranslationY(
+ stackPos.y + (mBubbleSize - mFlyout.getHeight()) / 2f);
+ }
+
final float destinationX = onLeft
- ? stackPos.x + mBubbleSize + mBubblePadding
- : stackPos.x - mFlyout.getMeasuredWidth();
+ ? stackPos.x + mBubbleSize + mFlyoutSpaceFromBubble
+ : stackPos.x - mFlyoutContainer.getWidth() - mFlyoutSpaceFromBubble;
// Translate towards the stack slightly, then spring out from the stack.
- mFlyout.setTranslationX(destinationX + (onLeft ? -mBubblePadding : mBubblePadding));
- mFlyout.setTranslationY(stackPos.y);
+ mFlyoutContainer.setTranslationX(
+ destinationX + (onLeft ? -mBubblePadding : mBubblePadding));
- mFlyout.animate().alpha(1f);
+ mFlyoutContainer.animate().alpha(1f);
mFlyoutSpring.animateToFinalPosition(destinationX);
mFlyout.removeCallbacks(mHideFlyout);
mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
});
+
logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
}
}
@@ -869,7 +913,7 @@ public class BubbleStackView extends FrameLayout {
mBubbleContainer.getBoundsOnScreen(outRect);
}
- if (mFlyout.getVisibility() == View.VISIBLE) {
+ if (mFlyoutContainer.getVisibility() == View.VISIBLE) {
final Rect flyoutBounds = new Rect();
mFlyout.getBoundsOnScreen(flyoutBounds);
outRect.union(flyoutBounds);
@@ -923,6 +967,74 @@ public class BubbleStackView extends FrameLayout {
}
}
+ /** Sets up the flyout views and drawables. */
+ private void setupFlyout() {
+ // Retrieve the styled floating background color.
+ TypedArray ta = mContext.obtainStyledAttributes(
+ new int[] {android.R.attr.colorBackgroundFloating});
+ final int floatingBackgroundColor = ta.getColor(0, Color.WHITE);
+ ta.recycle();
+
+ // Retrieve the flyout background, which is currently a rounded white rectangle with a
+ // shadow but no triangular arrow pointing anywhere.
+ final LayerDrawable flyoutBackground = (LayerDrawable) mFlyout.getBackground();
+
+ // Create the triangle drawables and set their color.
+ mLeftFlyoutTriangle =
+ new ShapeDrawable(TriangleShape.createHorizontal(
+ mPointerSize, mPointerSize, true /* isPointingLeft */));
+ mRightFlyoutTriangle =
+ new ShapeDrawable(TriangleShape.createHorizontal(
+ mPointerSize, mPointerSize, false /* isPointingLeft */));
+ mLeftFlyoutTriangle.getPaint().setColor(floatingBackgroundColor);
+ mRightFlyoutTriangle.getPaint().setColor(floatingBackgroundColor);
+
+ // Add both triangles to the drawable. We'll show and hide the appropriate ones when we show
+ // the flyout.
+ final int leftTriangleIndex = flyoutBackground.addLayer(mLeftFlyoutTriangle);
+ flyoutBackground.setLayerSize(leftTriangleIndex, mPointerSize, mPointerSize);
+ flyoutBackground.setLayerGravity(leftTriangleIndex, Gravity.LEFT | Gravity.CENTER_VERTICAL);
+ flyoutBackground.setLayerInsetLeft(leftTriangleIndex, -mPointerSize);
+
+ final int rightTriangleIndex = flyoutBackground.addLayer(mRightFlyoutTriangle);
+ flyoutBackground.setLayerSize(rightTriangleIndex, mPointerSize, mPointerSize);
+ flyoutBackground.setLayerGravity(
+ rightTriangleIndex, Gravity.RIGHT | Gravity.CENTER_VERTICAL);
+ flyoutBackground.setLayerInsetRight(rightTriangleIndex, -mPointerSize);
+
+ // Append the appropriate triangle's outline to the view's outline so that the shadows look
+ // correct.
+ mFlyout.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ final boolean leftPointing = mStackAnimationController.isStackOnLeftSide();
+
+ // Get the outline from the appropriate triangle.
+ final Outline triangleOutline = new Outline();
+ if (leftPointing) {
+ mLeftFlyoutTriangle.getOutline(triangleOutline);
+ } else {
+ mRightFlyoutTriangle.getOutline(triangleOutline);
+ }
+
+ // Offset it to the correct position, since it has no intrinsic position since
+ // that is maintained by the parent LayerDrawable.
+ triangleOutline.offset(
+ leftPointing ? -mPointerSize : mFlyout.getWidth(),
+ mFlyout.getHeight() / 2 - mPointerSize / 2);
+
+ // Merge the outlines.
+ final Outline compoundOutline = new Outline();
+ flyoutBackground.getOutline(compoundOutline);
+ compoundOutline.mPath.addPath(triangleOutline.mPath);
+ outline.set(compoundOutline);
+ }
+ });
+
+ mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text);
+ mFlyoutSpring = new SpringAnimation(mFlyoutContainer, DynamicAnimation.TRANSLATION_X);
+ }
+
private void applyCurrentState() {
Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index eb6ac796612a0..47f2cd40b5e11 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -120,7 +120,11 @@ public class StackAnimationController extends
private float mStackOffset;
/** Diameter of the bubbles themselves. */
private int mIndividualBubbleSize;
- /** Size of spacing around the bubbles, separating it from the edge of the screen. */
+ /**
+ * The amount of space to add between the bubbles and certain UI elements, such as the top of
+ * the screen or the IME. This does not apply to the left/right sides of the screen since the
+ * stack goes offscreen intentionally.
+ */
private int mBubblePadding;
/** How far offscreen the stack rests. */
private int mBubbleOffscreen;
@@ -381,7 +385,6 @@ public class StackAnimationController extends
if (insets != null) {
allowableRegion.left =
-mBubbleOffscreen
- - mBubblePadding
+ Math.max(
insets.getSystemWindowInsetLeft(),
insets.getDisplayCutout() != null
@@ -391,7 +394,6 @@ public class StackAnimationController extends
mLayout.getWidth()
- mIndividualBubbleSize
+ mBubbleOffscreen
- - mBubblePadding
- Math.max(
insets.getSystemWindowInsetRight(),
insets.getDisplayCutout() != null
@@ -521,7 +523,6 @@ public class StackAnimationController extends
if (mLayout.getChildCount() > 0) {
property.setValue(mLayout.getChildAt(0), value);
-
if (mLayout.getChildCount() > 1) {
animationForChildAtIndex(1)
.property(property, value + getOffsetForChainedPropertyAnimation(property))