AnimatedVectorDrawableTest.java revision fad2335f169d36b7b6f2c0ec8ddfe6c0094c2072
1/*
2 * Copyright (C) 2015 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
17package android.support.graphics.drawable.tests;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.drawable.Drawable.ConstantState;
24import android.support.annotation.DrawableRes;
25import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
26import android.support.graphics.drawable.animated.test.R;
27import android.support.test.InstrumentationRegistry;
28import android.support.test.rule.ActivityTestRule;
29import android.support.test.runner.AndroidJUnit4;
30import android.test.suitebuilder.annotation.MediumTest;
31import android.util.AttributeSet;
32import android.util.Log;
33import android.util.Xml;
34import android.view.View;
35import android.widget.ImageButton;
36import org.junit.Before;
37import org.junit.Rule;
38import org.junit.Test;
39import org.junit.runner.RunWith;
40import org.xmlpull.v1.XmlPullParser;
41import org.xmlpull.v1.XmlPullParserException;
42
43import java.io.File;
44import java.io.FileOutputStream;
45import java.io.IOException;
46import java.util.concurrent.CountDownLatch;
47import java.util.concurrent.TimeUnit;
48
49import static org.junit.Assert.*;
50
51@MediumTest
52@RunWith(AndroidJUnit4.class)
53public class AnimatedVectorDrawableTest {
54    @Rule public final ActivityTestRule<DrawableStubActivity> mActivityTestRule;
55    private static final String LOGTAG = AnimatedVectorDrawableTest.class.getSimpleName();
56
57    private static final int IMAGE_WIDTH = 64;
58    private static final int IMAGE_HEIGHT = 64;
59    private static final @DrawableRes int DRAWABLE_RES_ID =
60            R.drawable.animation_vector_drawable_grouping_1;
61
62    private Context mContext;
63    private Resources mResources;
64    private AnimatedVectorDrawableCompat mAnimatedVectorDrawable;
65    private Bitmap mBitmap;
66    private Canvas mCanvas;
67    private static final boolean DBG_DUMP_PNG = false;
68
69    public AnimatedVectorDrawableTest() {
70        mActivityTestRule = new ActivityTestRule<>(DrawableStubActivity.class);
71    }
72
73    @Before
74    public void setup() throws Exception {
75        mBitmap = Bitmap.createBitmap(IMAGE_WIDTH, IMAGE_HEIGHT, Bitmap.Config.ARGB_8888);
76        mCanvas = new Canvas(mBitmap);
77        mContext = mActivityTestRule.getActivity();
78        mResources = mContext.getResources();
79
80        mAnimatedVectorDrawable = AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
81    }
82
83    // This is only for debugging or golden image (re)generation purpose.
84    private void saveVectorDrawableIntoPNG(Bitmap bitmap, int resId) throws IOException {
85        // Save the image to the disk.
86        FileOutputStream out = null;
87        try {
88            String outputFolder = "/sdcard/temp/";
89            File folder = new File(outputFolder);
90            if (!folder.exists()) {
91                folder.mkdir();
92            }
93            String originalFilePath = mResources.getString(resId);
94            File originalFile = new File(originalFilePath);
95            String fileFullName = originalFile.getName();
96            String fileTitle = fileFullName.substring(0, fileFullName.lastIndexOf("."));
97            String outputFilename = outputFolder + fileTitle + "_golden.png";
98            File outputFile = new File(outputFilename);
99            if (!outputFile.exists()) {
100                outputFile.createNewFile();
101            }
102
103            out = new FileOutputStream(outputFile, false);
104            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
105            Log.v(LOGTAG, "Write test No." + outputFilename + " to file successfully.");
106        } catch (Exception e) {
107            e.printStackTrace();
108        } finally {
109            if (out != null) {
110                out.close();
111            }
112        }
113    }
114
115    @Test
116    public void testInflate() throws Exception {
117        // Setup AnimatedVectorDrawableCompat from xml file
118        XmlPullParser parser = mResources.getXml(DRAWABLE_RES_ID);
119        AttributeSet attrs = Xml.asAttributeSet(parser);
120
121        int type;
122        while ((type = parser.next()) != XmlPullParser.START_TAG &&
123                type != XmlPullParser.END_DOCUMENT) {
124            // Empty loop
125        }
126
127        if (type != XmlPullParser.START_TAG) {
128            throw new XmlPullParserException("No start tag found");
129        }
130
131        mAnimatedVectorDrawable.inflate(mResources, parser, attrs);
132        mAnimatedVectorDrawable.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
133        mBitmap.eraseColor(0);
134        mAnimatedVectorDrawable.draw(mCanvas);
135        int sunColor = mBitmap.getPixel(IMAGE_WIDTH / 2, IMAGE_HEIGHT / 2);
136        int earthColor = mBitmap.getPixel(IMAGE_WIDTH * 3 / 4 + 2, IMAGE_HEIGHT / 2);
137        assertTrue(sunColor == 0xFFFF8000);
138        assertTrue(earthColor == 0xFF5656EA);
139
140        if (DBG_DUMP_PNG) {
141            saveVectorDrawableIntoPNG(mBitmap, DRAWABLE_RES_ID);
142        }
143    }
144
145    @Test
146    public void testGetChangingConfigurations() {
147        AnimatedVectorDrawableCompat d1 = AnimatedVectorDrawableCompat.create(mContext,
148                R.drawable.animated_color_fill_copy);
149        ConstantState constantState = d1.getConstantState();
150
151        if (constantState != null) {
152            // default
153            assertEquals(0, constantState.getChangingConfigurations());
154            assertEquals(0, d1.getChangingConfigurations());
155
156            // change the drawable's configuration does not affect the state's configuration
157            d1.setChangingConfigurations(0xff);
158            assertEquals(0xff, d1.getChangingConfigurations());
159            assertEquals(0, constantState.getChangingConfigurations());
160
161            // the state's configuration get refreshed
162            constantState = d1.getConstantState();
163            assertEquals(0xff, constantState.getChangingConfigurations());
164
165            // set a new configuration to drawable
166            d1.setChangingConfigurations(0xff00);
167            assertEquals(0xff, constantState.getChangingConfigurations());
168            assertEquals(0xffff, d1.getChangingConfigurations());
169        }
170    }
171
172    @Test
173    public void testGetConstantState() {
174        ConstantState constantState = mAnimatedVectorDrawable.getConstantState();
175        if (constantState != null) {
176            assertEquals(0, constantState.getChangingConfigurations());
177
178            mAnimatedVectorDrawable.setChangingConfigurations(1);
179            constantState = mAnimatedVectorDrawable.getConstantState();
180            assertNotNull(constantState);
181            assertEquals(1, constantState.getChangingConfigurations());
182        }
183    }
184
185    @Test
186    public void testAnimateColor() throws Throwable {
187        final ImageButton imageButton =
188                (ImageButton) mActivityTestRule.getActivity().findViewById(R.id.imageButton);
189        final int viewW = imageButton.getWidth();
190        final int viewH = imageButton.getHeight();
191        int pixelX = viewW / 2;
192        int pixelY = viewH / 2;
193        final int numTests = 5;
194        final Bitmap bitmap = Bitmap.createBitmap(imageButton.getWidth(), imageButton.getHeight(),
195                Bitmap.Config.ARGB_8888);
196        final Canvas c = new Canvas(bitmap);
197        CountDownLatch latch = new CountDownLatch(numTests);
198
199        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
200            @Override
201            public void run() {
202                AnimatedVectorDrawableCompat avd = AnimatedVectorDrawableCompat.create(mContext,
203                        R.drawable.animated_color_fill);
204                imageButton.setBackgroundDrawable(avd);
205                avd.start();
206            }
207        });
208        // Check the view several times during the animation to verify that it only
209        // has red color in it
210        for (int i = 0; i < numTests; ++i) {
211            Thread.sleep(100);
212            // check fill
213            verifyRedOnly(pixelX, pixelY, imageButton, bitmap, c, latch);
214            // check stroke
215            verifyRedOnly(1, 1, imageButton, bitmap, c, latch);
216        }
217        latch.await(1000, TimeUnit.MILLISECONDS);
218    }
219
220    /**
221     * Utility method to verify that the pixel at the given location has only red values.
222     */
223    private void verifyRedOnly(final int pixelX, final int pixelY, final View button,
224            final Bitmap bitmap, final Canvas canvas, final CountDownLatch latch) throws Throwable {
225        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
226            @Override
227            public void run() {
228                button.draw(canvas);
229                int pixel = bitmap.getPixel(pixelX, pixelY);
230                int blue = pixel & 0xff;
231                int green = pixel & 0xff00 >> 8;
232                assertEquals("Blue channel not zero", 0, blue);
233                assertEquals("Green channel not zero", 0, green);
234                latch.countDown();
235            }
236        });
237    }
238
239    @Test
240    public void testMutate() {
241        AnimatedVectorDrawableCompat d1 =
242                AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
243        AnimatedVectorDrawableCompat d2 =
244                AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
245        AnimatedVectorDrawableCompat d3 =
246                AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
247
248        if (d1.getConstantState() != null) {
249            int originalAlpha = d2.getAlpha();
250            int newAlpha = (originalAlpha + 1) % 255;
251
252            // AVD is different than VectorDrawable. Every instance of it is a deep copy
253            // of the VectorDrawable.
254            // So every setAlpha operation will happen only to that specific object.
255            d1.setAlpha(newAlpha);
256            assertEquals(newAlpha, d1.getAlpha());
257            assertEquals(originalAlpha, d2.getAlpha());
258            assertEquals(originalAlpha, d3.getAlpha());
259
260            d1.mutate();
261            d1.setAlpha(0x40);
262            assertEquals(0x40, d1.getAlpha());
263            assertEquals(originalAlpha, d2.getAlpha());
264            assertEquals(originalAlpha, d3.getAlpha());
265
266            d2.setAlpha(0x20);
267            assertEquals(0x40, d1.getAlpha());
268            assertEquals(0x20, d2.getAlpha());
269            assertEquals(originalAlpha, d3.getAlpha());
270        }
271    }
272}
273