1 /* 2 * Copyright (C) 2016 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 org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertNull; 23 import static org.junit.Assert.assertSame; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assert.fail; 26 import static org.mockito.Mockito.mock; 27 28 import android.content.Context; 29 import android.content.res.Resources; 30 import android.content.res.XmlResourceParser; 31 import android.graphics.Canvas; 32 import android.graphics.ColorFilter; 33 import android.graphics.cts.R; 34 import android.graphics.drawable.Animatable; 35 import android.graphics.drawable.Animatable2; 36 import android.graphics.drawable.AnimatedStateListDrawable; 37 import android.graphics.drawable.Drawable; 38 import android.graphics.drawable.DrawableContainer.DrawableContainerState; 39 import android.graphics.drawable.StateListDrawable; 40 import android.util.StateSet; 41 import android.util.Xml; 42 43 import androidx.test.InstrumentationRegistry; 44 import androidx.test.filters.SmallTest; 45 import androidx.test.runner.AndroidJUnit4; 46 47 import org.junit.Before; 48 import org.junit.Test; 49 import org.junit.runner.RunWith; 50 import org.xmlpull.v1.XmlPullParser; 51 import org.xmlpull.v1.XmlPullParserException; 52 53 import java.io.IOException; 54 import java.util.Arrays; 55 import java.util.HashSet; 56 57 @SmallTest 58 @RunWith(AndroidJUnit4.class) 59 public class AnimatedStateListDrawableTest { 60 private static final int[] STATE_EMPTY = new int[] { }; 61 private static final int[] STATE_FOCUSED = new int[] { android.R.attr.state_focused }; 62 63 private Context mContext; 64 private Resources mResources; 65 66 @Before setup()67 public void setup() { 68 mContext = InstrumentationRegistry.getTargetContext(); 69 mResources = mContext.getResources(); 70 } 71 72 @Test testStateListDrawable()73 public void testStateListDrawable() { 74 new AnimatedStateListDrawable(); 75 76 // Check the values set in the constructor 77 assertNotNull(new AnimatedStateListDrawable().getConstantState()); 78 } 79 80 @Test testAddState()81 public void testAddState() { 82 AnimatedStateListDrawable asld = new AnimatedStateListDrawable(); 83 DrawableContainerState cs = (DrawableContainerState) asld.getConstantState(); 84 assertEquals(0, cs.getChildCount()); 85 86 try { 87 asld.addState(StateSet.WILD_CARD, null, R.id.focused); 88 fail("Expected IllegalArgumentException"); 89 } catch (IllegalArgumentException e) { 90 // Expected. 91 } 92 93 Drawable unfocused = mock(Drawable.class); 94 asld.addState(StateSet.WILD_CARD, unfocused, R.id.focused); 95 assertEquals(1, cs.getChildCount()); 96 97 Drawable focused = mock(Drawable.class); 98 asld.addState(STATE_FOCUSED, focused, R.id.unfocused); 99 assertEquals(2, cs.getChildCount()); 100 } 101 102 @Test testAddTransition()103 public void testAddTransition() { 104 AnimatedStateListDrawable asld = new AnimatedStateListDrawable(); 105 DrawableContainerState cs = (DrawableContainerState) asld.getConstantState(); 106 107 Drawable focused = mock(Drawable.class); 108 Drawable unfocused = mock(Drawable.class); 109 asld.addState(STATE_FOCUSED, focused, R.id.focused); 110 asld.addState(StateSet.WILD_CARD, unfocused, R.id.unfocused); 111 112 try { 113 asld.addTransition(R.id.focused, R.id.focused, null, false); 114 fail("Expected IllegalArgumentException"); 115 } catch (IllegalArgumentException e) { 116 // Expected. 117 } 118 119 MockTransition focusedToUnfocused = mock(MockTransition.class); 120 asld.addTransition(R.id.focused, R.id.unfocused, focusedToUnfocused, false); 121 assertEquals(3, cs.getChildCount()); 122 123 MockTransition unfocusedToFocused = mock(MockTransition.class); 124 asld.addTransition(R.id.unfocused, R.id.focused, unfocusedToFocused, false); 125 assertEquals(4, cs.getChildCount()); 126 127 MockTransition reversible = mock(MockTransition.class); 128 asld.addTransition(R.id.focused, R.id.unfocused, reversible, true); 129 assertEquals(5, cs.getChildCount()); 130 } 131 132 @Test testIsStateful()133 public void testIsStateful() { 134 assertTrue(new AnimatedStateListDrawable().isStateful()); 135 } 136 137 @Test testOnStateChange()138 public void testOnStateChange() { 139 AnimatedStateListDrawable asld = new AnimatedStateListDrawable(); 140 141 Drawable focused = mock(Drawable.class); 142 Drawable unfocused = mock(Drawable.class); 143 asld.addState(STATE_FOCUSED, focused, R.id.focused); 144 asld.addState(StateSet.WILD_CARD, unfocused, R.id.unfocused); 145 146 MockTransition focusedToUnfocused = mock(MockTransition.class); 147 MockTransition unfocusedToFocused = mock(MockTransition.class); 148 asld.addTransition(R.id.focused, R.id.unfocused, focusedToUnfocused, false); 149 asld.addTransition(R.id.unfocused, R.id.focused, unfocusedToFocused, false); 150 151 asld.setState(STATE_EMPTY); 152 assertSame(unfocused, asld.getCurrent()); 153 154 asld.setState(STATE_FOCUSED); 155 assertSame(unfocusedToFocused, asld.getCurrent()); 156 157 asld.setState(STATE_FOCUSED); 158 assertSame(unfocusedToFocused, asld.getCurrent()); 159 } 160 161 @Test testPreloadDensity()162 public void testPreloadDensity() throws XmlPullParserException, IOException { 163 runPreloadDensityTestForDrawable( 164 R.drawable.animated_state_list_density, false); 165 } 166 167 @Test testPreloadDensityConstantSize()168 public void testPreloadDensityConstantSize() throws XmlPullParserException, IOException { 169 runPreloadDensityTestForDrawable( 170 R.drawable.animated_state_list_density_constant_size, true); 171 } 172 runPreloadDensityTestForDrawable(int drawableResId, boolean isConstantSize)173 private void runPreloadDensityTestForDrawable(int drawableResId, boolean isConstantSize) 174 throws XmlPullParserException, IOException { 175 final Resources res = mResources; 176 final int densityDpi = res.getConfiguration().densityDpi; 177 try { 178 runPreloadDensityTestForDrawableInner(res, densityDpi, drawableResId, isConstantSize); 179 } finally { 180 DrawableTestUtils.setResourcesDensity(res, densityDpi); 181 } 182 } 183 runPreloadDensityTestForDrawableInner(Resources res, int densityDpi, int drawableResId, boolean isConstantSize)184 private void runPreloadDensityTestForDrawableInner(Resources res, int densityDpi, 185 int drawableResId, boolean isConstantSize) throws XmlPullParserException, IOException { 186 // Capture initial state at default density. 187 final XmlResourceParser parser = getResourceParser(drawableResId); 188 final AnimatedStateListDrawable asld = new AnimatedStateListDrawable(); 189 asld.inflate(res, parser, Xml.asAttributeSet(parser)); 190 191 final DrawableContainerState cs = (DrawableContainerState) asld.getConstantState(); 192 final int count = cs.getChildCount(); 193 final int[] origWidth = new int[count]; 194 int max = 0; 195 for (int i = 0; i < count; i++) { 196 final int width = cs.getChild(i).getIntrinsicWidth(); 197 max = Math.max(max, width); 198 origWidth[i] = width; 199 } 200 if (isConstantSize) { 201 Arrays.fill(origWidth, max); 202 } 203 204 // NOTE: instrinsic W/H may already be a scaled and truncated value computed from the 205 // underlying asset (e.g. bitmap) so account for this previously applied scaling/truncation 206 // by applying a small delta to the asserts. 207 208 // Set density to half of original. 209 DrawableTestUtils.setResourcesDensity(res, densityDpi / 2); 210 final StateListDrawable halfDrawable = 211 (StateListDrawable) cs.newDrawable(res); 212 for (int i = 0; i < count; i++) { 213 halfDrawable.selectDrawable(i); 214 assertEquals(Math.round(origWidth[i] * 0.5f), halfDrawable.getIntrinsicWidth(), 1); 215 } 216 217 // Set density to double original. 218 DrawableTestUtils.setResourcesDensity(res, densityDpi * 2); 219 final StateListDrawable doubleDrawable = 220 (StateListDrawable) cs.newDrawable(res); 221 for (int i = 0; i < count; i++) { 222 doubleDrawable.selectDrawable(i); 223 assertEquals(origWidth[i] * 2, doubleDrawable.getIntrinsicWidth(), 1); 224 } 225 226 // Restore original configuration and metrics. 227 DrawableTestUtils.setResourcesDensity(res, densityDpi); 228 final StateListDrawable origDrawable = 229 (StateListDrawable) cs.newDrawable(res); 230 for (int i = 0; i < count; i++) { 231 origDrawable.selectDrawable(i); 232 assertEquals(origWidth[i], origDrawable.getIntrinsicWidth()); 233 } 234 } 235 getResourceParser(int resId)236 private XmlResourceParser getResourceParser(int resId) throws XmlPullParserException, 237 IOException { 238 XmlResourceParser parser = mResources.getXml(resId); 239 int type; 240 while ((type = parser.next()) != XmlPullParser.START_TAG 241 && type != XmlPullParser.END_DOCUMENT) { 242 // Empty loop 243 } 244 return parser; 245 } 246 247 @Test testInflate()248 public void testInflate() throws XmlPullParserException, IOException { 249 AnimatedStateListDrawable asld = (AnimatedStateListDrawable) mContext.getDrawable( 250 R.drawable.animated_state_list_density); 251 DrawableContainerState asldState = (DrawableContainerState) asld.getConstantState(); 252 assertTrue(asld.isVisible()); 253 assertFalse(asldState.isConstantSize()); 254 assertNull(asldState.getConstantPadding()); 255 assertEquals(4, asldState.getChildCount()); 256 } 257 258 @Test testParsingTransitionDefinedWithAVD()259 public void testParsingTransitionDefinedWithAVD() { 260 AnimatedStateListDrawable asld = (AnimatedStateListDrawable) mContext.getDrawable( 261 R.drawable.animated_state_list_with_avd); 262 DrawableContainerState asldState = (DrawableContainerState) asld.getConstantState(); 263 // Ensure that everything defined in xml after the definition of a transition with AVD is 264 // parsed by checking the total drawables parsed. 265 assertEquals(6, asldState.getChildCount()); 266 } 267 268 public abstract class MockTransition extends MockDrawable implements Animatable, Animatable2 { 269 private HashSet<AnimationCallback> mCallbacks = new HashSet<>(); 270 271 @Override registerAnimationCallback(AnimationCallback callback)272 public void registerAnimationCallback(AnimationCallback callback) { 273 mCallbacks.add(callback); 274 } 275 276 @Override unregisterAnimationCallback(AnimationCallback callback)277 public boolean unregisterAnimationCallback(AnimationCallback callback) { 278 return mCallbacks.remove(callback); 279 } 280 281 @Override clearAnimationCallbacks()282 public void clearAnimationCallbacks() { 283 mCallbacks.clear(); 284 } 285 } 286 287 public class MockDrawable extends Drawable { 288 @Override draw(Canvas canvas)289 public void draw(Canvas canvas) { 290 } 291 292 @Override getOpacity()293 public int getOpacity() { 294 return 0; 295 } 296 297 @Override setAlpha(int alpha)298 public void setAlpha(int alpha) { 299 } 300 301 @Override setColorFilter(ColorFilter cf)302 public void setColorFilter(ColorFilter cf) { 303 } 304 } 305 } 306