Merge "a11y local actions - fling bubble stack to corners" into qt-dev
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index f75f255..e97055f0 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -103,6 +103,12 @@
     <item type="id" name="action_snooze_assistant_suggestion_1"/>
     <item type="id" name="action_snooze"/>
 
+    <!-- Accessibility actions for bubbles. -->
+    <item type="id" name="action_move_top_left"/>
+    <item type="id" name="action_move_top_right"/>
+    <item type="id" name="action_move_bottom_left"/>
+    <item type="id" name="action_move_bottom_right"/>
+
     <!-- For StatusIconContainer to tag its icon views -->
     <item type="id" name="status_bar_view_state_tag" />
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index d1bc9a9..123d73d 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.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 @@
     @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 @@
         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 47f2cd4..bc249ae 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 @@
 
     /** 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 @@
         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;