1 /* 2 * Copyright (C) 2016 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.drawable.cts; 18 19 import static junit.framework.TestCase.assertTrue; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.fail; 23 24 import android.app.Activity; 25 import android.content.res.Resources; 26 import android.graphics.Bitmap; 27 import android.graphics.Canvas; 28 import android.graphics.Color; 29 import android.graphics.Rect; 30 import android.graphics.cts.R; 31 import android.graphics.drawable.AnimatedVectorDrawable; 32 import android.util.Log; 33 import android.view.PixelCopy; 34 import android.view.View; 35 import android.widget.ImageView; 36 37 import androidx.test.InstrumentationRegistry; 38 import androidx.test.filters.FlakyTest; 39 import androidx.test.filters.LargeTest; 40 import androidx.test.filters.SmallTest; 41 import androidx.test.rule.ActivityTestRule; 42 43 import com.android.compatibility.common.util.OverrideAnimationScaleRule; 44 import com.android.compatibility.common.util.SynchronousPixelCopy; 45 import com.android.compatibility.common.util.SystemUtil; 46 import com.android.compatibility.common.util.WidgetTestUtils; 47 48 import org.junit.AfterClass; 49 import org.junit.Assert; 50 import org.junit.Before; 51 import org.junit.BeforeClass; 52 import org.junit.Rule; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 import org.junit.runners.Parameterized; 56 57 import java.util.concurrent.CountDownLatch; 58 import java.util.concurrent.TimeUnit; 59 60 @LargeTest 61 @RunWith(Parameterized.class) 62 public class AnimatedVectorDrawableParameterizedTest { 63 @Rule 64 public ActivityTestRule<DrawableStubActivity> mActivityRule = 65 new ActivityTestRule<>(DrawableStubActivity.class); 66 67 // Some of these tests require animations to work normally, which may not be the case 68 // depending on the current state of the test device 69 @Rule 70 public final OverrideAnimationScaleRule animationScaleRule = 71 new OverrideAnimationScaleRule(1f); 72 73 private static final int IMAGE_WIDTH = 64; 74 private static final int IMAGE_HEIGHT = 64; 75 private static final long MAX_TIMEOUT_MS = 1000; 76 77 private static float sTransitionScaleBefore = Float.NaN; 78 79 private Activity mActivity = null; 80 private Resources mResources = null; 81 private final int mLayerType; 82 83 @Parameterized.Parameters data()84 public static Object[] data() { 85 return new Object[] { 86 View.LAYER_TYPE_HARDWARE, 87 View.LAYER_TYPE_NONE, 88 View.LAYER_TYPE_SOFTWARE 89 }; 90 } 91 92 @BeforeClass setUpClass()93 public static void setUpClass() throws Exception { 94 try { 95 sTransitionScaleBefore = Float.parseFloat(SystemUtil.runShellCommand( 96 InstrumentationRegistry.getInstrumentation(), 97 "settings get global transition_animation_scale")); 98 99 SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), 100 "settings put global transition_animation_scale 0"); 101 } catch (NumberFormatException e) { 102 Log.e("AnimatedVectorDrawableTest", "Could not read transition_animation_scale", e); 103 sTransitionScaleBefore = Float.NaN; 104 } 105 } 106 107 @AfterClass tearDownClass()108 public static void tearDownClass() throws Exception { 109 if (!Float.isNaN(sTransitionScaleBefore)) { 110 SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), 111 "settings put global transition_animation_scale " + 112 sTransitionScaleBefore); 113 } 114 } 115 AnimatedVectorDrawableParameterizedTest(final int layerType)116 public AnimatedVectorDrawableParameterizedTest(final int layerType) throws Throwable { 117 mLayerType = layerType; 118 } 119 120 @Before setup()121 public void setup() { 122 mActivity = mActivityRule.getActivity(); 123 mResources = mActivity.getResources(); 124 } 125 126 @Test 127 @FlakyTest (bugId = 72737527) testAnimationOnLayer()128 public void testAnimationOnLayer() throws Throwable { 129 final Animatable2Callback callback = new Animatable2Callback(); 130 // Can't simply use final here, b/c it needs to be initialized and referred later in UI 131 // thread. 132 final ImageView[] imageView = new ImageView[1]; 133 mActivityRule.runOnUiThread(() -> { 134 mActivity.setContentView(R.layout.fixed_sized_imageview); 135 imageView[0] = (ImageView) mActivity.findViewById(R.id.imageview); 136 }); 137 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, imageView[0], 138 (Runnable) () -> { 139 imageView[0].setImageDrawable( 140 mResources.getDrawable(R.drawable.animated_vector_favorite)); 141 imageView[0].setLayerType(mLayerType, null); 142 AnimatedVectorDrawable avd = 143 (AnimatedVectorDrawable) imageView[0].getDrawable(); 144 avd.registerAnimationCallback(callback); 145 avd.start(); 146 }); 147 callback.waitForStart(); 148 waitWhilePumpingFrames(5, imageView[0], 200); 149 150 Bitmap lastScreenShot = null; 151 final Rect srcRect = new Rect(); 152 mActivityRule.runOnUiThread(() -> { 153 imageView[0].getGlobalVisibleRect(srcRect); 154 }); 155 156 int counter = 0; 157 while (!callback.endIsCalled()) { 158 // Take a screen shot every 50ms, and compare with previous screenshot for the ImageView 159 // content, to make sure the AVD is animating when set on HW layer. 160 Bitmap screenShot = takeScreenshot(srcRect); 161 if (callback.endIsCalled()) { 162 // Animation already ended, the screenshot may not contain valid animation content, 163 // skip the comparison. 164 break; 165 } 166 counter++; 167 boolean isIdentical = isAlmostIdenticalInRect(screenShot, lastScreenShot); 168 if (isIdentical) { 169 String outputFolder = mActivity.getExternalFilesDir(null).getAbsolutePath(); 170 DrawableTestUtils.saveVectorDrawableIntoPNG(screenShot, outputFolder, 171 "screenshot_" + counter); 172 DrawableTestUtils.saveVectorDrawableIntoPNG(lastScreenShot, outputFolder, 173 "screenshot_" + (counter - 1)); 174 fail("Two consecutive screenshots of AVD are identical, AVD is " 175 + "likely not animating"); 176 } 177 lastScreenShot = screenShot; 178 179 // Wait 50ms before the next screen shot. If animation ended during the wait, exit the 180 // loop. 181 if (callback.waitForEnd(50)) { 182 break; 183 } 184 } 185 // In this test, we want to make sure that we at least have 5 screenshots. 186 assertTrue(counter >= 5); 187 188 mActivityRule.runOnUiThread(() -> { 189 AnimatedVectorDrawable avd = (AnimatedVectorDrawable) imageView[0].getDrawable(); 190 avd.stop(); 191 }); 192 } 193 194 // Pump frames by repeatedly invalidating the given view. Return true if successfully pumped 195 // the given number of frames before timeout, false otherwise. waitWhilePumpingFrames(int frameCount, final View view, long timeout)196 private boolean waitWhilePumpingFrames(int frameCount, final View view, long timeout) 197 throws Throwable { 198 final CountDownLatch frameLatch = new CountDownLatch(frameCount); 199 mActivityRule.runOnUiThread(() -> { 200 view.getViewTreeObserver().addOnPreDrawListener(() -> { 201 if (frameLatch.getCount() > 0) { 202 frameLatch.countDown(); 203 view.postInvalidate(); 204 } 205 return true; 206 }); 207 }); 208 return frameLatch.await(timeout, TimeUnit.MILLISECONDS); 209 } 210 211 @SmallTest 212 @Test testSingleFrameAnimation()213 public void testSingleFrameAnimation() throws Throwable { 214 int resId = R.drawable.avd_single_frame; 215 final AnimatedVectorDrawable d1 = 216 (AnimatedVectorDrawable) mResources.getDrawable(resId); 217 // The AVD has a duration as 16ms. 218 mActivityRule.runOnUiThread(() -> { 219 Bitmap bitmap = 220 Bitmap.createBitmap(IMAGE_WIDTH, IMAGE_HEIGHT, Bitmap.Config.ARGB_8888); 221 Canvas canvas = new Canvas(bitmap); 222 223 mActivity.setContentView(R.layout.animated_vector_drawable_source); 224 ImageView imageView = (ImageView) mActivity.findViewById(R.id.avd_view); 225 imageView.setLayerType(mLayerType, null); 226 imageView.setImageDrawable(d1); 227 d1.start(); 228 d1.stop(); 229 d1.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT); 230 bitmap.eraseColor(0); 231 d1.draw(canvas); 232 int endColor = bitmap.getPixel(IMAGE_WIDTH / 2, IMAGE_HEIGHT / 2); 233 assertEquals("Center point's color must be green", 0xFF00FF00, endColor); 234 }); 235 } 236 237 @LargeTest 238 @Test testEmptyAnimatorSet()239 public void testEmptyAnimatorSet() throws Throwable { 240 int resId = R.drawable.avd_empty_animator; 241 final Animatable2Callback callback = new Animatable2Callback(); 242 final AnimatedVectorDrawable d1 = 243 (AnimatedVectorDrawable) mResources.getDrawable(resId); 244 d1.registerAnimationCallback(callback); 245 mActivityRule.runOnUiThread(() -> { 246 mActivity.setContentView(R.layout.animated_vector_drawable_source); 247 ImageView imageView = (ImageView) mActivity.findViewById(R.id.avd_view); 248 imageView.setLayerType(mLayerType, null); 249 imageView.setImageDrawable(d1); 250 d1.registerAnimationCallback(callback); 251 d1.start(); 252 }); 253 Assert.assertTrue(callback.waitForStart()); 254 AnimatedVectorDrawableTest.waitForAVDStop(callback, MAX_TIMEOUT_MS); 255 // Check that the AVD with empty AnimatorSet has finished 256 callback.assertEnded(true); 257 callback.assertAVDRuntime(0, TimeUnit.MILLISECONDS.toNanos(900)); 258 } 259 260 // Does a fuzzy comparison between two images. isAlmostIdenticalInRect(Bitmap image1, Bitmap image2)261 private static boolean isAlmostIdenticalInRect(Bitmap image1, Bitmap image2) { 262 if (image1 == null || image2 == null) { 263 return false; 264 } 265 266 if (image1.getWidth() != image2.getWidth() || image1.getHeight() != image2.getHeight()) { 267 throw new IllegalArgumentException("Images size are not the same. image1:" + image1 268 + "image2:" + image2); 269 } 270 271 Rect rangeRect = new Rect(0, 0, image1.getWidth(), image1.getHeight()); 272 273 for (int x = rangeRect.left; x < rangeRect.right; x++) { 274 for (int y = rangeRect.top; y < rangeRect.bottom; y++) { 275 int color1 = image1.getPixel(x, y); 276 int color2 = image2.getPixel(x, y); 277 int rDiff = Math.abs(Color.red(color1) - Color.red(color2)); 278 int gDiff = Math.abs(Color.green(color1) - Color.green(color2)); 279 int bDiff = Math.abs(Color.blue(color1) - Color.blue(color2)); 280 if (rDiff + gDiff + bDiff > 8) { 281 return false; 282 } 283 } 284 } 285 return true; 286 } 287 288 @Test 289 @FlakyTest (bugId = 72737527) testInfiniteAVD()290 public void testInfiniteAVD() throws Throwable { 291 final Animatable2Callback callback = new Animatable2Callback(); 292 // Can't simply use final here, b/c it needs to be initialized and referred later in UI 293 // thread. 294 final ImageView[] imageView = new ImageView[1]; 295 mActivityRule.runOnUiThread(() -> { 296 mActivity.setContentView(R.layout.fixed_sized_imageview); 297 imageView[0] = (ImageView) mActivity.findViewById(R.id.imageview); 298 }); 299 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, imageView[0], 300 (Runnable) () -> { 301 imageView[0].setImageDrawable(mResources.getDrawable(R.drawable.infinite_avd)); 302 imageView[0].setLayerType(mLayerType, null); 303 AnimatedVectorDrawable avd = (AnimatedVectorDrawable) imageView[0].getDrawable(); 304 avd.registerAnimationCallback(callback); 305 306 avd.start(); 307 }); 308 309 callback.waitForStart(); 310 waitWhilePumpingFrames(5, imageView[0], 200); 311 Bitmap lastScreenShot = null; 312 final Rect srcRect = new Rect(); 313 mActivityRule.runOnUiThread(() -> { 314 mActivity.findViewById(R.id.imageview).getGlobalVisibleRect(srcRect); 315 }); 316 317 for (int counter = 0; counter < 10; counter++) { 318 // Take a screen shot every 100ms, and compare with previous screenshot for the ImageView 319 // content, to make sure the AVD is animating when set on HW layer. 320 Bitmap screenShot = takeScreenshot(srcRect); 321 boolean isIdentical = isAlmostIdenticalInRect(screenShot, lastScreenShot); 322 if (isIdentical) { 323 String outputFolder = mActivity.getExternalFilesDir(null).getAbsolutePath(); 324 DrawableTestUtils.saveVectorDrawableIntoPNG(screenShot, outputFolder, 325 "inf_avd_screenshot_" + mLayerType + "_" + counter); 326 DrawableTestUtils.saveVectorDrawableIntoPNG(lastScreenShot, outputFolder, 327 "inf_avd_screenshot_" + mLayerType + "_" + (counter - 1)); 328 fail("Two consecutive screenshots of AVD are identical, AVD is " 329 + "likely not animating"); 330 } 331 lastScreenShot = screenShot; 332 counter++; 333 334 // Wait 100ms before the next screen shot. If animation ended during the wait, fail the 335 // test, as the infinite avd should not end until we call stop(). 336 if (callback.waitForEnd(100)) { 337 fail("Infinite AnimatedVectorDrawable should not end on its own."); 338 } 339 } 340 Assert.assertFalse(callback.endIsCalled()); 341 mActivityRule.runOnUiThread(() -> { 342 AnimatedVectorDrawable avd = (AnimatedVectorDrawable) imageView[0].getDrawable(); 343 avd.stop(); 344 }); 345 } 346 347 // Copy the source rectangle from the screen into the returned bitmap. takeScreenshot(Rect srcRect)348 private Bitmap takeScreenshot(Rect srcRect) { 349 SynchronousPixelCopy copy = new SynchronousPixelCopy(); 350 Bitmap dest = Bitmap.createBitmap( 351 srcRect.width(), srcRect.height(), Bitmap.Config.ARGB_8888); 352 int copyResult = copy.request(mActivity.getWindow(), srcRect, dest); 353 Assert.assertEquals(PixelCopy.SUCCESS, copyResult); 354 return dest; 355 } 356 } 357