blob: f633f3996d13e116a2a035b49b1d6f3a2977c69b [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.mockito.Mockito.when;
20
21import android.content.Context;
22import android.os.Handler;
23import android.os.Looper;
24import android.view.DisplayCutout;
25import android.view.View;
Joshua Tsujia08b6d32019-01-29 16:15:52 -050026import android.view.ViewGroup;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080027import android.view.WindowInsets;
28import android.widget.FrameLayout;
29
30import androidx.dynamicanimation.animation.DynamicAnimation;
Joshua Tsujic1108432019-02-22 16:10:12 -050031import androidx.dynamicanimation.animation.SpringForce;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080032
33import com.android.systemui.R;
34import com.android.systemui.SysuiTestCase;
35
36import org.junit.Before;
37import org.mockito.Mock;
38import org.mockito.MockitoAnnotations;
39
40import java.util.ArrayList;
41import java.util.List;
Joshua Tsujic1108432019-02-22 16:10:12 -050042import java.util.Set;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080043import java.util.concurrent.CountDownLatch;
44import java.util.concurrent.TimeUnit;
45
46/**
47 * Test case for tests that involve the {@link PhysicsAnimationLayout}. This test case constructs a
48 * testable version of the layout, and provides some helpful methods to add views to the layout and
49 * wait for physics animations to finish running.
50 *
51 * See physics-animation-testing.md.
52 */
53public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
54 TestablePhysicsAnimationLayout mLayout;
55 List<View> mViews = new ArrayList<>();
56
57 Handler mMainThreadHandler;
58
Joshua Tsujib1a796b2019-01-16 15:43:12 -080059 int mSystemWindowInsetSize = 50;
60 int mCutoutInsetSize = 100;
61
62 int mWidth = 1000;
63 int mHeight = 1000;
64
65 @Mock
66 private WindowInsets mWindowInsets;
67
68 @Mock
69 private DisplayCutout mCutout;
70
Joshua Tsujicb97a112019-05-29 16:20:41 -040071 private int mMaxBubbles;
72
Joshua Tsujib1a796b2019-01-16 15:43:12 -080073 @Before
74 public void setUp() throws Exception {
75 MockitoAnnotations.initMocks(this);
76
77 mLayout = new TestablePhysicsAnimationLayout(mContext);
78 mLayout.setLeft(0);
79 mLayout.setRight(mWidth);
80 mLayout.setTop(0);
81 mLayout.setBottom(mHeight);
82
Joshua Tsujicb97a112019-05-29 16:20:41 -040083 mMaxBubbles =
Joshua Tsujib1a796b2019-01-16 15:43:12 -080084 getContext().getResources().getInteger(R.integer.bubbles_max_rendered);
85 mMainThreadHandler = new Handler(Looper.getMainLooper());
86
87 when(mWindowInsets.getSystemWindowInsetTop()).thenReturn(mSystemWindowInsetSize);
88 when(mWindowInsets.getSystemWindowInsetBottom()).thenReturn(mSystemWindowInsetSize);
89 when(mWindowInsets.getSystemWindowInsetLeft()).thenReturn(mSystemWindowInsetSize);
90 when(mWindowInsets.getSystemWindowInsetRight()).thenReturn(mSystemWindowInsetSize);
91
92 when(mWindowInsets.getDisplayCutout()).thenReturn(mCutout);
93 when(mCutout.getSafeInsetTop()).thenReturn(mCutoutInsetSize);
94 when(mCutout.getSafeInsetBottom()).thenReturn(mCutoutInsetSize);
95 when(mCutout.getSafeInsetLeft()).thenReturn(mCutoutInsetSize);
96 when(mCutout.getSafeInsetRight()).thenReturn(mCutoutInsetSize);
97 }
98
99 /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */
Joshua Tsujicb97a112019-05-29 16:20:41 -0400100 void addOneMoreThanBubbleLimitBubbles() throws InterruptedException {
101 for (int i = 0; i < mMaxBubbles + 1; i++) {
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800102 final View newView = new FrameLayout(mContext);
103 mLayout.addView(newView, 0);
104 mViews.add(0, newView);
105
106 newView.setTranslationX(0);
107 newView.setTranslationY(0);
108 }
109 }
110
111 /**
112 * Uses a {@link java.util.concurrent.CountDownLatch} to wait for the given properties'
113 * animations to finish before allowing the test to proceed.
114 */
115 void waitForPropertyAnimations(DynamicAnimation.ViewProperty... properties)
116 throws InterruptedException {
117 final CountDownLatch animLatch = new CountDownLatch(properties.length);
118 for (DynamicAnimation.ViewProperty property : properties) {
Joshua Tsujic1108432019-02-22 16:10:12 -0500119 mLayout.setTestEndActionForProperty(animLatch::countDown, property);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800120 }
Joshua Tsujic1108432019-02-22 16:10:12 -0500121
122 animLatch.await(2, TimeUnit.SECONDS);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800123 }
124
Joshua Tsujic1108432019-02-22 16:10:12 -0500125 /** Uses a latch to wait for the main thread message queue to finish. */
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800126 void waitForLayoutMessageQueue() throws InterruptedException {
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800127 CountDownLatch layoutLatch = new CountDownLatch(1);
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500128 mMainThreadHandler.post(layoutLatch::countDown);
Joshua Tsujic1108432019-02-22 16:10:12 -0500129 layoutLatch.await(2, TimeUnit.SECONDS);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800130 }
131
132 /**
133 * Testable subclass of the PhysicsAnimationLayout that ensures methods that trigger animations
134 * are run on the main thread, which is a requirement of DynamicAnimation.
135 */
136 protected class TestablePhysicsAnimationLayout extends PhysicsAnimationLayout {
137 public TestablePhysicsAnimationLayout(Context context) {
138 super(context);
139 }
140
141 @Override
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400142 protected boolean isActiveController(PhysicsAnimationController controller) {
143 // Return true since otherwise all test controllers will be seen as inactive since they
144 // are wrapped by MainThreadAnimationControllerWrapper.
145 return true;
146 }
147
148 @Override
Joshua Tsuji33c0e9c2019-05-14 16:45:39 -0400149 public boolean post(Runnable action) {
150 return mMainThreadHandler.post(action);
151 }
152
153 @Override
154 public boolean postDelayed(Runnable action, long delayMillis) {
155 return mMainThreadHandler.postDelayed(action, delayMillis);
156 }
157
158 @Override
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400159 public void setActiveController(PhysicsAnimationController controller) {
Joshua Tsujic1108432019-02-22 16:10:12 -0500160 runOnMainThreadAndBlock(
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400161 () -> super.setActiveController(
Joshua Tsujic1108432019-02-22 16:10:12 -0500162 new MainThreadAnimationControllerWrapper(controller)));
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800163 }
164
165 @Override
166 public void cancelAllAnimations() {
167 mMainThreadHandler.post(super::cancelAllAnimations);
168 }
169
170 @Override
Joshua Tsuji442b6272019-02-08 13:23:43 -0500171 public void cancelAnimationsOnView(View view) {
172 mMainThreadHandler.post(() -> super.cancelAnimationsOnView(view));
173 }
174
175 @Override
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800176 public WindowInsets getRootWindowInsets() {
177 return mWindowInsets;
178 }
179
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500180 @Override
Joshua Tsujic1108432019-02-22 16:10:12 -0500181 public void addView(View child, int index) {
182 child.setTag(R.id.physics_animator_tag, new TestablePhysicsPropertyAnimator(child));
183 super.addView(child, index);
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500184 }
185
186 @Override
187 public void addView(View child, int index, ViewGroup.LayoutParams params) {
Joshua Tsujic1108432019-02-22 16:10:12 -0500188 child.setTag(R.id.physics_animator_tag, new TestablePhysicsPropertyAnimator(child));
189 super.addView(child, index, params);
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500190 }
191
192 /**
Joshua Tsujic1108432019-02-22 16:10:12 -0500193 * Sets an end action that will be called after the 'real' end action that was already set.
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500194 */
Joshua Tsujic1108432019-02-22 16:10:12 -0500195 private void setTestEndActionForProperty(
196 Runnable action, DynamicAnimation.ViewProperty property) {
197 final Runnable realEndAction = mEndActionForProperty.get(property);
198
199 setEndActionForProperty(() -> {
200 if (realEndAction != null) {
201 realEndAction.run();
202 }
203
204 action.run();
205 }, property);
206 }
207
208 /** PhysicsPropertyAnimator that posts its animations to the main thread. */
209 protected class TestablePhysicsPropertyAnimator extends PhysicsPropertyAnimator {
210 public TestablePhysicsPropertyAnimator(View view) {
211 super(view);
212 }
213
214 @Override
215 protected void animateValueForChild(DynamicAnimation.ViewProperty property, View view,
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400216 float value, float startVel, long startDelay, float stiffness,
217 float dampingRatio, Runnable[] afterCallbacks) {
Joshua Tsujic1108432019-02-22 16:10:12 -0500218 mMainThreadHandler.post(() -> super.animateValueForChild(
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400219 property, view, value, startVel, startDelay, stiffness, dampingRatio,
220 afterCallbacks));
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500221 }
222 }
223
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800224 /**
Joshua Tsujic1108432019-02-22 16:10:12 -0500225 * Wrapper around an animation controller that dispatches methods that could start
226 * animations to the main thread.
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800227 */
Joshua Tsujic1108432019-02-22 16:10:12 -0500228 protected class MainThreadAnimationControllerWrapper extends PhysicsAnimationController {
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800229
Joshua Tsujic1108432019-02-22 16:10:12 -0500230 private final PhysicsAnimationController mWrappedController;
231
232 protected MainThreadAnimationControllerWrapper(PhysicsAnimationController controller) {
233 mWrappedController = controller;
234 }
235
236 @Override
237 protected void setLayout(PhysicsAnimationLayout layout) {
238 mWrappedController.setLayout(layout);
239 }
240
241 @Override
242 protected PhysicsAnimationLayout getLayout() {
243 return mWrappedController.getLayout();
244 }
245
246 @Override
247 Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
248 return mWrappedController.getAnimatedProperties();
249 }
250
251 @Override
252 int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
253 return mWrappedController.getNextAnimationInChain(property, index);
254 }
255
256 @Override
257 float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
258 return mWrappedController.getOffsetForChainedPropertyAnimation(property);
259 }
260
261 @Override
262 SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
263 return mWrappedController.getSpringForce(property, view);
264 }
265
266 @Override
267 void onChildAdded(View child, int index) {
268 runOnMainThreadAndBlock(() -> mWrappedController.onChildAdded(child, index));
269 }
270
271 @Override
272 void onChildRemoved(View child, int index, Runnable finishRemoval) {
273 runOnMainThreadAndBlock(
274 () -> mWrappedController.onChildRemoved(child, index, finishRemoval));
275 }
276
277 @Override
Joshua Tsujif49ee142019-05-29 16:32:01 -0400278 void onChildReordered(View child, int oldIndex, int newIndex) {
279 runOnMainThreadAndBlock(
280 () -> mWrappedController.onChildReordered(child, oldIndex, newIndex));
281 }
282
283 @Override
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400284 void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
285 runOnMainThreadAndBlock(
286 () -> mWrappedController.onActiveControllerForLayout(layout));
287 }
288
289 @Override
Joshua Tsujic1108432019-02-22 16:10:12 -0500290 protected PhysicsPropertyAnimator animationForChild(View child) {
291 PhysicsPropertyAnimator animator =
292 (PhysicsPropertyAnimator) child.getTag(R.id.physics_animator_tag);
293
294 if (!(animator instanceof TestablePhysicsPropertyAnimator)) {
295 animator = new TestablePhysicsPropertyAnimator(child);
296 child.setTag(R.id.physics_animator_tag, animator);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800297 }
298
Joshua Tsujic1108432019-02-22 16:10:12 -0500299 return animator;
300 }
301 }
302 }
303
304 /**
305 * Posts the given Runnable on the main thread, and blocks the calling thread until it's run.
306 */
307 private void runOnMainThreadAndBlock(Runnable action) {
308 final CountDownLatch latch = new CountDownLatch(1);
309 mMainThreadHandler.post(() -> {
310 action.run();
311 latch.countDown();
312 });
313
314 try {
315 latch.await(5, TimeUnit.SECONDS);
316 } catch (InterruptedException e) {
317 e.printStackTrace();
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800318 }
319 }
320}