Joshua Tsuji | b1a796b | 2019-01-16 15:43:12 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2019 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.systemui.bubbles.animation; |
| 18 | |
| 19 | import static org.junit.Assert.assertEquals; |
| 20 | |
| 21 | import android.graphics.PointF; |
| 22 | import android.support.test.filters.SmallTest; |
| 23 | import android.testing.AndroidTestingRunner; |
| 24 | import android.view.View; |
| 25 | import android.widget.FrameLayout; |
| 26 | |
| 27 | import androidx.dynamicanimation.animation.DynamicAnimation; |
| 28 | import androidx.dynamicanimation.animation.SpringForce; |
| 29 | |
| 30 | import com.android.systemui.R; |
| 31 | |
| 32 | import org.junit.Before; |
Joshua Tsuji | 87ebd74 | 2019-01-25 16:01:26 -0500 | [diff] [blame] | 33 | import org.junit.Ignore; |
Joshua Tsuji | b1a796b | 2019-01-16 15:43:12 -0800 | [diff] [blame] | 34 | import org.junit.Test; |
| 35 | import org.junit.runner.RunWith; |
| 36 | import org.mockito.Spy; |
| 37 | |
| 38 | @SmallTest |
| 39 | @RunWith(AndroidTestingRunner.class) |
| 40 | public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase { |
| 41 | |
| 42 | @Spy |
| 43 | private TestableStackController mStackController = new TestableStackController(); |
| 44 | |
| 45 | private int mStackOffset; |
| 46 | |
| 47 | @Before |
| 48 | public void setUp() throws Exception { |
| 49 | super.setUp(); |
| 50 | addOneMoreThanRenderLimitBubbles(); |
| 51 | mLayout.setController(mStackController); |
| 52 | mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset); |
| 53 | } |
| 54 | |
| 55 | /** |
| 56 | * Test moving around the stack, and make sure the position is updated correctly, and the stack |
| 57 | * direction is correct. |
| 58 | */ |
| 59 | @Test |
| 60 | public void testMoveFirstBubbleWithStackFollowing() throws InterruptedException { |
| 61 | mStackController.moveFirstBubbleWithStackFollowing(200, 100); |
| 62 | |
| 63 | // The first bubble should have moved instantly, the rest should be waiting for animation. |
| 64 | assertEquals(200, mViews.get(0).getTranslationX(), .1f); |
| 65 | assertEquals(100, mViews.get(0).getTranslationY(), .1f); |
| 66 | assertEquals(0, mViews.get(1).getTranslationX(), .1f); |
| 67 | assertEquals(0, mViews.get(1).getTranslationY(), .1f); |
| 68 | |
| 69 | waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); |
| 70 | |
| 71 | // Make sure the rest of the stack got moved to the right place and is stacked to the left. |
| 72 | testStackedAtPosition(200, 100, -1); |
| 73 | assertEquals(new PointF(200, 100), mStackController.getStackPosition()); |
| 74 | |
| 75 | mStackController.moveFirstBubbleWithStackFollowing(1000, 500); |
| 76 | |
| 77 | // The first bubble again should have moved instantly while the rest remained where they |
| 78 | // were until the animation takes over. |
| 79 | assertEquals(1000, mViews.get(0).getTranslationX(), .1f); |
| 80 | assertEquals(500, mViews.get(0).getTranslationY(), .1f); |
| 81 | assertEquals(200 + -mStackOffset, mViews.get(1).getTranslationX(), .1f); |
| 82 | assertEquals(100, mViews.get(1).getTranslationY(), .1f); |
| 83 | |
| 84 | waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); |
| 85 | |
| 86 | // Make sure the rest of the stack moved again, including the first bubble not moving, and |
| 87 | // is stacked to the right now that we're on the right side of the screen. |
| 88 | testStackedAtPosition(1000, 500, 1); |
| 89 | assertEquals(new PointF(1000, 500), mStackController.getStackPosition()); |
| 90 | } |
| 91 | |
| 92 | @Test |
Joshua Tsuji | 87ebd74 | 2019-01-25 16:01:26 -0500 | [diff] [blame] | 93 | @Ignore("Sporadically failing due to DynamicAnimation not settling.") |
Joshua Tsuji | b1a796b | 2019-01-16 15:43:12 -0800 | [diff] [blame] | 94 | public void testFlingSideways() throws InterruptedException { |
| 95 | // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much |
| 96 | // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top |
| 97 | // but should bounce back down. |
| 98 | mStackController.flingThenSpringFirstBubbleWithStackFollowing( |
| 99 | DynamicAnimation.TRANSLATION_X, |
| 100 | 5000f, 1.15f, new SpringForce(), mWidth * 1f); |
| 101 | mStackController.flingThenSpringFirstBubbleWithStackFollowing( |
| 102 | DynamicAnimation.TRANSLATION_Y, |
| 103 | 0f, 1.15f, new SpringForce(), 0f); |
| 104 | |
| 105 | // Nothing should move initially since the animations haven't begun, including the first |
| 106 | // view. |
| 107 | assertEquals(0f, mViews.get(0).getTranslationX(), 1f); |
| 108 | assertEquals(0f, mViews.get(0).getTranslationY(), 1f); |
| 109 | |
| 110 | // Wait for the flinging. |
| 111 | waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, |
| 112 | DynamicAnimation.TRANSLATION_Y); |
| 113 | |
| 114 | // Wait for the springing. |
| 115 | waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, |
| 116 | DynamicAnimation.TRANSLATION_Y); |
| 117 | |
| 118 | // Once the dust has settled, we should have flung all the way to the right side, with the |
| 119 | // stack stacked off to the right now. |
| 120 | testStackedAtPosition(mWidth * 1f, 0f, 1); |
| 121 | } |
| 122 | |
| 123 | @Test |
Joshua Tsuji | 87ebd74 | 2019-01-25 16:01:26 -0500 | [diff] [blame] | 124 | @Ignore("Sporadically failing due to DynamicAnimation not settling.") |
Joshua Tsuji | b1a796b | 2019-01-16 15:43:12 -0800 | [diff] [blame] | 125 | public void testFlingUpFromBelowBottomCenter() throws InterruptedException { |
| 126 | // Move to the center of the screen, just past the bottom. |
| 127 | mStackController.moveFirstBubbleWithStackFollowing(mWidth / 2f, mHeight + 100); |
| 128 | waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); |
| 129 | |
| 130 | // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much |
| 131 | // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top |
| 132 | // but should bounce back down. |
| 133 | mStackController.flingThenSpringFirstBubbleWithStackFollowing( |
| 134 | DynamicAnimation.TRANSLATION_X, |
| 135 | 0, 1.15f, new SpringForce(), 27f); |
| 136 | mStackController.flingThenSpringFirstBubbleWithStackFollowing( |
| 137 | DynamicAnimation.TRANSLATION_Y, |
| 138 | 5000f, 1.15f, new SpringForce(), 27f); |
| 139 | |
| 140 | // Nothing should move initially since the animations haven't begun. |
| 141 | assertEquals(mWidth / 2f, mViews.get(0).getTranslationX(), .1f); |
| 142 | assertEquals(mHeight + 100, mViews.get(0).getTranslationY(), .1f); |
| 143 | |
| 144 | waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, |
| 145 | DynamicAnimation.TRANSLATION_Y); |
| 146 | |
| 147 | // Once the dust has settled, we should have flung a bit but then sprung to the final |
| 148 | // destination which is (27, 27). |
| 149 | testStackedAtPosition(27, 27, -1); |
| 150 | } |
| 151 | |
| 152 | @Test |
| 153 | public void testChildAdded() throws InterruptedException { |
| 154 | // Move the stack to y = 500. |
| 155 | mStackController.moveFirstBubbleWithStackFollowing(0f, 500f); |
| 156 | waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, |
| 157 | DynamicAnimation.TRANSLATION_Y); |
| 158 | |
| 159 | final View newView = new FrameLayout(mContext); |
| 160 | mLayout.addView( |
| 161 | newView, |
| 162 | 0, |
| 163 | new FrameLayout.LayoutParams(50, 50)); |
| 164 | |
| 165 | waitForPropertyAnimations( |
| 166 | DynamicAnimation.TRANSLATION_X, |
| 167 | DynamicAnimation.TRANSLATION_Y, |
| 168 | DynamicAnimation.SCALE_X, |
| 169 | DynamicAnimation.SCALE_Y); |
| 170 | |
| 171 | // The new view should be at the top of the stack, in the correct position. |
| 172 | assertEquals(0f, newView.getTranslationX(), .1f); |
| 173 | assertEquals(500f, newView.getTranslationY(), .1f); |
| 174 | assertEquals(1f, newView.getScaleX(), .1f); |
| 175 | assertEquals(1f, newView.getScaleY(), .1f); |
| 176 | assertEquals(1f, newView.getAlpha(), .1f); |
| 177 | } |
| 178 | |
| 179 | @Test |
| 180 | public void testChildRemoved() throws InterruptedException { |
Joshua Tsuji | a08b6d3 | 2019-01-29 16:15:52 -0500 | [diff] [blame] | 181 | assertEquals(0, mLayout.getTransientViewCount()); |
| 182 | |
Joshua Tsuji | b1a796b | 2019-01-16 15:43:12 -0800 | [diff] [blame] | 183 | final View firstView = mLayout.getChildAt(0); |
| 184 | mLayout.removeView(firstView); |
| 185 | |
Joshua Tsuji | a08b6d3 | 2019-01-29 16:15:52 -0500 | [diff] [blame] | 186 | // The view should now be transient, and missing from the view's normal hierarchy. |
| 187 | assertEquals(1, mLayout.getTransientViewCount()); |
| 188 | assertEquals(-1, mLayout.indexOfChild(firstView)); |
Joshua Tsuji | b1a796b | 2019-01-16 15:43:12 -0800 | [diff] [blame] | 189 | |
| 190 | waitForPropertyAnimations(DynamicAnimation.ALPHA); |
| 191 | waitForLayoutMessageQueue(); |
| 192 | |
Joshua Tsuji | a08b6d3 | 2019-01-29 16:15:52 -0500 | [diff] [blame] | 193 | // The view should now be gone entirely, no transient views left. |
| 194 | assertEquals(0, mLayout.getTransientViewCount()); |
Joshua Tsuji | b1a796b | 2019-01-16 15:43:12 -0800 | [diff] [blame] | 195 | |
| 196 | // The subsequent view should have been translated over to 0, not stacked off to the left. |
| 197 | assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); |
| 198 | } |
| 199 | |
| 200 | /** |
| 201 | * Checks every child view to make sure it's stacked at the given coordinates, off to the left |
| 202 | * or right side depending on offset multiplier. |
| 203 | */ |
| 204 | private void testStackedAtPosition(float x, float y, int offsetMultiplier) { |
| 205 | // Make sure the rest of the stack moved again, including the first bubble not moving, and |
| 206 | // is stacked to the right now that we're on the right side of the screen. |
| 207 | for (int i = 0; i < mLayout.getChildCount(); i++) { |
| 208 | assertEquals(x + i * offsetMultiplier * mStackOffset, |
| 209 | mViews.get(i).getTranslationX(), 2f); |
| 210 | assertEquals(y, mViews.get(i).getTranslationY(), 2f); |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | /** |
| 215 | * Testable version of the stack controller that dispatches its animations on the main thread. |
| 216 | */ |
| 217 | private class TestableStackController extends StackAnimationController { |
| 218 | @Override |
| 219 | public void flingThenSpringFirstBubbleWithStackFollowing( |
| 220 | DynamicAnimation.ViewProperty property, float vel, float friction, |
| 221 | SpringForce spring, Float finalPosition) { |
| 222 | mMainThreadHandler.post(() -> |
| 223 | super.flingThenSpringFirstBubbleWithStackFollowing( |
| 224 | property, vel, friction, spring, finalPosition)); |
| 225 | } |
| 226 | } |
| 227 | } |