blob: d79128ca5c78886761f0813d2de944394cf583e3 [file] [log] [blame]
Joshua Tsujib1a796b2019-01-16 15:43:12 -08001/*
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
17package com.android.systemui.bubbles.animation;
18
19import static org.junit.Assert.assertEquals;
Joshua Tsuji4accf5982019-04-22 17:36:11 -040020import static org.junit.Assert.assertNotEquals;
21import static org.mockito.Mockito.verify;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080022
23import android.graphics.PointF;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080024import android.testing.AndroidTestingRunner;
25import android.view.View;
26import android.widget.FrameLayout;
27
28import androidx.dynamicanimation.animation.DynamicAnimation;
29import androidx.dynamicanimation.animation.SpringForce;
Brett Chabot84151d92019-02-27 15:37:59 -080030import androidx.test.filters.SmallTest;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080031
32import com.android.systemui.R;
33
34import org.junit.Before;
Joshua Tsuji87ebd742019-01-25 16:01:26 -050035import org.junit.Ignore;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080036import org.junit.Test;
37import org.junit.runner.RunWith;
Joshua Tsuji4accf5982019-04-22 17:36:11 -040038import org.mockito.Mockito;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080039import org.mockito.Spy;
40
Joshua Tsuji33c0e9c2019-05-14 16:45:39 -040041import java.util.concurrent.CountDownLatch;
42import java.util.concurrent.TimeUnit;
43
Joshua Tsujib1a796b2019-01-16 15:43:12 -080044@SmallTest
45@RunWith(AndroidTestingRunner.class)
46public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
47
48 @Spy
49 private TestableStackController mStackController = new TestableStackController();
50
51 private int mStackOffset;
Joshua Tsuji33c0e9c2019-05-14 16:45:39 -040052 private Runnable mCheckStartPosSet;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080053
54 @Before
55 public void setUp() throws Exception {
56 super.setUp();
Joshua Tsujic36ee6f2019-05-28 17:00:16 -040057 mLayout.setActiveController(mStackController);
Joshua Tsujicb97a112019-05-29 16:20:41 -040058 addOneMoreThanBubbleLimitBubbles();
Joshua Tsujib1a796b2019-01-16 15:43:12 -080059 mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset);
60 }
61
62 /**
63 * Test moving around the stack, and make sure the position is updated correctly, and the stack
64 * direction is correct.
65 */
66 @Test
Joshua Tsujie567f072019-04-03 12:37:26 -040067 @Ignore("Flaking")
Joshua Tsujib1a796b2019-01-16 15:43:12 -080068 public void testMoveFirstBubbleWithStackFollowing() throws InterruptedException {
69 mStackController.moveFirstBubbleWithStackFollowing(200, 100);
70
71 // The first bubble should have moved instantly, the rest should be waiting for animation.
72 assertEquals(200, mViews.get(0).getTranslationX(), .1f);
73 assertEquals(100, mViews.get(0).getTranslationY(), .1f);
74 assertEquals(0, mViews.get(1).getTranslationX(), .1f);
75 assertEquals(0, mViews.get(1).getTranslationY(), .1f);
76
77 waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
78
79 // Make sure the rest of the stack got moved to the right place and is stacked to the left.
80 testStackedAtPosition(200, 100, -1);
81 assertEquals(new PointF(200, 100), mStackController.getStackPosition());
82
83 mStackController.moveFirstBubbleWithStackFollowing(1000, 500);
84
85 // The first bubble again should have moved instantly while the rest remained where they
86 // were until the animation takes over.
87 assertEquals(1000, mViews.get(0).getTranslationX(), .1f);
88 assertEquals(500, mViews.get(0).getTranslationY(), .1f);
89 assertEquals(200 + -mStackOffset, mViews.get(1).getTranslationX(), .1f);
90 assertEquals(100, mViews.get(1).getTranslationY(), .1f);
91
92 waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
93
94 // Make sure the rest of the stack moved again, including the first bubble not moving, and
95 // is stacked to the right now that we're on the right side of the screen.
96 testStackedAtPosition(1000, 500, 1);
97 assertEquals(new PointF(1000, 500), mStackController.getStackPosition());
98 }
99
100 @Test
Joshua Tsuji87ebd742019-01-25 16:01:26 -0500101 @Ignore("Sporadically failing due to DynamicAnimation not settling.")
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800102 public void testFlingSideways() throws InterruptedException {
103 // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much
104 // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top
105 // but should bounce back down.
106 mStackController.flingThenSpringFirstBubbleWithStackFollowing(
107 DynamicAnimation.TRANSLATION_X,
108 5000f, 1.15f, new SpringForce(), mWidth * 1f);
109 mStackController.flingThenSpringFirstBubbleWithStackFollowing(
110 DynamicAnimation.TRANSLATION_Y,
111 0f, 1.15f, new SpringForce(), 0f);
112
113 // Nothing should move initially since the animations haven't begun, including the first
114 // view.
115 assertEquals(0f, mViews.get(0).getTranslationX(), 1f);
116 assertEquals(0f, mViews.get(0).getTranslationY(), 1f);
117
118 // Wait for the flinging.
119 waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
120 DynamicAnimation.TRANSLATION_Y);
121
122 // Wait for the springing.
123 waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
124 DynamicAnimation.TRANSLATION_Y);
125
126 // Once the dust has settled, we should have flung all the way to the right side, with the
127 // stack stacked off to the right now.
128 testStackedAtPosition(mWidth * 1f, 0f, 1);
129 }
130
131 @Test
Joshua Tsuji87ebd742019-01-25 16:01:26 -0500132 @Ignore("Sporadically failing due to DynamicAnimation not settling.")
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800133 public void testFlingUpFromBelowBottomCenter() throws InterruptedException {
134 // Move to the center of the screen, just past the bottom.
135 mStackController.moveFirstBubbleWithStackFollowing(mWidth / 2f, mHeight + 100);
136 waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
137
138 // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much
139 // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top
140 // but should bounce back down.
141 mStackController.flingThenSpringFirstBubbleWithStackFollowing(
142 DynamicAnimation.TRANSLATION_X,
143 0, 1.15f, new SpringForce(), 27f);
144 mStackController.flingThenSpringFirstBubbleWithStackFollowing(
145 DynamicAnimation.TRANSLATION_Y,
146 5000f, 1.15f, new SpringForce(), 27f);
147
148 // Nothing should move initially since the animations haven't begun.
149 assertEquals(mWidth / 2f, mViews.get(0).getTranslationX(), .1f);
150 assertEquals(mHeight + 100, mViews.get(0).getTranslationY(), .1f);
151
152 waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
153 DynamicAnimation.TRANSLATION_Y);
154
155 // Once the dust has settled, we should have flung a bit but then sprung to the final
156 // destination which is (27, 27).
157 testStackedAtPosition(27, 27, -1);
158 }
159
160 @Test
Joshua Tsujib8a67172019-05-18 23:44:17 -0400161 @Ignore("Flaking")
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800162 public void testChildAdded() throws InterruptedException {
163 // Move the stack to y = 500.
164 mStackController.moveFirstBubbleWithStackFollowing(0f, 500f);
165 waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
166 DynamicAnimation.TRANSLATION_Y);
167
168 final View newView = new FrameLayout(mContext);
169 mLayout.addView(
170 newView,
171 0,
172 new FrameLayout.LayoutParams(50, 50));
173
Joshua Tsuji33c0e9c2019-05-14 16:45:39 -0400174 waitForStartPosToBeSet();
175 waitForLayoutMessageQueue();
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800176 waitForPropertyAnimations(
177 DynamicAnimation.TRANSLATION_X,
178 DynamicAnimation.TRANSLATION_Y,
179 DynamicAnimation.SCALE_X,
180 DynamicAnimation.SCALE_Y);
181
182 // The new view should be at the top of the stack, in the correct position.
183 assertEquals(0f, newView.getTranslationX(), .1f);
184 assertEquals(500f, newView.getTranslationY(), .1f);
185 assertEquals(1f, newView.getScaleX(), .1f);
186 assertEquals(1f, newView.getScaleY(), .1f);
187 assertEquals(1f, newView.getAlpha(), .1f);
188 }
189
190 @Test
Joshua Tsujied386f22019-03-06 23:59:22 -0500191 @Ignore("Occasionally flakes, ignoring pending investigation.")
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800192 public void testChildRemoved() throws InterruptedException {
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500193 assertEquals(0, mLayout.getTransientViewCount());
194
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800195 final View firstView = mLayout.getChildAt(0);
196 mLayout.removeView(firstView);
197
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500198 // The view should now be transient, and missing from the view's normal hierarchy.
199 assertEquals(1, mLayout.getTransientViewCount());
200 assertEquals(-1, mLayout.indexOfChild(firstView));
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800201
202 waitForPropertyAnimations(DynamicAnimation.ALPHA);
203 waitForLayoutMessageQueue();
204
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500205 // The view should now be gone entirely, no transient views left.
206 assertEquals(0, mLayout.getTransientViewCount());
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800207
208 // The subsequent view should have been translated over to 0, not stacked off to the left.
209 assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f);
210 }
211
Joshua Tsujie13b4fc2019-02-28 18:39:57 -0500212 @Test
Joshua Tsuji92e7f782019-04-02 11:56:21 -0400213 @Ignore("Flaky")
Joshua Tsujie13b4fc2019-02-28 18:39:57 -0500214 public void testRestoredAtRestingPosition() throws InterruptedException {
215 mStackController.flingStackThenSpringToEdge(0, 5000, 5000);
216
217 waitForPropertyAnimations(
218 DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
219 waitForLayoutMessageQueue();
220
221 final PointF prevStackPos = mStackController.getStackPosition();
222
223 mLayout.removeAllViews();
Joshua Tsujic1108432019-02-22 16:10:12 -0500224
225 waitForLayoutMessageQueue();
226
Joshua Tsujie13b4fc2019-02-28 18:39:57 -0500227 mLayout.addView(new FrameLayout(getContext()));
228
229 waitForLayoutMessageQueue();
230 waitForPropertyAnimations(
231 DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
232
233 assertEquals(prevStackPos, mStackController.getStackPosition());
234 }
235
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400236 @Test
Joshua Tsuji16a4abf2019-05-22 13:03:58 -0400237 @Ignore("Flaky")
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400238 public void testMagnetToDismiss_dismiss() throws InterruptedException {
239 final Runnable after = Mockito.mock(Runnable.class);
240
241 // Magnet to dismiss, verify the stack is at the dismiss target and the callback was
242 // called.
243 mStackController.magnetToDismiss(100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
244 waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
245 verify(after).run();
246 assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
247
248 // Dismiss the stack, verify that the callback was called.
249 final Runnable afterImplode = Mockito.mock(Runnable.class);
250 mStackController.implodeStack(afterImplode);
251 waitForPropertyAnimations(
252 DynamicAnimation.ALPHA, DynamicAnimation.SCALE_X, DynamicAnimation.SCALE_Y);
253 verify(after).run();
254 }
255
256 @Test
Joshua Tsujib8a67172019-05-18 23:44:17 -0400257 @Ignore("Flaking")
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400258 public void testMagnetToDismiss_demagnetizeThenDrag() throws InterruptedException {
259 final Runnable after = Mockito.mock(Runnable.class);
260
261 // Magnet to dismiss, verify the stack is at the dismiss target and the callback was
262 // called.
263 mStackController.magnetToDismiss(100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
264 waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
265 verify(after).run();
266
267 assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
268
269 // Demagnetize towards (25, 25) and then send a touch event.
270 mStackController.demagnetizeFromDismissToPoint(25, 25, 0, 0);
271 waitForLayoutMessageQueue();
272 mStackController.moveStackFromTouch(20, 20);
273
274 // Since the stack is demagnetizing, it shouldn't be at the stack position yet.
275 assertNotEquals(20, mStackController.getStackPosition().x, 1f);
276 assertNotEquals(20, mStackController.getStackPosition().y, 1f);
277
278 waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
279
280 // Once the animation is done it should end at the touch position coordinates.
281 assertEquals(20, mStackController.getStackPosition().x, 1f);
282 assertEquals(20, mStackController.getStackPosition().y, 1f);
283
284 mStackController.moveStackFromTouch(30, 30);
285
286 // Touches after the animation are done should change the stack position instantly.
287 assertEquals(30, mStackController.getStackPosition().x, 1f);
288 assertEquals(30, mStackController.getStackPosition().y, 1f);
289 }
290
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800291 /**
292 * Checks every child view to make sure it's stacked at the given coordinates, off to the left
293 * or right side depending on offset multiplier.
294 */
295 private void testStackedAtPosition(float x, float y, int offsetMultiplier) {
296 // Make sure the rest of the stack moved again, including the first bubble not moving, and
297 // is stacked to the right now that we're on the right side of the screen.
298 for (int i = 0; i < mLayout.getChildCount(); i++) {
299 assertEquals(x + i * offsetMultiplier * mStackOffset,
300 mViews.get(i).getTranslationX(), 2f);
301 assertEquals(y, mViews.get(i).getTranslationY(), 2f);
302 }
303 }
304
Joshua Tsuji33c0e9c2019-05-14 16:45:39 -0400305 /** Waits up to 2 seconds for the initial stack position to be initialized. */
306 private void waitForStartPosToBeSet() throws InterruptedException {
307 final CountDownLatch animLatch = new CountDownLatch(1);
308
309 mCheckStartPosSet = () -> {
310 if (mStackController.getStackPosition().x >= 0) {
311 animLatch.countDown();
312 } else {
313 mMainThreadHandler.post(mCheckStartPosSet);
314 }
315 };
316
317 mMainThreadHandler.post(mCheckStartPosSet);
318
319 try {
320 animLatch.await(2, TimeUnit.SECONDS);
321 } catch (InterruptedException e) {
322 mMainThreadHandler.removeCallbacks(mCheckStartPosSet);
323 throw e;
324 }
325 }
326
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800327 /**
328 * Testable version of the stack controller that dispatches its animations on the main thread.
329 */
330 private class TestableStackController extends StackAnimationController {
331 @Override
Joshua Tsujie13b4fc2019-02-28 18:39:57 -0500332 protected void flingThenSpringFirstBubbleWithStackFollowing(
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800333 DynamicAnimation.ViewProperty property, float vel, float friction,
334 SpringForce spring, Float finalPosition) {
335 mMainThreadHandler.post(() ->
336 super.flingThenSpringFirstBubbleWithStackFollowing(
337 property, vel, friction, spring, finalPosition));
338 }
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400339
340 @Override
341 protected void springFirstBubbleWithStackFollowing(DynamicAnimation.ViewProperty property,
Joshua Tsujiaf8df2d2019-08-01 16:08:01 -0400342 SpringForce spring, float vel, float finalPosition, Runnable... after) {
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400343 mMainThreadHandler.post(() ->
344 super.springFirstBubbleWithStackFollowing(
Joshua Tsujiaf8df2d2019-08-01 16:08:01 -0400345 property, spring, vel, finalPosition, after));
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400346 }
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800347 }
348}