| /* |
| * Copyright (C) 2018 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 android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; |
| |
| import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast; |
| import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; |
| import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; |
| import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; |
| import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.mockito.ArgumentMatchers.eq; |
| |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.os.Binder; |
| import android.os.IInterface; |
| import android.platform.test.annotations.Presubmit; |
| import android.view.IRemoteAnimationFinishedCallback; |
| import android.view.IRemoteAnimationRunner; |
| import android.view.RemoteAnimationAdapter; |
| import android.view.RemoteAnimationTarget; |
| import android.view.SurfaceControl; |
| import android.view.SurfaceControl.Transaction; |
| |
| import androidx.test.filters.FlakyTest; |
| import androidx.test.filters.SmallTest; |
| |
| import com.android.server.testutils.OffsettableClock; |
| import com.android.server.testutils.TestHandler; |
| import com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord; |
| import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| /** |
| * Build/Install/Run: |
| * atest FrameworksServicesTests:RemoteAnimationControllerTest |
| */ |
| @SmallTest |
| @Presubmit |
| public class RemoteAnimationControllerTest extends WindowTestsBase { |
| |
| @Mock SurfaceControl mMockLeash; |
| @Mock SurfaceControl mMockThumbnailLeash; |
| @Mock Transaction mMockTransaction; |
| @Mock OnAnimationFinishedCallback mFinishedCallback; |
| @Mock OnAnimationFinishedCallback mThumbnailFinishedCallback; |
| @Mock IRemoteAnimationRunner mMockRunner; |
| private RemoteAnimationAdapter mAdapter; |
| private RemoteAnimationController mController; |
| private final OffsettableClock mClock = new OffsettableClock.Stopped(); |
| private TestHandler mHandler; |
| |
| @Before |
| public void setUp() throws Exception { |
| MockitoAnnotations.initMocks(this); |
| |
| when(mMockRunner.asBinder()).thenReturn(new Binder()); |
| mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50, true /* changeNeedsSnapshot */); |
| mAdapter.setCallingPidUid(123, 456); |
| mWm.mH.runWithScissors(() -> mHandler = new TestHandler(null, mClock), 0); |
| mController = new RemoteAnimationController(mWm, mAdapter, mHandler); |
| } |
| |
| @Test |
| public void testRun() throws Exception { |
| final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); |
| mDisplayContent.mOpeningApps.add(win.mAppToken); |
| try { |
| final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, |
| new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; |
| adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); |
| mController.goodToGo(); |
| mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); |
| final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = |
| ArgumentCaptor.forClass(RemoteAnimationTarget[].class); |
| final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = |
| ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); |
| verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture()); |
| assertEquals(1, appsCaptor.getValue().length); |
| final RemoteAnimationTarget app = appsCaptor.getValue()[0]; |
| assertEquals(new Point(50, 100), app.position); |
| assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds); |
| assertEquals(win.mAppToken.getPrefixOrderIndex(), app.prefixOrderIndex); |
| assertEquals(win.mAppToken.getTask().mTaskId, app.taskId); |
| assertEquals(mMockLeash, app.leash); |
| assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect); |
| assertEquals(false, app.isTranslucent); |
| verify(mMockTransaction).setLayer(mMockLeash, app.prefixOrderIndex); |
| verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y); |
| verify(mMockTransaction).setWindowCrop(mMockLeash, 100, 50); |
| |
| finishedCaptor.getValue().onAnimationFinished(); |
| verify(mFinishedCallback).onAnimationFinished(eq(adapter)); |
| } finally { |
| mDisplayContent.mOpeningApps.clear(); |
| } |
| } |
| |
| @Test |
| public void testCancel() throws Exception { |
| final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); |
| final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, |
| new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; |
| adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); |
| mController.goodToGo(); |
| |
| adapter.onAnimationCancelled(mMockLeash); |
| verify(mMockRunner).onAnimationCancelled(); |
| } |
| |
| @Test |
| @FlakyTest(bugId = 133372977) |
| public void testTimeout() throws Exception { |
| final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); |
| final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, |
| new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; |
| adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); |
| mController.goodToGo(); |
| |
| mClock.fastForward(2500); |
| mHandler.timeAdvance(); |
| |
| verify(mMockRunner).onAnimationCancelled(); |
| verify(mFinishedCallback).onAnimationFinished(eq(adapter)); |
| } |
| |
| @Test |
| public void testTimeout_scaled() throws Exception { |
| try { |
| mWm.setAnimationScale(2, 5.0f); |
| final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, |
| "testWin"); |
| final AnimationAdapter adapter = mController.createRemoteAnimationRecord( |
| win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; |
| adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); |
| mController.goodToGo(); |
| |
| mClock.fastForward(2500); |
| mHandler.timeAdvance(); |
| |
| verify(mMockRunner, never()).onAnimationCancelled(); |
| |
| mClock.fastForward(10000); |
| mHandler.timeAdvance(); |
| |
| verify(mMockRunner).onAnimationCancelled(); |
| verify(mFinishedCallback).onAnimationFinished(eq(adapter)); |
| } finally { |
| mWm.setAnimationScale(2, 1.0f); |
| } |
| } |
| |
| @Test |
| public void testZeroAnimations() { |
| mController.goodToGo(); |
| verifyNoMoreInteractionsExceptAsBinder(mMockRunner); |
| } |
| |
| @Test |
| public void testNotReallyStarted() { |
| final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); |
| mController.createRemoteAnimationRecord(win.mAppToken, |
| new Point(50, 100), new Rect(50, 100, 150, 150), null); |
| mController.goodToGo(); |
| verifyNoMoreInteractionsExceptAsBinder(mMockRunner); |
| } |
| |
| @Test |
| public void testOneNotStarted() throws Exception { |
| final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1"); |
| final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2"); |
| mController.createRemoteAnimationRecord(win1.mAppToken, |
| new Point(50, 100), new Rect(50, 100, 150, 150), null); |
| final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win2.mAppToken, |
| new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; |
| adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); |
| mController.goodToGo(); |
| mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); |
| final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = |
| ArgumentCaptor.forClass(RemoteAnimationTarget[].class); |
| final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = |
| ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); |
| verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture()); |
| assertEquals(1, appsCaptor.getValue().length); |
| assertEquals(mMockLeash, appsCaptor.getValue()[0].leash); |
| } |
| |
| @Test |
| public void testRemovedBeforeStarted() { |
| final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); |
| final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, |
| new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; |
| adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); |
| win.mAppToken.removeImmediately(); |
| mController.goodToGo(); |
| verifyNoMoreInteractionsExceptAsBinder(mMockRunner); |
| verify(mFinishedCallback).onAnimationFinished(eq(adapter)); |
| } |
| |
| @Test |
| public void testChange() throws Exception { |
| final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); |
| mDisplayContent.mChangingApps.add(win.mAppToken); |
| try { |
| final RemoteAnimationRecord record = mController.createRemoteAnimationRecord( |
| win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150), |
| new Rect(0, 0, 200, 200)); |
| assertNotNull(record.mThumbnailAdapter); |
| ((AnimationAdapter) record.mAdapter) |
| .startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); |
| ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, |
| mMockTransaction, mThumbnailFinishedCallback); |
| mController.goodToGo(); |
| mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); |
| final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = |
| ArgumentCaptor.forClass(RemoteAnimationTarget[].class); |
| final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = |
| ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); |
| verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture()); |
| assertEquals(1, appsCaptor.getValue().length); |
| final RemoteAnimationTarget app = appsCaptor.getValue()[0]; |
| assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode); |
| assertEquals(new Point(50, 100), app.position); |
| assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds); |
| assertEquals(new Rect(0, 0, 200, 200), app.startBounds); |
| assertEquals(mMockLeash, app.leash); |
| assertEquals(mMockThumbnailLeash, app.startLeash); |
| assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect); |
| assertEquals(false, app.isTranslucent); |
| verify(mMockTransaction).setLayer(mMockLeash, app.prefixOrderIndex); |
| verify(mMockTransaction).setPosition( |
| mMockLeash, app.startBounds.left, app.startBounds.top); |
| verify(mMockTransaction).setWindowCrop(mMockLeash, 200, 200); |
| verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0); |
| |
| finishedCaptor.getValue().onAnimationFinished(); |
| verify(mFinishedCallback).onAnimationFinished(eq(record.mAdapter)); |
| verify(mThumbnailFinishedCallback).onAnimationFinished(eq(record.mThumbnailAdapter)); |
| } finally { |
| mDisplayContent.mChangingApps.clear(); |
| } |
| } |
| |
| private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { |
| verify(binder, atLeast(0)).asBinder(); |
| verifyNoMoreInteractions(binder); |
| } |
| } |