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