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