diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index f75f255324a63..e97055f08e32d 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -103,6 +103,12 @@ + + + + + + diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index d1bc9a91636c9..123d73dc6432b 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -45,6 +45,7 @@ import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.FrameLayout; import android.widget.TextView; @@ -391,11 +392,34 @@ public class BubbleStackView extends FrameLayout { @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); + + // Custom actions. + AccessibilityAction moveTopLeft = new AccessibilityAction(R.id.action_move_top_left, + getContext().getResources() + .getString(R.string.bubble_accessibility_action_move_top_left)); + info.addAction(moveTopLeft); + + AccessibilityAction moveTopRight = new AccessibilityAction(R.id.action_move_top_right, + getContext().getResources() + .getString(R.string.bubble_accessibility_action_move_top_right)); + info.addAction(moveTopRight); + + AccessibilityAction moveBottomLeft = new AccessibilityAction(R.id.action_move_bottom_left, + getContext().getResources() + .getString(R.string.bubble_accessibility_action_move_bottom_left)); + info.addAction(moveBottomLeft); + + AccessibilityAction moveBottomRight = new AccessibilityAction(R.id.action_move_bottom_right, + getContext().getResources() + .getString(R.string.bubble_accessibility_action_move_bottom_right)); + info.addAction(moveBottomRight); + + // Default actions. + info.addAction(AccessibilityAction.ACTION_DISMISS); if (mIsExpanded) { - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE); + info.addAction(AccessibilityAction.ACTION_COLLAPSE); } else { - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); + info.addAction(AccessibilityAction.ACTION_EXPAND); } } @@ -404,16 +428,30 @@ public class BubbleStackView extends FrameLayout { if (super.performAccessibilityActionInternal(action, arguments)) { return true; } - switch (action) { - case AccessibilityNodeInfo.ACTION_DISMISS: - mBubbleData.dismissAll(BubbleController.DISMISS_ACCESSIBILITY_ACTION); - return true; - case AccessibilityNodeInfo.ACTION_COLLAPSE: - mBubbleData.setExpanded(false); - return true; - case AccessibilityNodeInfo.ACTION_EXPAND: - mBubbleData.setExpanded(true); - return true; + final RectF stackBounds = mStackAnimationController.getAllowableStackPositionRegion(); + + // R constants are not final so we cannot use switch-case here. + if (action == AccessibilityNodeInfo.ACTION_DISMISS) { + mBubbleData.dismissAll(BubbleController.DISMISS_ACCESSIBILITY_ACTION); + return true; + } else if (action == AccessibilityNodeInfo.ACTION_COLLAPSE) { + mBubbleData.setExpanded(false); + return true; + } else if (action == AccessibilityNodeInfo.ACTION_EXPAND) { + mBubbleData.setExpanded(true); + return true; + } else if (action == R.id.action_move_top_left) { + mStackAnimationController.springStack(stackBounds.left, stackBounds.top); + return true; + } else if (action == R.id.action_move_top_right) { + mStackAnimationController.springStack(stackBounds.right, stackBounds.top); + return true; + } else if (action == R.id.action_move_bottom_left) { + mStackAnimationController.springStack(stackBounds.left, stackBounds.bottom); + return true; + } else if (action == R.id.action_move_bottom_right) { + mStackAnimationController.springStack(stackBounds.right, stackBounds.bottom); + return true; } return false; } 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 47f2cd40b5e11..bc249aedc6052 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -175,11 +175,33 @@ public class StackAnimationController extends /** Whether the stack is on the left side of the screen. */ public boolean isStackOnLeftSide() { - if (mLayout != null) { - return mStackPosition.x - mIndividualBubbleSize / 2 < mLayout.getWidth() / 2; - } else { + if (mLayout == null) { return false; } + float stackCenter = mStackPosition.x + mIndividualBubbleSize / 2; + float screenCenter = mLayout.getWidth() / 2; + return stackCenter < screenCenter; + } + + /** + * Fling stack to given corner, within allowable screen bounds. + * Note that we need new SpringForce instances per animation despite identical configs because + * SpringAnimation uses SpringForce's internal (changing) velocity while the animation runs. + */ + public void springStack(float destinationX, float destinationY) { + springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X, + new SpringForce() + .setStiffness(SPRING_AFTER_FLING_STIFFNESS) + .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO), + 0 /* startXVelocity */, + destinationX); + + springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y, + new SpringForce() + .setStiffness(SPRING_AFTER_FLING_STIFFNESS) + .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO), + 0 /* startYVelocity */, + destinationY); } /** @@ -352,6 +374,7 @@ public class StackAnimationController extends float destinationY = Float.MIN_VALUE; if (imeVisible) { + // Stack is lower than it should be and overlaps the now-visible IME. if (mStackPosition.y > maxBubbleY && mPreImeY == Float.MIN_VALUE) { mPreImeY = mStackPosition.y; destinationY = maxBubbleY;