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