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.platform.app.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.getInstance(getInstrumentation()).pressBack(); 182 183 animatorPausedLatch.await(5, TimeUnit.SECONDS); 184 185 // It is not possible (or obvious) how to bring the activity back into the foreground. 186 // However, AnimationHandler pauses/resumes all animators for the process based on 187 // *any* visible activities in that process. So it is sufficient to launch a second 188 // activity, which should resume the animators paused when the first activity went 189 // into the background. 190 mActivityRule2.launchActivity(null); 191 animatorResumedLatch.await(5, TimeUnit.SECONDS); 192 } catch (Exception e) { } 193 Assert.assertTrue("Animator was not paused", mPaused); 194 Assert.assertTrue("AnimatorSet was not paused", mPausedSet); 195 Assert.assertFalse("Non-infinite Animator was paused", mFinitePaused); 196 Assert.assertFalse("Non-infinite AnimatorSet was paused", mFinitePausedSet); 197 Assert.assertTrue("Animator was not resumed", mResumed); 198 Assert.assertTrue("AnimatorSet was not resumed", mResumed); 199 } 200 201 /** 202 * The approach of this test is to start animators in the main activity for the test. 203 * That activity is forced into the background and the test checks whether the animators 204 * are paused appropriately. The activity is then forced back into the foreground again 205 * and the test checks whether the animators previously paused are resumed. There are also 206 * checks to make sure that animators which should not have been paused are handled 207 * correctly. 208 */ 209 @Test testPauseDisablement()210 public void testPauseDisablement() { 211 // Latches used to wait for each of the appropriate lifecycle events 212 final CountDownLatch animatorStartedLatch = new CountDownLatch(1); 213 // There are 2 animators which should be paused and resumed, thus a countdown of 2 214 final CountDownLatch animatorPausedLatch = new CountDownLatch(1); 215 final CountDownLatch animatorResumedLatch = new CountDownLatch(1); 216 217 // The first of these (infinite) should get paused, the second (finite) should not 218 ValueAnimator infiniteAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(1000); 219 infiniteAnimator.setRepeatCount(ValueAnimator.INFINITE); 220 221 // This listener tracks which animators get paused and resumed 222 AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { 223 @Override 224 public void onAnimationStart(Animator animation) { 225 // Wait until animators start to trigger the lifecycle changes 226 animatorStartedLatch.countDown(); 227 } 228 229 @Override 230 public void onAnimationPause(Animator animation) { 231 mPaused = true; 232 animatorPausedLatch.countDown(); 233 } 234 235 @Override 236 public void onAnimationResume(Animator animation) { 237 mResumed = true; 238 animatorResumedLatch.countDown(); 239 } 240 }; 241 infiniteAnimator.addListener(listener); 242 infiniteAnimator.addPauseListener(listener); 243 244 getInstrumentation().runOnMainSync(new Runnable() { 245 public void run() { 246 Animator.setBackgroundPauseDelay(500); 247 Animator.setAnimatorPausingEnabled(false); 248 try { 249 infiniteAnimator.start(); 250 } catch (Throwable throwable) { 251 } 252 } 253 }); 254 try { 255 // Wait until the animators are running to start changing the activity lifecycle 256 animatorStartedLatch.await(5, TimeUnit.SECONDS); 257 258 // Send the activity to the background. This should cause the animators to be paused 259 // after Animator.getBackgroundPauseDelay() 260 UiDevice uiDevice = UiDevice.getInstance(getInstrumentation()); 261 uiDevice.pressHome(); 262 263 animatorPausedLatch.await(2, TimeUnit.SECONDS); 264 265 // It is not possible (or obvious) how to bring the activity back into the foreground. 266 // However, AnimationHandler pauses/resumes all animators for the process based on 267 // *any* visible activities in that process. So it is sufficient to launch a second 268 // activity, which should resume the animators paused when the first activity went 269 // into the background. 270 mActivityRule2.launchActivity(null); 271 animatorResumedLatch.await(2, TimeUnit.SECONDS); 272 } catch (Exception e) { } 273 Assert.assertFalse("Animator paused when pausing disabled", mPaused); 274 Assert.assertFalse("Animator resumed when pausing disabled", mResumed); 275 } 276 277 } 278