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