blob: 096f205e7a56a06992e12578c0e01f2f4f717186 [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;
20
21import android.graphics.PointF;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080022import android.testing.AndroidTestingRunner;
23import android.view.View;
24import android.widget.FrameLayout;
25
26import androidx.dynamicanimation.animation.DynamicAnimation;
27import androidx.dynamicanimation.animation.SpringForce;
Brett Chabot84151d92019-02-27 15:37:59 -080028import androidx.test.filters.SmallTest;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080029
30import com.android.systemui.R;
31
32import org.junit.Before;
Joshua Tsuji87ebd742019-01-25 16:01:26 -050033import org.junit.Ignore;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080034import org.junit.Test;
35import org.junit.runner.RunWith;
36import org.mockito.Spy;
37
38@SmallTest
39@RunWith(AndroidTestingRunner.class)
40public 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 Tsuji87ebd742019-01-25 16:01:26 -050093 @Ignore("Sporadically failing due to DynamicAnimation not settling.")
Joshua Tsujib1a796b2019-01-16 15:43:12 -080094 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 Tsuji87ebd742019-01-25 16:01:26 -0500124 @Ignore("Sporadically failing due to DynamicAnimation not settling.")
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800125 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
Joshua Tsujied386f22019-03-06 23:59:22 -0500180 @Ignore("Occasionally flakes, ignoring pending investigation.")
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800181 public void testChildRemoved() throws InterruptedException {
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500182 assertEquals(0, mLayout.getTransientViewCount());
183
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800184 final View firstView = mLayout.getChildAt(0);
185 mLayout.removeView(firstView);
186
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500187 // The view should now be transient, and missing from the view's normal hierarchy.
188 assertEquals(1, mLayout.getTransientViewCount());
189 assertEquals(-1, mLayout.indexOfChild(firstView));
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800190
191 waitForPropertyAnimations(DynamicAnimation.ALPHA);
192 waitForLayoutMessageQueue();
193
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500194 // The view should now be gone entirely, no transient views left.
195 assertEquals(0, mLayout.getTransientViewCount());
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800196
197 // The subsequent view should have been translated over to 0, not stacked off to the left.
198 assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f);
199 }
200
Joshua Tsujie13b4fc2019-02-28 18:39:57 -0500201 @Test
202 public void testRestoredAtRestingPosition() throws InterruptedException {
203 mStackController.flingStackThenSpringToEdge(0, 5000, 5000);
204
205 waitForPropertyAnimations(
206 DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
207 waitForLayoutMessageQueue();
208
209 final PointF prevStackPos = mStackController.getStackPosition();
210
211 mLayout.removeAllViews();
Joshua Tsujic1108432019-02-22 16:10:12 -0500212
213 waitForLayoutMessageQueue();
214
Joshua Tsujie13b4fc2019-02-28 18:39:57 -0500215 mLayout.addView(new FrameLayout(getContext()));
216
217 waitForLayoutMessageQueue();
218 waitForPropertyAnimations(
219 DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
220
221 assertEquals(prevStackPos, mStackController.getStackPosition());
222 }
223
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800224 /**
225 * Checks every child view to make sure it's stacked at the given coordinates, off to the left
226 * or right side depending on offset multiplier.
227 */
228 private void testStackedAtPosition(float x, float y, int offsetMultiplier) {
229 // Make sure the rest of the stack moved again, including the first bubble not moving, and
230 // is stacked to the right now that we're on the right side of the screen.
231 for (int i = 0; i < mLayout.getChildCount(); i++) {
232 assertEquals(x + i * offsetMultiplier * mStackOffset,
233 mViews.get(i).getTranslationX(), 2f);
234 assertEquals(y, mViews.get(i).getTranslationY(), 2f);
235 }
236 }
237
238 /**
239 * Testable version of the stack controller that dispatches its animations on the main thread.
240 */
241 private class TestableStackController extends StackAnimationController {
242 @Override
Joshua Tsujie13b4fc2019-02-28 18:39:57 -0500243 protected void flingThenSpringFirstBubbleWithStackFollowing(
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800244 DynamicAnimation.ViewProperty property, float vel, float friction,
245 SpringForce spring, Float finalPosition) {
246 mMainThreadHandler.post(() ->
247 super.flingThenSpringFirstBubbleWithStackFollowing(
248 property, vel, friction, spring, finalPosition));
249 }
250 }
251}