Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2014 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 org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertNotNull;
     21 
     22 import android.content.Context;
     23 import android.content.res.Resources;
     24 import android.content.res.Resources.Theme;
     25 import android.content.res.XmlResourceParser;
     26 import android.graphics.Bitmap;
     27 import android.graphics.BitmapFactory;
     28 import android.graphics.Canvas;
     29 import android.graphics.Color;
     30 import android.graphics.PixelFormat;
     31 import android.graphics.PorterDuff.Mode;
     32 import android.graphics.PorterDuffColorFilter;
     33 import android.graphics.cts.R;
     34 import android.graphics.drawable.Drawable.ConstantState;
     35 import android.graphics.drawable.VectorDrawable;
     36 import androidx.annotation.Nullable;
     37 import android.support.test.InstrumentationRegistry;
     38 import android.support.test.filters.SmallTest;
     39 import android.support.test.runner.AndroidJUnit4;
     40 import android.util.AttributeSet;
     41 import android.util.Log;
     42 import android.util.Xml;
     43 
     44 import org.junit.Before;
     45 import org.junit.Test;
     46 import org.junit.runner.RunWith;
     47 import org.xmlpull.v1.XmlPullParser;
     48 import org.xmlpull.v1.XmlPullParserException;
     49 
     50 import java.io.File;
     51 import java.io.FileOutputStream;
     52 import java.io.IOException;
     53 
     54 @SmallTest
     55 @RunWith(AndroidJUnit4.class)
     56 public class VectorDrawableTest {
     57     private static final String LOGTAG = "VectorDrawableTest";
     58 
     59     // Separate the test assets into different groups such that we could isolate the issue faster.
     60     // Some new APIs or bug fixes only exist in particular os version, such that we name the tests
     61     // and associated assets with OS code name L, M, N etc...
     62     private static final int[] BASIC_ICON_RES_IDS = new int[]{
     63             R.drawable.vector_icon_create,
     64             R.drawable.vector_icon_delete,
     65             R.drawable.vector_icon_heart,
     66             R.drawable.vector_icon_schedule,
     67             R.drawable.vector_icon_settings,
     68             R.drawable.vector_icon_random_path_1,
     69             R.drawable.vector_icon_random_path_2,
     70             R.drawable.vector_icon_repeated_cq,
     71             R.drawable.vector_icon_repeated_st,
     72             R.drawable.vector_icon_repeated_a_1,
     73             R.drawable.vector_icon_repeated_a_2,
     74             R.drawable.vector_icon_clip_path_1,
     75     };
     76 
     77     private static final int[] BASIC_GOLDEN_IMAGES = new int[] {
     78             R.drawable.vector_icon_create_golden,
     79             R.drawable.vector_icon_delete_golden,
     80             R.drawable.vector_icon_heart_golden,
     81             R.drawable.vector_icon_schedule_golden,
     82             R.drawable.vector_icon_settings_golden,
     83             R.drawable.vector_icon_random_path_1_golden,
     84             R.drawable.vector_icon_random_path_2_golden,
     85             R.drawable.vector_icon_repeated_cq_golden,
     86             R.drawable.vector_icon_repeated_st_golden,
     87             R.drawable.vector_icon_repeated_a_1_golden,
     88             R.drawable.vector_icon_repeated_a_2_golden,
     89             R.drawable.vector_icon_clip_path_1_golden,
     90     };
     91 
     92     private static final int[] L_M_ICON_RES_IDS = new int[] {
     93             R.drawable.vector_icon_transformation_1,
     94             R.drawable.vector_icon_transformation_2,
     95             R.drawable.vector_icon_transformation_3,
     96             R.drawable.vector_icon_transformation_4,
     97             R.drawable.vector_icon_transformation_5,
     98             R.drawable.vector_icon_transformation_6,
     99             R.drawable.vector_icon_render_order_1,
    100             R.drawable.vector_icon_render_order_2,
    101             R.drawable.vector_icon_stroke_1,
    102             R.drawable.vector_icon_stroke_2,
    103             R.drawable.vector_icon_stroke_3,
    104             R.drawable.vector_icon_scale_1,
    105             R.drawable.vector_icon_scale_2,
    106             R.drawable.vector_icon_scale_3,
    107             R.drawable.vector_icon_group_clip,
    108     };
    109 
    110     private static final int[] L_M_GOLDEN_IMAGES = new int[] {
    111             R.drawable.vector_icon_transformation_1_golden,
    112             R.drawable.vector_icon_transformation_2_golden,
    113             R.drawable.vector_icon_transformation_3_golden,
    114             R.drawable.vector_icon_transformation_4_golden,
    115             R.drawable.vector_icon_transformation_5_golden,
    116             R.drawable.vector_icon_transformation_6_golden,
    117             R.drawable.vector_icon_render_order_1_golden,
    118             R.drawable.vector_icon_render_order_2_golden,
    119             R.drawable.vector_icon_stroke_1_golden,
    120             R.drawable.vector_icon_stroke_2_golden,
    121             R.drawable.vector_icon_stroke_3_golden,
    122             R.drawable.vector_icon_scale_1_golden,
    123             R.drawable.vector_icon_scale_2_golden,
    124             R.drawable.vector_icon_scale_3_golden,
    125             R.drawable.vector_icon_group_clip_golden,
    126     };
    127 
    128     private static final int[] N_ICON_RES_IDS = new int[] {
    129             R.drawable.vector_icon_implicit_lineto,
    130             R.drawable.vector_icon_arcto,
    131             R.drawable.vector_icon_filltype_nonzero,
    132             R.drawable.vector_icon_filltype_evenodd,
    133     };
    134 
    135     private static final int[] N_GOLDEN_IMAGES = new int[] {
    136             R.drawable.vector_icon_implicit_lineto_golden,
    137             R.drawable.vector_icon_arcto_golden,
    138             R.drawable.vector_icon_filltype_nonzero_golden,
    139             R.drawable.vector_icon_filltype_evenodd_golden,
    140     };
    141 
    142     private static final int[] GRADIENT_ICON_RES_IDS = new int[] {
    143             R.drawable.vector_icon_gradient_1,
    144             R.drawable.vector_icon_gradient_2,
    145             R.drawable.vector_icon_gradient_3,
    146             R.drawable.vector_icon_gradient_1_clamp,
    147             R.drawable.vector_icon_gradient_2_repeat,
    148             R.drawable.vector_icon_gradient_3_mirror,
    149     };
    150 
    151     private static final int[] GRADIENT_GOLDEN_IMAGES = new int[] {
    152             R.drawable.vector_icon_gradient_1_golden,
    153             R.drawable.vector_icon_gradient_2_golden,
    154             R.drawable.vector_icon_gradient_3_golden,
    155             R.drawable.vector_icon_gradient_1_clamp_golden,
    156             R.drawable.vector_icon_gradient_2_repeat_golden,
    157             R.drawable.vector_icon_gradient_3_mirror_golden,
    158     };
    159 
    160     private static final int[] STATEFUL_RES_IDS = new int[] {
    161             // All these icons are using the same color state list, make sure it works for either
    162             // the same drawable ID or different ID but same content.
    163             R.drawable.vector_icon_state_list,
    164             R.drawable.vector_icon_state_list,
    165             R.drawable.vector_icon_state_list_2,
    166     };
    167 
    168     private static final int[][] STATEFUL_GOLDEN_IMAGES = new int[][] {
    169             {
    170                     R.drawable.vector_icon_state_list_golden,
    171                     R.drawable.vector_icon_state_list_golden,
    172                     R.drawable.vector_icon_state_list_2_golden
    173             },
    174             {
    175                     R.drawable.vector_icon_state_list_pressed_golden,
    176                     R.drawable.vector_icon_state_list_pressed_golden,
    177                     R.drawable.vector_icon_state_list_2_pressed_golden
    178             }
    179     };
    180 
    181     private static final int[][] STATEFUL_STATE_SETS = new int[][] {
    182             {},
    183             { android.R.attr.state_pressed }
    184     };
    185 
    186     private static final int IMAGE_WIDTH = 64;
    187     private static final int IMAGE_HEIGHT = 64;
    188 
    189     private static final boolean DBG_DUMP_PNG = false;
    190 
    191     private Resources mResources;
    192     private Bitmap mBitmap;
    193     private Canvas mCanvas;
    194     private Context mContext;
    195 
    196     @Before
    197     public void setup() {
    198         final int width = IMAGE_WIDTH;
    199         final int height = IMAGE_HEIGHT;
    200 
    201         mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    202         mCanvas = new Canvas(mBitmap);
    203         mContext = InstrumentationRegistry.getTargetContext();
    204         mResources = mContext.getResources();
    205     }
    206 
    207     @Test
    208     public void testBasicVectorDrawables() throws XmlPullParserException, IOException {
    209         verifyVectorDrawables(BASIC_ICON_RES_IDS, BASIC_GOLDEN_IMAGES, null);
    210     }
    211 
    212     @Test
    213     public void testLMVectorDrawables() throws XmlPullParserException, IOException {
    214         verifyVectorDrawables(L_M_ICON_RES_IDS, L_M_GOLDEN_IMAGES, null);
    215     }
    216 
    217     @Test
    218     public void testNVectorDrawables() throws XmlPullParserException, IOException {
    219         verifyVectorDrawables(N_ICON_RES_IDS, N_GOLDEN_IMAGES, null);
    220     }
    221 
    222     @Test
    223     public void testVectorDrawableGradient() throws XmlPullParserException, IOException {
    224         verifyVectorDrawables(GRADIENT_ICON_RES_IDS, GRADIENT_GOLDEN_IMAGES, null);
    225     }
    226 
    227     @Test
    228     public void testColorStateList() throws XmlPullParserException, IOException {
    229         for (int i = 0; i < STATEFUL_STATE_SETS.length; i++) {
    230             verifyVectorDrawables(
    231                     STATEFUL_RES_IDS, STATEFUL_GOLDEN_IMAGES[i], STATEFUL_STATE_SETS[i]);
    232         }
    233     }
    234 
    235     private void verifyVectorDrawables(int[] resIds, int[] goldenImages, int[] stateSet)
    236             throws XmlPullParserException, IOException {
    237         for (int i = 0; i < resIds.length; i++) {
    238             VectorDrawable vectorDrawable = new VectorDrawable();
    239             vectorDrawable.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
    240 
    241             // Setup VectorDrawable from xml file and draw into the bitmap.
    242             XmlPullParser parser = mResources.getXml(resIds[i]);
    243             AttributeSet attrs = Xml.asAttributeSet(parser);
    244 
    245             int type;
    246             while ((type=parser.next()) != XmlPullParser.START_TAG
    247                     && type != XmlPullParser.END_DOCUMENT) {
    248                 // Empty loop
    249             }
    250 
    251             if (type != XmlPullParser.START_TAG) {
    252                 throw new XmlPullParserException("No start tag found");
    253             }
    254 
    255             Theme theme = mResources.newTheme();
    256             theme.applyStyle(R.style.Theme_ThemedDrawableTest, true);
    257             vectorDrawable.inflate(mResources, parser, attrs, theme);
    258 
    259             if (stateSet != null) {
    260                 vectorDrawable.setState(stateSet);
    261             }
    262 
    263             mBitmap.eraseColor(0);
    264             vectorDrawable.draw(mCanvas);
    265 
    266             if (DBG_DUMP_PNG) {
    267                 String stateSetTitle = getTitleForStateSet(stateSet);
    268                 DrawableTestUtils.saveAutoNamedVectorDrawableIntoPNG(mContext, mBitmap, resIds[i],
    269                         stateSetTitle);
    270             } else {
    271                 // Start to compare
    272                 Bitmap golden = BitmapFactory.decodeResource(mResources, goldenImages[i]);
    273                 DrawableTestUtils.compareImages(mResources.getString(resIds[i]), mBitmap, golden,
    274                         DrawableTestUtils.PIXEL_ERROR_THRESHOLD,
    275                         DrawableTestUtils.PIXEL_ERROR_COUNT_THRESHOLD,
    276                         DrawableTestUtils.PIXEL_ERROR_TOLERANCE);
    277 
    278             }
    279         }
    280     }
    281 
    282     /**
    283      * Generates an underline-delimited list of states in a given state set.
    284      * <p>
    285      * For example, the array {@code {R.attr.state_pressed}} would return
    286      * {@code "pressed"}.
    287      *
    288      * @param stateSet a state set
    289      * @return a string representing the state set, or {@code null} if the state set is empty or
    290      * {@code null}
    291      */
    292     private @Nullable String getTitleForStateSet(int[] stateSet) {
    293         if (stateSet == null || stateSet.length == 0) {
    294             return null;
    295         }
    296 
    297         final StringBuilder builder = new StringBuilder();
    298         for (int i = 0; i < stateSet.length; i++) {
    299             final String state = mResources.getResourceName(stateSet[i]);
    300             final int stateIndex = state.indexOf("state_");
    301             if (stateIndex >= 0) {
    302                 builder.append(state.substring(stateIndex + 6));
    303             } else {
    304                 builder.append(stateSet[i]);
    305             }
    306         }
    307 
    308         return builder.toString();
    309     }
    310 
    311     @Test
    312     public void testGetChangingConfigurations() {
    313         VectorDrawable vectorDrawable = new VectorDrawable();
    314         ConstantState constantState = vectorDrawable.getConstantState();
    315 
    316         // default
    317         assertEquals(0, constantState.getChangingConfigurations());
    318         assertEquals(0, vectorDrawable.getChangingConfigurations());
    319 
    320         // change the drawable's configuration does not affect the state's configuration
    321         vectorDrawable.setChangingConfigurations(0xff);
    322         assertEquals(0xff, vectorDrawable.getChangingConfigurations());
    323         assertEquals(0, constantState.getChangingConfigurations());
    324 
    325         // the state's configuration get refreshed
    326         constantState = vectorDrawable.getConstantState();
    327         assertEquals(0xff,  constantState.getChangingConfigurations());
    328 
    329         // set a new configuration to drawable
    330         vectorDrawable.setChangingConfigurations(0xff00);
    331         assertEquals(0xff,  constantState.getChangingConfigurations());
    332         assertEquals(0xffff,  vectorDrawable.getChangingConfigurations());
    333     }
    334 
    335     @Test
    336     public void testGetConstantState() {
    337         VectorDrawable vectorDrawable = new VectorDrawable();
    338         ConstantState constantState = vectorDrawable.getConstantState();
    339         assertNotNull(constantState);
    340         assertEquals(0, constantState.getChangingConfigurations());
    341 
    342         vectorDrawable.setChangingConfigurations(1);
    343         constantState = vectorDrawable.getConstantState();
    344         assertNotNull(constantState);
    345         assertEquals(1, constantState.getChangingConfigurations());
    346     }
    347 
    348     @Test
    349     public void testMutate() {
    350         // d1 and d2 will be mutated, while d3 will not.
    351         VectorDrawable d1 = (VectorDrawable) mResources.getDrawable(R.drawable.vector_icon_create);
    352         VectorDrawable d2 = (VectorDrawable) mResources.getDrawable(R.drawable.vector_icon_create);
    353         VectorDrawable d3 = (VectorDrawable) mResources.getDrawable(R.drawable.vector_icon_create);
    354         int restoreAlpha = d1.getAlpha();
    355 
    356         try {
    357             // verify bad behavior - modify before mutate pollutes other drawables
    358             d1.setAlpha(0x80);
    359             assertEquals(0x80, d1.getAlpha());
    360             assertEquals(0x80, d2.getAlpha());
    361             assertEquals(0x80, d3.getAlpha());
    362 
    363             d1.mutate();
    364             d1.setAlpha(0x40);
    365             assertEquals(0x40, d1.getAlpha());
    366             assertEquals(0x80, d2.getAlpha());
    367             assertEquals(0x80, d3.getAlpha());
    368 
    369             d2.setAlpha(0x00);
    370             d2.mutate();
    371             // Test that after mutating, the alpha value is copied over.
    372             assertEquals(0x00, d2.getAlpha());
    373 
    374             d2.setAlpha(0x20);
    375             assertEquals(0x40, d1.getAlpha());
    376             assertEquals(0x20, d2.getAlpha());
    377             assertEquals(0x00, d3.getAlpha());
    378         } finally {
    379             mResources.getDrawable(R.drawable.vector_icon_create).setAlpha(restoreAlpha);
    380         }
    381     }
    382 
    383     @Test
    384     public void testColorFilter() {
    385         PorterDuffColorFilter filter = new PorterDuffColorFilter(Color.RED, Mode.SRC_IN);
    386         VectorDrawable vectorDrawable = new VectorDrawable();
    387         vectorDrawable.setColorFilter(filter);
    388 
    389         assertEquals(filter, vectorDrawable.getColorFilter());
    390     }
    391 
    392     @Test
    393     public void testGetOpacity () throws XmlPullParserException, IOException {
    394         VectorDrawable vectorDrawable = new VectorDrawable();
    395 
    396         assertEquals("Default alpha should be 255", 255, vectorDrawable.getAlpha());
    397         assertEquals("Default opacity should be TRANSLUCENT", PixelFormat.TRANSLUCENT,
    398                 vectorDrawable.getOpacity());
    399 
    400         vectorDrawable.setAlpha(0);
    401         assertEquals("Alpha should be 0 now", 0, vectorDrawable.getAlpha());
    402         assertEquals("Opacity should be TRANSPARENT now", PixelFormat.TRANSPARENT,
    403                 vectorDrawable.getOpacity());
    404     }
    405 
    406     @Test
    407     public void testPreloadDensity() throws XmlPullParserException, IOException {
    408         final int densityDpi = mResources.getConfiguration().densityDpi;
    409         try {
    410             DrawableTestUtils.setResourcesDensity(mResources, densityDpi);
    411             verifyPreloadDensityInner(mResources, densityDpi);
    412         } finally {
    413             DrawableTestUtils.setResourcesDensity(mResources, densityDpi);
    414         }
    415     }
    416 
    417     @Test
    418     public void testPreloadDensity_tvdpi() throws XmlPullParserException, IOException {
    419         final int densityDpi = mResources.getConfiguration().densityDpi;
    420         try {
    421             DrawableTestUtils.setResourcesDensity(mResources, 213);
    422             verifyPreloadDensityInner(mResources, 213);
    423         } finally {
    424             DrawableTestUtils.setResourcesDensity(mResources, densityDpi);
    425         }
    426     }
    427 
    428     private void verifyPreloadDensityInner(Resources res, int densityDpi)
    429             throws XmlPullParserException, IOException {
    430         // Capture initial state at default density.
    431         final XmlResourceParser parser = DrawableTestUtils.getResourceParser(
    432                 res, R.drawable.vector_density);
    433         final VectorDrawable preloadedDrawable = new VectorDrawable();
    434         preloadedDrawable.inflate(mResources, parser, Xml.asAttributeSet(parser));
    435         final ConstantState preloadedConstantState = preloadedDrawable.getConstantState();
    436         final int origWidth = preloadedDrawable.getIntrinsicWidth();
    437 
    438         // Set density to half of original. Unlike offsets, which are
    439         // truncated, dimensions are rounded to the nearest pixel.
    440         DrawableTestUtils.setResourcesDensity(res, densityDpi / 2);
    441         final VectorDrawable halfDrawable =
    442                 (VectorDrawable) preloadedConstantState.newDrawable(res);
    443         // NOTE: densityDpi may not be an even number, so account for *actual* scaling in asserts
    444         final float approxHalf = (float)(densityDpi / 2) / densityDpi;
    445         assertEquals(Math.round(origWidth * approxHalf), halfDrawable.getIntrinsicWidth());
    446 
    447         // Set density to double original.
    448         DrawableTestUtils.setResourcesDensity(res, densityDpi * 2);
    449         final VectorDrawable doubleDrawable =
    450                 (VectorDrawable) preloadedConstantState.newDrawable(res);
    451         assertEquals(origWidth * 2, doubleDrawable.getIntrinsicWidth());
    452 
    453         // Restore original density.
    454         DrawableTestUtils.setResourcesDensity(res, densityDpi);
    455         final VectorDrawable origDrawable =
    456                 (VectorDrawable) preloadedConstantState.newDrawable();
    457         assertEquals(origWidth, origDrawable.getIntrinsicWidth());
    458 
    459         // Ensure theme density is applied correctly.
    460         final Theme t = res.newTheme();
    461         halfDrawable.applyTheme(t);
    462         assertEquals(origWidth, halfDrawable.getIntrinsicWidth());
    463         doubleDrawable.applyTheme(t);
    464         assertEquals(origWidth, doubleDrawable.getIntrinsicWidth());
    465     }
    466 }
    467