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