Home | History | Annotate | Download | only in cts
      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