blob: dfdbf323365f02f3d1eaf97c1ffba90ca16a110a [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wm;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeastOnce;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static java.util.concurrent.TimeUnit.SECONDS;
import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
import android.animation.ValueAnimator;
import android.graphics.Matrix;
import android.graphics.Point;
import android.os.PowerManagerInternal;
import android.platform.test.annotations.Presubmit;
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.CountDownLatch;
/**
* Test class for {@link SurfaceAnimationRunner}.
*
* Build/Install/Run:
* atest FrameworksServicesTests:SurfaceAnimationRunnerTest
*/
@SmallTest
@Presubmit
public class SurfaceAnimationRunnerTest extends WindowTestsBase {
@Mock SurfaceControl mMockSurface;
@Mock Transaction mMockTransaction;
@Mock AnimationSpec mMockAnimationSpec;
@Mock PowerManagerInternal mMockPowerManager;
private SurfaceAnimationRunner mSurfaceAnimationRunner;
private CountDownLatch mFinishCallbackLatch;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mFinishCallbackLatch = new CountDownLatch(1);
mSurfaceAnimationRunner = new SurfaceAnimationRunner(null /* callbackProvider */, null,
mMockTransaction, mMockPowerManager);
}
private void finishedCallback() {
mFinishCallbackLatch.countDown();
}
@Test
public void testAnimation() throws Exception {
mSurfaceAnimationRunner
.startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction,
this::finishedCallback);
// Ensure that the initial transformation has been applied.
final Matrix m = new Matrix();
m.setTranslate(-10, 0);
verify(mMockTransaction, atLeastOnce()).setMatrix(eq(mMockSurface), eq(m), any());
verify(mMockTransaction, atLeastOnce()).setAlpha(eq(mMockSurface), eq(1.0f));
mFinishCallbackLatch.await(1, SECONDS);
assertFinishCallbackCalled();
m.setTranslate(10, 0);
verify(mMockTransaction, atLeastOnce()).setMatrix(eq(mMockSurface), eq(m), any());
// At least 3 times: After initialization, first frame, last frame.
verify(mMockTransaction, atLeast(3)).setAlpha(eq(mMockSurface), eq(1.0f));
}
@Test
public void testCancel_notStarted() {
mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
mMockTransaction, mMockPowerManager);
mSurfaceAnimationRunner
.startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction,
this::finishedCallback);
mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
waitUntilHandlersIdle();
assertTrue(mSurfaceAnimationRunner.mPendingAnimations.isEmpty());
assertFinishCallbackNotCalled();
}
@Test
public void testCancel_running() throws Exception {
mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
mMockTransaction, mMockPowerManager);
mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
mMockTransaction, this::finishedCallback);
waitUntilNextFrame();
assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
waitUntilHandlersIdle();
assertFinishCallbackNotCalled();
}
@FlakyTest(bugId = 71719744)
@Test
public void testCancel_sneakyCancelBeforeUpdate() throws Exception {
mSurfaceAnimationRunner = new SurfaceAnimationRunner(null, () -> new ValueAnimator() {
{
setFloatValues(0f, 1f);
}
@Override
public void addUpdateListener(AnimatorUpdateListener listener) {
super.addUpdateListener(animation -> {
// Sneaky test cancels animation just before applying frame to simulate
// interleaving of multiple threads. Muahahaha
if (animation.getCurrentPlayTime() > 0) {
mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
}
listener.onAnimationUpdate(animation);
});
}
}, mMockTransaction, mMockPowerManager);
when(mMockAnimationSpec.getDuration()).thenReturn(200L);
mSurfaceAnimationRunner.startAnimation(mMockAnimationSpec, mMockSurface, mMockTransaction,
this::finishedCallback);
// We need to wait for two frames: The first frame starts the animation, the second frame
// actually cancels the animation.
waitUntilNextFrame();
waitUntilNextFrame();
assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
verify(mMockAnimationSpec, atLeastOnce()).apply(any(), any(), eq(0L));
}
@FlakyTest(bugId = 74780584)
@Test
public void testDeferStartingAnimations() throws Exception {
mSurfaceAnimationRunner.deferStartingAnimations();
mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
mMockTransaction, this::finishedCallback);
waitUntilNextFrame();
assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
mSurfaceAnimationRunner.continueStartingAnimations();
waitUntilNextFrame();
assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
mFinishCallbackLatch.await(1, SECONDS);
assertFinishCallbackCalled();
}
@Test
public void testPowerHint() throws Exception {
mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
mMockTransaction, mMockPowerManager);
mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
mMockTransaction, this::finishedCallback);
waitUntilNextFrame();
// TODO: For some reason we don't have access to PowerHint definition from the tests. For
// now let's just verify that we got some kind of hint.
verify(mMockPowerManager).powerHint(anyInt(), anyInt());
}
private void waitUntilNextFrame() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mSurfaceAnimationRunner.mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT,
latch::countDown, null /* token */);
latch.await();
}
private void assertFinishCallbackCalled() {
assertEquals(0, mFinishCallbackLatch.getCount());
}
private void assertFinishCallbackNotCalled() {
assertEquals(1, mFinishCallbackLatch.getCount());
}
private AnimationSpec createTranslateAnimation() {
final Animation a = new TranslateAnimation(-10, 10, 0, 0);
a.initialize(0, 0, 0, 0);
a.setDuration(50);
return new WindowAnimationSpec(a, new Point(0, 0), false /* canSkipFirstFrame */);
}
/**
* Callback provider that doesn't animate at all.
*/
private static final class NoOpFrameCallbackProvider implements AnimationFrameCallbackProvider {
@Override
public void postFrameCallback(FrameCallback callback) {
}
@Override
public void postCommitCallback(Runnable runnable) {
}
@Override
public long getFrameTime() {
return 0;
}
@Override
public long getFrameDelay() {
return 0;
}
@Override
public void setFrameDelay(long delay) {
}
}
}