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 junit.framework.Assert.fail; 20 import static junit.framework.TestCase.assertFalse; 21 import static junit.framework.TestCase.assertTrue; 22 23 import static org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertNotNull; 25 26 import android.Manifest; 27 import android.app.Activity; 28 import android.content.res.Resources; 29 import android.graphics.Bitmap; 30 import android.graphics.Canvas; 31 import android.graphics.Color; 32 import android.graphics.Insets; 33 import android.graphics.PixelFormat; 34 import android.graphics.PorterDuff; 35 import android.graphics.PorterDuffColorFilter; 36 import android.graphics.cts.R; 37 import android.graphics.drawable.AnimatedVectorDrawable; 38 import android.graphics.drawable.Drawable.ConstantState; 39 import android.util.AttributeSet; 40 import android.util.Xml; 41 import android.widget.ImageView; 42 43 import androidx.test.filters.FlakyTest; 44 import androidx.test.filters.LargeTest; 45 import androidx.test.filters.SmallTest; 46 import androidx.test.platform.app.InstrumentationRegistry; 47 import androidx.test.rule.ActivityTestRule; 48 import androidx.test.runner.AndroidJUnit4; 49 50 import com.android.compatibility.common.util.AdoptShellPermissionsRule; 51 52 import org.junit.Before; 53 import org.junit.Rule; 54 import org.junit.Test; 55 import org.junit.runner.RunWith; 56 import org.xmlpull.v1.XmlPullParser; 57 import org.xmlpull.v1.XmlPullParserException; 58 59 @LargeTest 60 @RunWith(AndroidJUnit4.class) 61 public class AnimatedVectorDrawableTest { 62 private static final int IMAGE_WIDTH = 64; 63 private static final int IMAGE_HEIGHT = 64; 64 private static final long MAX_TIMEOUT_MS = 1000; 65 private static final int MS_TO_NS = 1000000; 66 67 @Rule(order = 0) 68 public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule( 69 InstrumentationRegistry.getInstrumentation().getUiAutomation(), 70 Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); 71 72 @Rule(order = 1) 73 public ActivityTestRule<DrawableStubActivity> mActivityRule = 74 new ActivityTestRule<DrawableStubActivity>(DrawableStubActivity.class); 75 private Activity mActivity; 76 private Resources mResources; 77 private static final boolean DBG_DUMP_PNG = false; 78 private final int mResId = R.drawable.animation_vector_drawable_grouping_1; 79 private final int mLayoutId = R.layout.animated_vector_drawable_source; 80 private final int mImageViewId = R.id.avd_view; 81 82 @Before setup()83 public void setup() { 84 mActivity = mActivityRule.getActivity(); 85 mResources = mActivity.getResources(); 86 } 87 88 @SmallTest 89 @Test testInflate()90 public void testInflate() throws Exception { 91 // Setup AnimatedVectorDrawable from xml file 92 XmlPullParser parser = mResources.getXml(mResId); 93 AttributeSet attrs = Xml.asAttributeSet(parser); 94 95 int type; 96 while ((type=parser.next()) != XmlPullParser.START_TAG && 97 type != XmlPullParser.END_DOCUMENT) { 98 // Empty loop 99 } 100 101 if (type != XmlPullParser.START_TAG) { 102 throw new XmlPullParserException("No start tag found"); 103 } 104 Bitmap bitmap = Bitmap.createBitmap(IMAGE_WIDTH, IMAGE_HEIGHT, Bitmap.Config.ARGB_8888); 105 Canvas canvas = new Canvas(bitmap); 106 AnimatedVectorDrawable drawable = new AnimatedVectorDrawable(); 107 drawable.inflate(mResources, parser, attrs); 108 drawable.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT); 109 bitmap.eraseColor(0); 110 drawable.draw(canvas); 111 int sunColor = bitmap.getPixel(IMAGE_WIDTH / 2, IMAGE_HEIGHT / 2); 112 int earthColor = bitmap.getPixel(IMAGE_WIDTH * 3 / 4 + 2, IMAGE_HEIGHT / 2); 113 assertTrue(sunColor == 0xFFFF8000); 114 assertTrue(earthColor == 0xFF5656EA); 115 116 if (DBG_DUMP_PNG) { 117 DrawableTestUtils.saveAutoNamedVectorDrawableIntoPNG(mActivity, bitmap, mResId, null); 118 } 119 } 120 121 @SmallTest 122 @Test testGetOpticalInsets()123 public void testGetOpticalInsets() throws Exception { 124 XmlPullParser parser = mResources.getXml(mResId); 125 AttributeSet attrs = Xml.asAttributeSet(parser); 126 AnimatedVectorDrawable drawable = new AnimatedVectorDrawable(); 127 drawable.inflate(mResources, parser, attrs); 128 129 assertEquals(Insets.of(10, 20, 30, 40), drawable.getOpticalInsets()); 130 } 131 132 @Test testGetChangingConfigurations()133 public void testGetChangingConfigurations() { 134 AnimatedVectorDrawable avd = new AnimatedVectorDrawable(); 135 ConstantState constantState = avd.getConstantState(); 136 137 // default 138 assertEquals(0, constantState.getChangingConfigurations()); 139 assertEquals(0, avd.getChangingConfigurations()); 140 141 // change the drawable's configuration does not affect the state's configuration 142 avd.setChangingConfigurations(0xff); 143 assertEquals(0xff, avd.getChangingConfigurations()); 144 assertEquals(0, constantState.getChangingConfigurations()); 145 146 // the state's configuration get refreshed 147 constantState = avd.getConstantState(); 148 assertEquals(0xff, constantState.getChangingConfigurations()); 149 150 // set a new configuration to drawable 151 avd.setChangingConfigurations(0xff00); 152 assertEquals(0xff, constantState.getChangingConfigurations()); 153 assertEquals(0xffff, avd.getChangingConfigurations()); 154 } 155 156 @Test testGetConstantState()157 public void testGetConstantState() { 158 AnimatedVectorDrawable AnimatedVectorDrawable = new AnimatedVectorDrawable(); 159 ConstantState constantState = AnimatedVectorDrawable.getConstantState(); 160 assertNotNull(constantState); 161 assertEquals(0, constantState.getChangingConfigurations()); 162 163 AnimatedVectorDrawable.setChangingConfigurations(1); 164 constantState = AnimatedVectorDrawable.getConstantState(); 165 assertNotNull(constantState); 166 assertEquals(1, constantState.getChangingConfigurations()); 167 } 168 169 @SmallTest 170 @Test testMutate()171 public void testMutate() { 172 AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId); 173 AnimatedVectorDrawable d2 = (AnimatedVectorDrawable) mResources.getDrawable(mResId); 174 AnimatedVectorDrawable d3 = (AnimatedVectorDrawable) mResources.getDrawable(mResId); 175 int restoreAlpha = d1.getAlpha(); 176 177 try { 178 int originalAlpha = d2.getAlpha(); 179 int newAlpha = (originalAlpha + 1) % 255; 180 181 // AVD is different than VectorDrawable. Every instance of it is a deep copy 182 // of the VectorDrawable. 183 // So every setAlpha operation will happen only to that specific object. 184 d1.setAlpha(newAlpha); 185 assertEquals(newAlpha, d1.getAlpha()); 186 assertEquals(originalAlpha, d2.getAlpha()); 187 assertEquals(originalAlpha, d3.getAlpha()); 188 189 d1.mutate(); 190 d1.setAlpha(0x40); 191 assertEquals(0x40, d1.getAlpha()); 192 assertEquals(originalAlpha, d2.getAlpha()); 193 assertEquals(originalAlpha, d3.getAlpha()); 194 195 d2.setAlpha(0x20); 196 assertEquals(0x40, d1.getAlpha()); 197 assertEquals(0x20, d2.getAlpha()); 198 assertEquals(originalAlpha, d3.getAlpha()); 199 } finally { 200 mResources.getDrawable(mResId).setAlpha(restoreAlpha); 201 } 202 } 203 204 @SmallTest 205 @Test testGetOpacity()206 public void testGetOpacity() { 207 AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId); 208 assertEquals("Default is translucent", PixelFormat.TRANSLUCENT, d1.getOpacity()); 209 d1.setAlpha(0); 210 assertEquals("Still translucent", PixelFormat.TRANSLUCENT, d1.getOpacity()); 211 } 212 213 @SmallTest 214 @Test testColorFilter()215 public void testColorFilter() { 216 PorterDuffColorFilter filter = new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN); 217 AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId); 218 d1.setColorFilter(filter); 219 220 assertEquals(filter, d1.getColorFilter()); 221 } 222 223 @Test testReset()224 public void testReset() throws Throwable { 225 final Animatable2Callback callback = new Animatable2Callback(); 226 final AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId); 227 // The AVD has a duration as 100ms. 228 mActivityRule.runOnUiThread(() -> { 229 d1.registerAnimationCallback(callback); 230 d1.start(); 231 d1.reset(); 232 }); 233 waitForAVDStop(callback, MAX_TIMEOUT_MS); 234 assertFalse(d1.isRunning()); 235 236 } 237 238 @Test testStop()239 public void testStop() throws Throwable { 240 final Animatable2Callback callback = new Animatable2Callback(); 241 final AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId); 242 // The AVD has a duration as 100ms. 243 mActivityRule.runOnUiThread(() -> { 244 d1.registerAnimationCallback(callback); 245 d1.start(); 246 d1.stop(); 247 }); 248 waitForAVDStop(callback, MAX_TIMEOUT_MS); 249 assertFalse(d1.isRunning()); 250 } 251 252 @Test 253 @FlakyTest (bugId = 72737527) testAddCallbackBeforeStart()254 public void testAddCallbackBeforeStart() throws Throwable { 255 final Animatable2Callback callback = new Animatable2Callback(); 256 // The AVD has a duration as 100ms. 257 mActivityRule.runOnUiThread(() -> { 258 mActivity.setContentView(mLayoutId); 259 ImageView imageView = (ImageView) mActivity.findViewById(mImageViewId); 260 AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) imageView.getDrawable(); 261 d1.registerAnimationCallback(callback); 262 d1.start(); 263 }); 264 callback.waitForStart(); 265 waitForAVDStop(callback, MAX_TIMEOUT_MS); 266 callback.assertStarted(true); 267 callback.assertEnded(true); 268 } 269 270 @Test 271 @FlakyTest (bugId = 72737527) testAddCallbackAfterTrigger()272 public void testAddCallbackAfterTrigger() throws Throwable { 273 final Animatable2Callback callback = new Animatable2Callback(); 274 // The AVD has a duration as 100ms. 275 mActivityRule.runOnUiThread(() -> { 276 mActivity.setContentView(mLayoutId); 277 ImageView imageView = (ImageView) mActivity.findViewById(mImageViewId); 278 AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) imageView.getDrawable(); 279 // This reset call can enforce the AnimatorSet is setup properly in AVD, when 280 // running on UI thread. 281 d1.reset(); 282 d1.registerAnimationCallback(callback); 283 d1.start(); 284 }); 285 callback.waitForStart(); 286 waitForAVDStop(callback, MAX_TIMEOUT_MS); 287 288 callback.assertStarted(true); 289 callback.assertEnded(true); 290 } 291 292 @Test 293 @FlakyTest (bugId = 72737527) testAddCallbackAfterStart()294 public void testAddCallbackAfterStart() throws Throwable { 295 final Animatable2Callback callback = new Animatable2Callback(); 296 // The AVD has a duration as 100ms. 297 mActivityRule.runOnUiThread(() -> { 298 mActivity.setContentView(mLayoutId); 299 ImageView imageView = (ImageView) mActivity.findViewById(mImageViewId); 300 AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) imageView.getDrawable(); 301 d1.start(); 302 d1.registerAnimationCallback(callback); 303 }); 304 callback.waitForStart(); 305 306 waitForAVDStop(callback, MAX_TIMEOUT_MS); 307 // Whether or not the callback.start is true could vary when running on Render Thread. 308 // Therefore, we don't make assertion here. The most useful flag is the callback.mEnded. 309 callback.assertEnded(true); 310 callback.assertAVDRuntime(0, 400 * MS_TO_NS); // 4 times of the duration of the AVD. 311 } 312 313 @Test testRemoveCallback()314 public void testRemoveCallback() throws Throwable { 315 final Animatable2Callback callback = new Animatable2Callback(); 316 // The AVD has a duration as 100ms. 317 mActivityRule.runOnUiThread(() -> { 318 mActivity.setContentView(mLayoutId); 319 ImageView imageView = (ImageView) mActivity.findViewById(mImageViewId); 320 AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) imageView.getDrawable(); 321 d1.registerAnimationCallback(callback); 322 assertTrue(d1.unregisterAnimationCallback(callback)); 323 d1.start(); 324 }); 325 callback.waitForStart(); 326 327 waitForAVDStop(callback, MAX_TIMEOUT_MS); 328 callback.assertStarted(false); 329 callback.assertEnded(false); 330 } 331 332 @Test 333 @FlakyTest (bugId = 72737527) testClearCallback()334 public void testClearCallback() throws Throwable { 335 final Animatable2Callback callback = new Animatable2Callback(); 336 337 // The AVD has a duration as 100ms. 338 mActivityRule.runOnUiThread(() -> { 339 mActivity.setContentView(mLayoutId); 340 ImageView imageView = (ImageView) mActivity.findViewById(mImageViewId); 341 AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) imageView.getDrawable(); 342 d1.registerAnimationCallback(callback); 343 d1.clearAnimationCallbacks(); 344 d1.start(); 345 }); 346 callback.waitForStart(); 347 348 waitForAVDStop(callback, MAX_TIMEOUT_MS); 349 callback.assertStarted(false); 350 callback.assertEnded(false); 351 } 352 353 // The time out is expected when the listener is removed successfully. 354 // Such that we don't get the end event. waitForAVDStop(Animatable2Callback callback, long timeout)355 static void waitForAVDStop(Animatable2Callback callback, long timeout) { 356 try { 357 callback.waitForEnd(timeout); 358 } catch (InterruptedException e) { 359 e.printStackTrace(); 360 fail("We should not see the AVD run this long time!"); 361 } 362 } 363 } 364