• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.wm.shell.activityembedding;
18 
19 import static android.view.WindowManager.TRANSIT_OPEN;
20 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
21 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
22 
23 import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.calculateParentBounds;
24 import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.shouldUseJumpCutForAnimation;
25 import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
26 
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assert.assertFalse;
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.ArgumentMatchers.eq;
32 import static org.mockito.Mockito.doNothing;
33 import static org.mockito.Mockito.doReturn;
34 import static org.mockito.Mockito.never;
35 import static org.mockito.Mockito.verify;
36 import static org.mockito.Mockito.verifyNoMoreInteractions;
37 
38 import android.animation.Animator;
39 import android.annotation.NonNull;
40 import android.graphics.Point;
41 import android.graphics.Rect;
42 import android.view.animation.AlphaAnimation;
43 import android.view.animation.Animation;
44 import android.window.TransitionInfo;
45 
46 import androidx.test.filters.SmallTest;
47 
48 import com.android.wm.shell.transition.TransitionInfoBuilder;
49 
50 import com.google.testing.junit.testparameterinjector.TestParameter;
51 import com.google.testing.junit.testparameterinjector.TestParameterInjector;
52 
53 import org.junit.Before;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.mockito.ArgumentCaptor;
57 
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 
61 /**
62  * Tests for {@link ActivityEmbeddingAnimationRunner}.
63  *
64  * Build/Install/Run:
65  *  atest WMShellUnitTests:ActivityEmbeddingAnimationRunnerTests
66  */
67 @SmallTest
68 @RunWith(TestParameterInjector.class)
69 public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnimationTestBase {
70 
71     @Before
setup()72     public void setup() {
73         super.setUp();
74         doNothing().when(mController).onAnimationFinished(any());
75     }
76 
77     @Test
testStartAnimation()78     public void testStartAnimation() {
79         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
80                 .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
81                 .build();
82         doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(),
83                 any());
84 
85         mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction);
86 
87         final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class);
88         verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction),
89                 eq(mFinishTransaction),
90                 finishCallback.capture(), any());
91         verify(mStartTransaction).apply();
92         verify(mAnimator).start();
93         verifyNoMoreInteractions(mFinishTransaction);
94         verify(mController, never()).onAnimationFinished(any());
95 
96         // Call onAnimationFinished() when the animation is finished.
97         finishCallback.getValue().run();
98 
99         verify(mController).onAnimationFinished(mTransition);
100     }
101 
102     @Test
testChangesBehindStartingWindow()103     public void testChangesBehindStartingWindow() {
104         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
105                 .addChange(createChange(FLAG_IS_BEHIND_STARTING_WINDOW))
106                 .build();
107         final Animator animator = mAnimRunner.createAnimator(
108                 info, mStartTransaction, mFinishTransaction,
109                 () -> mFinishCallback.onTransitionFinished(null /* wct */),
110                 new ArrayList());
111 
112         // The animation should be empty when it is behind starting window.
113         assertEquals(0, animator.getDuration());
114     }
115 
116     @Test
testTransitionTypeDragResize()117     public void testTransitionTypeDragResize() {
118         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TASK_FRAGMENT_DRAG_RESIZE, 0)
119                 .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
120                 .build();
121         final Animator animator = mAnimRunner.createAnimator(
122                 info, mStartTransaction, mFinishTransaction,
123                 () -> mFinishCallback.onTransitionFinished(null /* wct */),
124                 new ArrayList());
125 
126         // The animation should be empty when it is a jump cut for drag resize.
127         assertEquals(0, animator.getDuration());
128     }
129 
130     @Test
testInvalidCustomAnimation_enableAnimationOptionsPerChange()131     public void testInvalidCustomAnimation_enableAnimationOptionsPerChange() {
132         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
133                 .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN))
134                 .build();
135         info.getChanges().getFirst().setAnimationOptions(TransitionInfo.AnimationOptions
136                 .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* changeResId */,
137                         0 /* exitResId */, false /* overrideTaskTransition */));
138         final Animator animator = mAnimRunner.createAnimator(
139                 info, mStartTransaction, mFinishTransaction,
140                 () -> mFinishCallback.onTransitionFinished(null /* wct */),
141                 new ArrayList<>());
142 
143         // An invalid custom animation is equivalent to jump-cut.
144         assertEquals(0, animator.getDuration());
145     }
146 
147     @Test
testCalculateParentBounds_flagEnabled_emptyParentSize()148     public void testCalculateParentBounds_flagEnabled_emptyParentSize() {
149         TransitionInfo.Change change;
150         final Rect actualParentBounds = new Rect();
151         change = prepareChangeForParentBoundsCalculationTest(
152                 new Point(0, 0) /* endRelOffset */,
153                 new Rect(0, 0, 2000, 2000),
154                 new Point() /* endParentSize */
155         );
156 
157         calculateParentBounds(change, actualParentBounds);
158 
159         assertTrue("Parent bounds must be empty because end parent size is not set.",
160                 actualParentBounds.isEmpty());
161     }
162 
163     @Test
testCalculateParentBounds_flagEnabled( @estParameter ParentBoundsTestParameters params)164     public void testCalculateParentBounds_flagEnabled(
165             @TestParameter ParentBoundsTestParameters params) {
166         final Rect parentBounds = params.getParentBounds();
167         final Rect endAbsBounds = params.getEndAbsBounds();
168         final TransitionInfo.Change change = prepareChangeForParentBoundsCalculationTest(
169                 new Point(endAbsBounds.left - parentBounds.left,
170                         endAbsBounds.top - parentBounds.top),
171                 endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
172         final Rect actualParentBounds = new Rect();
173 
174         calculateParentBounds(change, actualParentBounds);
175 
176         assertEquals(parentBounds, actualParentBounds);
177     }
178 
179     private enum ParentBoundsTestParameters {
180         PARENT_START_WITH_0_0(
181                 new int[]{0, 0, 2000, 2000},
182                 new int[]{0, 0, 2000, 2000}),
183         CONTAINER_NOT_START_WITH_0_0(
184                 new int[] {0, 0, 2000, 2000},
185                 new int[] {1000, 500, 1500, 1500}),
186         PARENT_ON_THE_RIGHT(
187                 new int[] {1000, 0, 2000, 2000},
188                 new int[] {1000, 500, 1500, 1500}),
189         PARENT_ON_THE_BOTTOM(
190                 new int[] {0, 1000, 2000, 2000},
191                 new int[] {500, 1500, 1500, 2000}),
192         PARENT_IN_THE_MIDDLE(
193                 new int[] {500, 500, 1500, 1500},
194                 new int[] {1000, 500, 1500, 1000});
195 
196         /**
197          * An int array to present {left, top, right, bottom} of the parent {@link Rect bounds}.
198          */
199         @NonNull
200         private final int[] mParentBounds;
201 
202         /**
203          * An int array to present {left, top, right, bottom} of the absolute container
204          * {@link Rect bounds} after the transition finishes.
205          */
206         @NonNull
207         private final int[] mEndAbsBounds;
208 
ParentBoundsTestParameters( @onNull int[] parentBounds, @NonNull int[] endAbsBounds)209         ParentBoundsTestParameters(
210                 @NonNull int[] parentBounds, @NonNull int[] endAbsBounds) {
211             mParentBounds = parentBounds;
212             mEndAbsBounds = endAbsBounds;
213         }
214 
215         @NonNull
getParentBounds()216         private Rect getParentBounds() {
217             return asRect(mParentBounds);
218         }
219 
220         @NonNull
getEndAbsBounds()221         private Rect getEndAbsBounds() {
222             return asRect(mEndAbsBounds);
223         }
224 
225         @NonNull
asRect(@onNull int[] bounds)226         private static Rect asRect(@NonNull int[] bounds) {
227             if (bounds.length != 4) {
228                 throw new IllegalArgumentException("There must be exactly 4 elements in bounds, "
229                         + "but found " + bounds.length + ": " + Arrays.toString(bounds));
230             }
231             return new Rect(bounds[0], bounds[1], bounds[2], bounds[3]);
232         }
233     }
234 
235     @Test
testShouldUseJumpCutForAnimation()236     public void testShouldUseJumpCutForAnimation() {
237         final Animation noopAnimation = new AlphaAnimation(0f, 1f);
238         assertTrue("Animation without duration should use jump cut.",
239                 shouldUseJumpCutForAnimation(noopAnimation));
240 
241         final Animation alphaAnimation = new AlphaAnimation(0f, 1f);
242         alphaAnimation.setDuration(100);
243         assertFalse("Animation with duration should not use jump cut.",
244                 shouldUseJumpCutForAnimation(alphaAnimation));
245     }
246 
247     @NonNull
prepareChangeForParentBoundsCalculationTest( @onNull Point endRelOffset, @NonNull Rect endAbsBounds, @NonNull Point endParentSize)248     private static TransitionInfo.Change prepareChangeForParentBoundsCalculationTest(
249             @NonNull Point endRelOffset, @NonNull Rect endAbsBounds, @NonNull Point endParentSize) {
250         final TransitionInfo.Change change = createChange(0 /* flags */);
251         change.setEndRelOffset(endRelOffset.x, endRelOffset.y);
252         change.setEndAbsBounds(endAbsBounds);
253         change.setEndParentSize(endParentSize.x, endParentSize.y);
254         return change;
255     }
256 }
257