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 android.graphics.cts; 18 19 import static androidx.test.InstrumentationRegistry.getInstrumentation; 20 21 import android.Manifest; 22 import android.animation.Animator; 23 import android.animation.AnimatorListenerAdapter; 24 import android.animation.AnimatorSet; 25 import android.animation.ValueAnimator; 26 import android.support.test.uiautomator.UiDevice; 27 28 import androidx.test.filters.MediumTest; 29 import androidx.test.platform.app.InstrumentationRegistry; 30 import androidx.test.rule.ActivityTestRule; 31 import androidx.test.runner.AndroidJUnit4; 32 33 import com.android.compatibility.common.util.AdoptShellPermissionsRule; 34 import com.android.compatibility.common.util.OverrideAnimationScaleRule; 35 36 import junit.framework.Assert; 37 38 import org.junit.After; 39 import org.junit.Before; 40 import org.junit.Rule; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 44 import java.util.concurrent.CountDownLatch; 45 import java.util.concurrent.TimeUnit; 46 47 @MediumTest 48 @RunWith(AndroidJUnit4.class) 49 public class AnimatorLeakTest { 50 51 @Rule(order = 0) 52 public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule( 53 InstrumentationRegistry.getInstrumentation().getUiAutomation(), 54 Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); 55 56 @Rule(order = 1) 57 public ActivityTestRule<EmptyActivity> mActivityRule = 58 new ActivityTestRule<>(EmptyActivity.class, false, true); 59 public ActivityTestRule<EmptyActivity2> mActivityRule2 = 60 new ActivityTestRule<>(EmptyActivity2.class, false, false); 61 62 // Ensure animators are enabled when these tests run 63 @Rule 64 public final OverrideAnimationScaleRule animationScaleRule = 65 new OverrideAnimationScaleRule(1f); 66 67 boolean mPaused = false; 68 boolean mPausedSet = false; 69 boolean mFinitePaused = false; 70 boolean mFinitePausedSet = false; 71 boolean mResumed = false; 72 long mDefaultAnimatorPauseDelay = 10000L; 73 74 @Before setup()75 public void setup() { 76 mDefaultAnimatorPauseDelay = Animator.getBackgroundPauseDelay(); 77 } 78 79 @After cleanup()80 public void cleanup() { 81 Animator.setAnimatorPausingEnabled(true); 82 Animator.setBackgroundPauseDelay(mDefaultAnimatorPauseDelay); 83 } 84 85 /** 86 * The approach of this test is to start animators in the main activity for the test. 87 * That activity is forced into the background and the test checks whether the animators 88 * are paused appropriately. The activity is then forced back into the foreground again 89 * and the test checks whether the animators previously paused are resumed. There are also 90 * checks to make sure that animators which should not have been paused are handled 91 * correctly. 92 */ 93 @Test testPauseResume()94 public void testPauseResume() { 95 // Latches used to wait for each of the appropriate lifecycle events 96 final CountDownLatch animatorStartedLatch = new CountDownLatch(1); 97 // There are 2 animators which should be paused and resumed, thus a countdown of 2 98 final CountDownLatch animatorPausedLatch = new CountDownLatch(2); 99 final CountDownLatch animatorResumedLatch = new CountDownLatch(2); 100 101 // The first of these (infinite) should get paused, the second (finite) should not 102 ValueAnimator infiniteAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(1000); 103 infiniteAnimator.setRepeatCount(ValueAnimator.INFINITE); 104 ValueAnimator finiteAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(5000); 105 106 // Now create infinite and finite AnimatorSets 107 // As above, the infinite set should get paused, the finite one should not 108 ValueAnimator infiniteAnimator1 = ValueAnimator.ofFloat(0f, 1f).setDuration(1000); 109 infiniteAnimator1.setRepeatCount(ValueAnimator.INFINITE); 110 AnimatorSet infiniteSet = new AnimatorSet(); 111 infiniteSet.play(infiniteAnimator1); 112 ValueAnimator finiteAnimator1 = ValueAnimator.ofFloat(0f, 1f).setDuration(5000); 113 AnimatorSet finiteSet = new AnimatorSet(); 114 finiteSet.play(finiteAnimator1); 115 116 // This listener tracks which animators get paused and resumed 117 AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { 118 @Override 119 public void onAnimationStart(Animator animation) { 120 // Wait until animators start to trigger the lifecycle changes 121 animatorStartedLatch.countDown(); 122 } 123 124 @Override 125 public void onAnimationPause(Animator animation) { 126 if (animation == infiniteAnimator) { 127 mPaused = true; 128 } else if (animation == infiniteSet) { 129 mPausedSet = true; 130 } else if (animation == finiteAnimator) { 131 mFinitePaused = true; 132 // end it to avoid having it interfere with future resume latch 133 animation.end(); 134 return; 135 } else if (animation == finiteSet) { 136 mFinitePausedSet = true; 137 // end it to avoid having it interfere with future resume latch 138 animation.end(); 139 return; 140 } 141 animatorPausedLatch.countDown(); 142 } 143 144 @Override 145 public void onAnimationResume(Animator animation) { 146 mResumed = true; 147 animatorResumedLatch.countDown(); 148 } 149 }; 150 infiniteAnimator.addListener(listener); 151 infiniteAnimator.addPauseListener(listener); 152 finiteAnimator.addPauseListener(listener); 153 infiniteSet.addPauseListener(listener); 154 finiteSet.addPauseListener(listener); 155 156 getInstrumentation().runOnMainSync(new Runnable() { 157 public void run() { 158 Animator.setBackgroundPauseDelay(500); 159 try { 160 infiniteAnimator.start(); 161 finiteAnimator.start(); 162 infiniteSet.start(); 163 finiteSet.start(); 164 } catch (Throwable throwable) { 165 } 166 } 167 }); 168 try { 169 // Wait until the animators are running to start changing the activity lifecycle 170 animatorStartedLatch.await(5, TimeUnit.SECONDS); 171 172 // First, test that animators are *not* paused when an activity goes to the background 173 // if there is another activity in the same process which is now in the foreground. 174 mActivityRule2.launchActivity(null); 175 animatorPausedLatch.await(1, TimeUnit.SECONDS); 176 Assert.assertFalse("Animator was paused", mPaused); 177 mActivityRule2.finishActivity(); 178 179 // Send the activity to the background. This should cause the animators to be paused 180 // after Animator.getBackgroundPauseDelay() 181 UiDevice uiDevice = UiDevice.getInstance(getInstrumentation()); 182 uiDevice.pressHome(); 183 184 animatorPausedLatch.await(5, TimeUnit.SECONDS); 185 186 // It is not possible (or obvious) how to bring the activity back into the foreground. 187 // However, AnimationHandler pauses/resumes all animators for the process based on 188 // *any* visible activities in that process. So it is sufficient to launch a second 189 // activity, which should resume the animators paused when the first activity went 190 // into the background. 191 mActivityRule2.launchActivity(null); 192 animatorResumedLatch.await(5, TimeUnit.SECONDS); 193 } catch (Exception e) { } 194 Assert.assertTrue("Animator was not paused", mPaused); 195 Assert.assertTrue("AnimatorSet was not paused", mPausedSet); 196 Assert.assertFalse("Non-infinite Animator was paused", mFinitePaused); 197 Assert.assertFalse("Non-infinite AnimatorSet was paused", mFinitePausedSet); 198 Assert.assertTrue("Animator was not resumed", mResumed); 199 Assert.assertTrue("AnimatorSet was not resumed", mResumed); 200 } 201 202 /** 203 * The approach of this test is to start animators in the main activity for the test. 204 * That activity is forced into the background and the test checks whether the animators 205 * are paused appropriately. The activity is then forced back into the foreground again 206 * and the test checks whether the animators previously paused are resumed. There are also 207 * checks to make sure that animators which should not have been paused are handled 208 * correctly. 209 */ 210 @Test testPauseDisablement()211 public void testPauseDisablement() { 212 // Latches used to wait for each of the appropriate lifecycle events 213 final CountDownLatch animatorStartedLatch = new CountDownLatch(1); 214 // There are 2 animators which should be paused and resumed, thus a countdown of 2 215 final CountDownLatch animatorPausedLatch = new CountDownLatch(1); 216 final CountDownLatch animatorResumedLatch = new CountDownLatch(1); 217 218 // The first of these (infinite) should get paused, the second (finite) should not 219 ValueAnimator infiniteAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(1000); 220 infiniteAnimator.setRepeatCount(ValueAnimator.INFINITE); 221 222 // This listener tracks which animators get paused and resumed 223 AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { 224 @Override 225 public void onAnimationStart(Animator animation) { 226 // Wait until animators start to trigger the lifecycle changes 227 animatorStartedLatch.countDown(); 228 } 229 230 @Override 231 public void onAnimationPause(Animator animation) { 232 mPaused = true; 233 animatorPausedLatch.countDown(); 234 } 235 236 @Override 237 public void onAnimationResume(Animator animation) { 238 mResumed = true; 239 animatorResumedLatch.countDown(); 240 } 241 }; 242 infiniteAnimator.addListener(listener); 243 infiniteAnimator.addPauseListener(listener); 244 245 getInstrumentation().runOnMainSync(new Runnable() { 246 public void run() { 247 Animator.setBackgroundPauseDelay(500); 248 Animator.setAnimatorPausingEnabled(false); 249 try { 250 infiniteAnimator.start(); 251 } catch (Throwable throwable) { 252 } 253 } 254 }); 255 try { 256 // Wait until the animators are running to start changing the activity lifecycle 257 animatorStartedLatch.await(5, TimeUnit.SECONDS); 258 259 // Send the activity to the background. This should cause the animators to be paused 260 // after Animator.getBackgroundPauseDelay() 261 UiDevice uiDevice = UiDevice.getInstance(getInstrumentation()); 262 uiDevice.pressHome(); 263 264 animatorPausedLatch.await(2, TimeUnit.SECONDS); 265 266 // It is not possible (or obvious) how to bring the activity back into the foreground. 267 // However, AnimationHandler pauses/resumes all animators for the process based on 268 // *any* visible activities in that process. So it is sufficient to launch a second 269 // activity, which should resume the animators paused when the first activity went 270 // into the background. 271 mActivityRule2.launchActivity(null); 272 animatorResumedLatch.await(2, TimeUnit.SECONDS); 273 } catch (Exception e) { } 274 Assert.assertFalse("Animator paused when pausing disabled", mPaused); 275 Assert.assertFalse("Animator resumed when pausing disabled", mResumed); 276 } 277 278 } 279