1 /*
2  * Copyright (C) 2017 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.server.wm;
18 
19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeastOnce;
22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
25 
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertFalse;
28 import static org.junit.Assert.assertTrue;
29 
30 import static java.util.concurrent.TimeUnit.SECONDS;
31 
32 import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
33 import android.animation.ValueAnimator;
34 import android.graphics.Matrix;
35 import android.graphics.Point;
36 import android.hardware.power.Boost;
37 import android.os.Handler;
38 import android.os.PowerManagerInternal;
39 import android.platform.test.annotations.Presubmit;
40 import android.view.Choreographer;
41 import android.view.Choreographer.FrameCallback;
42 import android.view.SurfaceControl;
43 import android.view.SurfaceControl.Transaction;
44 import android.view.animation.Animation;
45 import android.view.animation.TranslateAnimation;
46 
47 import androidx.test.filters.FlakyTest;
48 import androidx.test.filters.SmallTest;
49 
50 import com.android.server.AnimationThread;
51 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
52 
53 import org.junit.After;
54 import org.junit.Before;
55 import org.junit.Test;
56 import org.mockito.Mock;
57 import org.mockito.MockitoAnnotations;
58 
59 import java.util.concurrent.CountDownLatch;
60 
61 /**
62  * Test class for {@link SurfaceAnimationRunner}.
63  *
64  * Build/Install/Run:
65  *  atest WmTests:SurfaceAnimationRunnerTest
66  */
67 @SmallTest
68 @Presubmit
69 public class SurfaceAnimationRunnerTest {
70 
71     @Mock SurfaceControl mMockSurface;
72     @Mock Transaction mMockTransaction;
73     @Mock AnimationSpec mMockAnimationSpec;
74     @Mock PowerManagerInternal mMockPowerManager;
75 
76     private SurfaceAnimationRunner mSurfaceAnimationRunner;
77     private CountDownLatch mFinishCallbackLatch;
78 
79     private final Handler mAnimationThreadHandler = AnimationThread.getHandler();
80     private final Handler mSurfaceAnimationHandler = SurfaceAnimationThread.getHandler();
81 
82     @Before
setUp()83     public void setUp() throws Exception {
84         MockitoAnnotations.initMocks(this);
85 
86         mFinishCallbackLatch = new CountDownLatch(1);
87         mSurfaceAnimationRunner = new SurfaceAnimationRunner(null /* callbackProvider */, null,
88                 mMockTransaction, mMockPowerManager);
89     }
90 
91     @After
tearDown()92     public void tearDown() {
93         SurfaceAnimationThread.dispose();
94         AnimationThread.dispose();
95     }
96 
finishedCallback()97     private void finishedCallback() {
98         mFinishCallbackLatch.countDown();
99     }
100 
101     @Test
testAnimation()102     public void testAnimation() throws Exception {
103         mSurfaceAnimationRunner
104                 .startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction,
105                 this::finishedCallback);
106 
107         // Ensure that the initial transformation has been applied.
108         final Matrix m = new Matrix();
109         m.setTranslate(-10, 0);
110         verify(mMockTransaction, atLeastOnce()).setMatrix(eq(mMockSurface), eq(m), any());
111         verify(mMockTransaction, atLeastOnce()).setAlpha(eq(mMockSurface), eq(1.0f));
112 
113         waitHandlerIdle(mSurfaceAnimationHandler);
114         assertFinishCallbackCalled();
115 
116         m.setTranslate(10, 0);
117         verify(mMockTransaction, atLeastOnce()).setMatrix(eq(mMockSurface), eq(m), any());
118 
119         // At least 3 times: After initialization, first frame, last frame.
120         verify(mMockTransaction, atLeast(3)).setAlpha(eq(mMockSurface), eq(1.0f));
121     }
122 
123     @Test
testCancel_notStarted()124     public void testCancel_notStarted() {
125         mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
126                 mMockTransaction, mMockPowerManager);
127         mSurfaceAnimationRunner
128                 .startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction,
129                 this::finishedCallback);
130         mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
131         waitHandlerIdle(mAnimationThreadHandler);
132         assertTrue(mSurfaceAnimationRunner.mPendingAnimations.isEmpty());
133         assertFinishCallbackNotCalled();
134     }
135 
136     @Test
testCancel_running()137     public void testCancel_running() throws Exception {
138         mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
139                 mMockTransaction, mMockPowerManager);
140         mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
141                 mMockTransaction, this::finishedCallback);
142         waitUntilNextFrame();
143         assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
144         mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
145         assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
146         waitHandlerIdle(mAnimationThreadHandler);
147         assertFinishCallbackNotCalled();
148     }
149 
150     @FlakyTest(bugId = 71719744)
151     @Test
testCancel_sneakyCancelBeforeUpdate()152     public void testCancel_sneakyCancelBeforeUpdate() throws Exception {
153         mSurfaceAnimationRunner = new SurfaceAnimationRunner(null, () -> new ValueAnimator() {
154             {
155                 setFloatValues(0f, 1f);
156             }
157 
158             @Override
159             public void addUpdateListener(AnimatorUpdateListener listener) {
160                 super.addUpdateListener(animation -> {
161                     // Sneaky test cancels animation just before applying frame to simulate
162                     // interleaving of multiple threads. Muahahaha
163                     if (animation.getCurrentPlayTime() > 0) {
164                         mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
165                     }
166                     listener.onAnimationUpdate(animation);
167                 });
168             }
169         }, mMockTransaction, mMockPowerManager);
170         when(mMockAnimationSpec.getDuration()).thenReturn(200L);
171         mSurfaceAnimationRunner.startAnimation(mMockAnimationSpec, mMockSurface, mMockTransaction,
172                 this::finishedCallback);
173 
174         // We need to wait for two frames: The first frame starts the animation, the second frame
175         // actually cancels the animation.
176         waitUntilNextFrame();
177         waitUntilNextFrame();
178         assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
179         verify(mMockAnimationSpec, atLeastOnce()).apply(any(), any(), eq(0L));
180     }
181 
182     @Test
testDeferStartingAnimations()183     public void testDeferStartingAnimations() throws Exception {
184         mSurfaceAnimationRunner.deferStartingAnimations();
185         mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
186                 mMockTransaction, this::finishedCallback);
187         waitUntilNextFrame();
188         assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
189         mSurfaceAnimationRunner.continueStartingAnimations();
190         waitUntilNextFrame();
191         waitHandlerIdle(mSurfaceAnimationHandler);
192         assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
193         assertFinishCallbackCalled();
194     }
195 
196     @Test
testPowerBoost()197     public void testPowerBoost() throws Exception {
198         mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
199                 mMockTransaction, mMockPowerManager);
200         mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
201                 mMockTransaction, this::finishedCallback);
202         waitUntilNextFrame();
203 
204         verify(mMockPowerManager).setPowerBoost(eq(Boost.INTERACTION), eq(0));
205     }
206 
waitUntilNextFrame()207     private void waitUntilNextFrame() throws Exception {
208         final CountDownLatch latch = new CountDownLatch(1);
209         mSurfaceAnimationRunner.mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT,
210                 latch::countDown, null /* token */);
211         latch.await();
212     }
213 
waitHandlerIdle(Handler handler)214     private static void waitHandlerIdle(Handler handler) {
215         handler.runWithScissors(() -> { }, 0 /* timeout */);
216     }
217 
assertFinishCallbackCalled()218     private void assertFinishCallbackCalled() {
219         try {
220             assertTrue(mFinishCallbackLatch.await(5, SECONDS));
221         } catch (InterruptedException ignored) {
222         }
223         assertEquals(0, mFinishCallbackLatch.getCount());
224     }
225 
assertFinishCallbackNotCalled()226     private void assertFinishCallbackNotCalled() {
227         assertEquals(1, mFinishCallbackLatch.getCount());
228     }
229 
createTranslateAnimation()230     private AnimationSpec createTranslateAnimation() {
231         final Animation a = new TranslateAnimation(-10, 10, 0, 0);
232         a.initialize(0, 0, 0, 0);
233         a.setDuration(50);
234         return new WindowAnimationSpec(a, new Point(0, 0), false /* canSkipFirstFrame */,
235                 0 /* windowCornerRadius */);
236     }
237 
238     /**
239      * Callback provider that doesn't animate at all.
240      */
241     private static final class NoOpFrameCallbackProvider implements AnimationFrameCallbackProvider {
242 
243         @Override
postFrameCallback(FrameCallback callback)244         public void postFrameCallback(FrameCallback callback) {
245         }
246 
247         @Override
postCommitCallback(Runnable runnable)248         public void postCommitCallback(Runnable runnable) {
249         }
250 
251         @Override
getFrameTime()252         public long getFrameTime() {
253             return 0;
254         }
255 
256         @Override
getFrameDelay()257         public long getFrameDelay() {
258             return 0;
259         }
260 
261         @Override
setFrameDelay(long delay)262         public void setFrameDelay(long delay) {
263         }
264     }
265 }
266