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