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