1 package android.graphics.drawable.cts; 2 3 import static org.junit.Assert.assertEquals; 4 import static org.junit.Assert.assertFalse; 5 import static org.junit.Assert.assertNotNull; 6 import static org.junit.Assert.assertTrue; 7 import static org.junit.Assert.fail; 8 9 import android.content.Context; 10 import android.content.res.Resources; 11 import android.graphics.Bitmap; 12 import android.graphics.Bitmap.Config; 13 import android.graphics.Canvas; 14 import android.graphics.Color; 15 import android.graphics.ColorFilter; 16 import android.graphics.Path; 17 import android.graphics.Path.Direction; 18 import android.graphics.PixelFormat; 19 import android.graphics.Rect; 20 import android.graphics.Region; 21 import android.graphics.cts.R; 22 import android.graphics.drawable.AdaptiveIconDrawable; 23 import android.graphics.drawable.BitmapDrawable; 24 import android.graphics.drawable.ColorDrawable; 25 import android.graphics.drawable.Drawable; 26 import android.graphics.drawable.Drawable.ConstantState; 27 import android.graphics.drawable.StateListDrawable; 28 import android.util.AttributeSet; 29 import android.util.Log; 30 import android.util.Xml; 31 32 import androidx.test.InstrumentationRegistry; 33 import androidx.test.filters.SmallTest; 34 import androidx.test.runner.AndroidJUnit4; 35 36 import org.junit.Test; 37 import org.junit.runner.RunWith; 38 import org.xmlpull.v1.XmlPullParser; 39 40 import java.io.File; 41 import java.io.FileOutputStream; 42 import java.util.Arrays; 43 44 @SmallTest 45 @RunWith(AndroidJUnit4.class) 46 public class AdaptiveIconDrawableTest { 47 48 public static final String TAG = AdaptiveIconDrawableTest.class.getSimpleName(); 49 public static final boolean DEBUG = false; 50 L(String s, Object... parts)51 public static void L(String s, Object... parts) { 52 Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts)); 53 } 54 55 @Test testConstructor()56 public void testConstructor() { 57 new AdaptiveIconDrawable(null, null); 58 } 59 60 @Test testInflate()61 public void testInflate() throws Throwable { 62 AdaptiveIconDrawable dr = new AdaptiveIconDrawable(null, null); 63 64 Resources r = InstrumentationRegistry.getTargetContext().getResources(); 65 XmlPullParser parser = r.getXml(R.layout.framelayout_layout); 66 AttributeSet attrs = Xml.asAttributeSet(parser); 67 68 // Should not throw inflate exception 69 dr.inflate(r, parser, attrs); 70 } 71 72 @Test(expected=NullPointerException.class) testInflateNull()73 public void testInflateNull() throws Throwable { 74 AdaptiveIconDrawable dr = new AdaptiveIconDrawable(null, null); 75 dr.inflate(null, null, null); 76 } 77 78 @Test testDraw()79 public void testDraw() { 80 Canvas c = new Canvas(); 81 AdaptiveIconDrawable dr = new AdaptiveIconDrawable(null, null); 82 dr.draw(c); 83 } 84 85 @Test(expected=NullPointerException.class) testDrawNull()86 public void testDrawNull() { 87 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable( 88 new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE)); 89 iconDrawable.setBounds(0, 0, 100, 100); 90 iconDrawable.draw(null); 91 } 92 93 @Test testInvalidateDrawable()94 public void testInvalidateDrawable() { 95 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable( 96 new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE)); 97 iconDrawable.invalidateDrawable( 98 InstrumentationRegistry.getTargetContext().getDrawable(R.drawable.pass)); 99 } 100 101 @Test testScheduleDrawable()102 public void testScheduleDrawable() { 103 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable( 104 new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE)); 105 iconDrawable.scheduleDrawable( 106 InstrumentationRegistry.getTargetContext().getDrawable(R.drawable.pass), 107 () -> {}, 10); 108 109 // input null as params 110 iconDrawable.scheduleDrawable(null, null, -1); 111 // expected, no Exception thrown out, test success 112 } 113 114 @Test testUnscheduleDrawable()115 public void testUnscheduleDrawable() { 116 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable( 117 new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE)); 118 iconDrawable.unscheduleDrawable( 119 InstrumentationRegistry.getTargetContext().getDrawable(R.drawable.pass), () -> {}); 120 121 // input null as params 122 iconDrawable.unscheduleDrawable(null, null); 123 // expected, no Exception thrown out, test success 124 } 125 126 @Test testGetChangingConfigurations()127 public void testGetChangingConfigurations() { 128 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable( 129 new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE)); 130 iconDrawable.setChangingConfigurations(11); 131 assertEquals(11, iconDrawable.getChangingConfigurations()); 132 133 iconDrawable.setChangingConfigurations(-21); 134 assertEquals(-21, iconDrawable.getChangingConfigurations()); 135 } 136 137 @Test testGetAlpha()138 public void testGetAlpha() { 139 ColorDrawable drawable = new ColorDrawable(Color.RED); 140 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(null, drawable); 141 iconDrawable.setAlpha(128); 142 assertEquals(128, iconDrawable.getAlpha()); 143 } 144 145 /** 146 * When setBound isn't called before draw method is called. 147 * Nothing is drawn. 148 */ 149 @Test testDrawWithoutSetBounds()150 public void testDrawWithoutSetBounds() throws Exception { 151 Drawable backgroundDrawable = new ColorDrawable(Color.BLUE); 152 Drawable foregroundDrawable = new ColorDrawable(Color.RED); 153 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(backgroundDrawable, foregroundDrawable); 154 Context context = InstrumentationRegistry.getTargetContext(); 155 File dir = context.getExternalFilesDir(null); 156 L("writing temp bitmaps to %s...", dir); 157 158 final Bitmap bm_test = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888); 159 final Bitmap bm_org = bm_test.copy(Config.ARGB_8888, false); 160 final Canvas can1 = new Canvas(bm_test); 161 162 // Even when setBounds is not called, should not crash 163 iconDrawable.draw(can1); 164 // Draws nothing! Hence same as original. 165 if (!equalBitmaps(bm_test, bm_org)) { 166 findBitmapDifferences(bm_test, bm_org); 167 fail("bm differs, check " + dir); 168 } 169 } 170 171 /** 172 * When setBound is called, translate accordingly. 173 */ 174 @Test testDrawSetBounds()175 public void testDrawSetBounds() throws Exception { 176 int dpi = 4 ; 177 int top = 18 * dpi; 178 int left = 18 * dpi; 179 int right = 90 * dpi; 180 int bottom = 90 * dpi; 181 int width = right - left; 182 int height = bottom - top; 183 Context context = InstrumentationRegistry.getTargetContext(); 184 AdaptiveIconDrawable iconDrawable = (AdaptiveIconDrawable) context.getResources().getDrawable(android.R.drawable.sym_def_app_icon); 185 File dir = context.getExternalFilesDir(null); 186 L("writing temp bitmaps to %s...", dir); 187 final Bitmap bm_org = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 188 final Canvas can_org = new Canvas(bm_org); 189 iconDrawable.setBounds(0, 0, width, height); 190 iconDrawable.draw(can_org); 191 192 // Tested bitmap is drawn from the adaptive icon drawable. 193 final Bitmap bm_test = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 194 final Canvas can_test = new Canvas(bm_test); 195 196 iconDrawable.setBounds(left, top, right, bottom); 197 can_test.translate(-left, -top); 198 iconDrawable.draw(can_test); 199 can_test.translate(left, top); 200 201 if (DEBUG) { 202 bm_org.compress(Bitmap.CompressFormat.PNG, 100, 203 new FileOutputStream(new File(dir, "adaptive-bm-original.png"))); 204 bm_test.compress(Bitmap.CompressFormat.PNG, 100, 205 new FileOutputStream(new File(dir, "adaptive-bm-test.png"))); 206 } 207 Region region = new Region(new Rect(0, 0, width, height)); 208 209 Path circle = new Path(); 210 circle.addCircle(width / 2, height / 2, (right - left)/2 -10 /* room for anti-alias */, Direction.CW); 211 212 region.setPath(circle, region); 213 if (!equalBitmaps(bm_test, bm_org, region)) { 214 findBitmapDifferences(bm_test, bm_org); 215 fail("bm differs, check " + dir); 216 } 217 } 218 219 @Test testSetVisible()220 public void testSetVisible() { 221 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable( 222 new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE)); 223 assertFalse(iconDrawable.setVisible(true, true)); /* unchanged */ 224 assertTrue(iconDrawable.setVisible(false, true)); /* changed */ 225 assertFalse(iconDrawable.setVisible(false, true)); /* unchanged */ 226 } 227 228 @Test testSetAlpha()229 public void testSetAlpha() { 230 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable( 231 new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE)); 232 iconDrawable.setBounds(0, 0, 100, 100); 233 234 Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 235 Canvas canvas = new Canvas(bitmap); 236 237 iconDrawable.draw(canvas); 238 assertEquals(255, Color.alpha(bitmap.getPixel(50, 50))); 239 240 iconDrawable.setAlpha(200); 241 bitmap.eraseColor(Color.TRANSPARENT); 242 iconDrawable.draw(canvas); 243 assertEquals(200, Color.alpha(bitmap.getPixel(50, 50))); 244 245 iconDrawable.setAlpha(100); 246 bitmap.eraseColor(Color.TRANSPARENT); 247 iconDrawable.draw(canvas); 248 assertEquals(100, Color.alpha(bitmap.getPixel(50, 50))); 249 } 250 251 @Test testSetColorFilter()252 public void testSetColorFilter() { 253 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable( 254 new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE)); 255 ColorFilter cf = new ColorFilter(); 256 iconDrawable.setColorFilter(cf); 257 258 // input null as param 259 iconDrawable.setColorFilter(null); 260 // expected, no Exception thrown out, test success 261 } 262 263 @Test testGetOpacity()264 public void testGetOpacity() { 265 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable( 266 new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE)); 267 // Drawable#getOpacity is deprecated, AdaptiveIconDrawable 268 // should return PixelFormat.TRANSLUCENT always 269 iconDrawable.setOpacity(PixelFormat.OPAQUE); 270 assertEquals(PixelFormat.TRANSLUCENT, iconDrawable.getOpacity()); 271 272 iconDrawable.setOpacity(PixelFormat.TRANSPARENT); 273 assertEquals(PixelFormat.TRANSLUCENT, iconDrawable.getOpacity()); 274 } 275 276 @Test testIsStateful()277 public void testIsStateful() { 278 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable( 279 new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE)); 280 assertFalse(iconDrawable.isStateful()); 281 282 iconDrawable = new AdaptiveIconDrawable(new StateListDrawable(), new ColorDrawable(Color.RED)); 283 assertTrue(iconDrawable.isStateful()); 284 } 285 286 287 @Test testGetConstantState()288 public void testGetConstantState() { 289 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable( 290 new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE)); 291 ConstantState constantState = iconDrawable.getConstantState(); 292 assertNotNull(constantState); 293 } 294 295 @Test testMutate()296 public void testMutate() { 297 // Obtain the first instance, then mutate and modify a property held by 298 // constant state. If mutate() works correctly, the property should not 299 // be modified on the second or third instances. 300 Resources res = InstrumentationRegistry.getTargetContext().getResources(); 301 AdaptiveIconDrawable first = (AdaptiveIconDrawable) res.getDrawable(R.drawable.adaptive_icon_drawable, null); 302 AdaptiveIconDrawable pre = (AdaptiveIconDrawable) res.getDrawable(R.drawable.adaptive_icon_drawable, null); 303 304 first.mutate().setBounds(0, 0, 100, 100); 305 306 assertEquals("Modified first loaded instance", 100, first.getBounds().width()); 307 assertEquals("Did not modify pre-mutate() instance", 0, pre.getBounds().width()); 308 309 AdaptiveIconDrawable post = (AdaptiveIconDrawable) res.getDrawable(R.drawable.adaptive_icon_drawable, null); 310 311 assertEquals("Did not modify post-mutate() instance", 0, post.getBounds().width()); 312 } 313 314 @Test testGetForegroundBackground()315 public void testGetForegroundBackground() { 316 Context context = InstrumentationRegistry.getTargetContext(); 317 AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable( 318 new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE)); 319 Drawable fgDrawable = iconDrawable.getForeground(); 320 Drawable bgDrawable = iconDrawable.getBackground(); 321 assertTrue("Foreground layer is color drawable.", fgDrawable instanceof ColorDrawable); 322 assertTrue("Backgroud layer is color drawable.", bgDrawable instanceof ColorDrawable); 323 324 AdaptiveIconDrawable iconDrawableInflated = 325 (AdaptiveIconDrawable) context.getDrawable(R.drawable.adaptive_icon_drawable); 326 fgDrawable = iconDrawableInflated.getForeground(); 327 bgDrawable = iconDrawableInflated.getBackground(); 328 assertTrue("Foreground layer is color drawable.", fgDrawable instanceof BitmapDrawable); 329 assertTrue("Backgroud layer is color drawable.", bgDrawable instanceof BitmapDrawable); 330 } 331 332 @Test testGetIntrinsicWidth()333 public void testGetIntrinsicWidth() { 334 Context context = InstrumentationRegistry.getTargetContext(); 335 AdaptiveIconDrawable iconDrawableInflated = 336 (AdaptiveIconDrawable) context.getDrawable(R.drawable.adaptive_icon_drawable); 337 int fgWidth = iconDrawableInflated.getForeground().getIntrinsicWidth(); 338 int bgWidth = iconDrawableInflated.getBackground().getIntrinsicWidth(); 339 float iconIntrinsicWidth = Math.max(fgWidth, bgWidth) / (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction()); 340 assertEquals("Max intrinsic width of the layers should be icon's intrinsic width", 341 (int) iconIntrinsicWidth, iconDrawableInflated.getIntrinsicWidth()); 342 } 343 344 @Test testGetIntrinsicHeight()345 public void testGetIntrinsicHeight() { 346 Context context = InstrumentationRegistry.getTargetContext(); 347 AdaptiveIconDrawable iconDrawableInflated = 348 (AdaptiveIconDrawable) context.getDrawable(R.drawable.adaptive_icon_drawable); 349 int fgWidth = iconDrawableInflated.getForeground().getIntrinsicHeight(); 350 int bgWidth = iconDrawableInflated.getBackground().getIntrinsicHeight(); 351 float iconIntrinsicHeight = Math.max(fgWidth, bgWidth) / (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction()); 352 assertEquals("Max intrinsic height of the layers should be icon's intrinsic height", 353 (int) iconIntrinsicHeight, iconDrawableInflated.getIntrinsicHeight()); 354 } 355 356 // 357 // Utils 358 // 359 equalBitmaps(Bitmap a, Bitmap b)360 boolean equalBitmaps(Bitmap a, Bitmap b) { 361 return equalBitmaps(a, b, null); 362 } 363 equalBitmaps(Bitmap a, Bitmap b, Region region)364 boolean equalBitmaps(Bitmap a, Bitmap b, Region region) { 365 if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) return false; 366 367 final int w = a.getWidth(); 368 final int h = a.getHeight(); 369 int[] aPix = new int[w * h]; 370 int[] bPix = new int[w * h]; 371 372 if (region != null) { 373 for (int i = 0; i < w; i++) { 374 for (int j = 0; j < h; j++) { 375 int ra = (a.getPixel(i, j) >> 16) & 0xff; 376 int ga = (a.getPixel(i, j) >> 8) & 0xff; 377 int ba = a.getPixel(i, j) & 0xff; 378 int rb = (b.getPixel(i, j) >> 16) & 0xff; 379 int gb = (b.getPixel(i, j) >> 8) & 0xff; 380 int bb = b.getPixel(i, j) & 0xff; 381 if (region.contains(i, j) && a.getPixel(i, j) != b.getPixel(i, j) ) { 382 return false; 383 } 384 } 385 } 386 return true; 387 } else { 388 a.getPixels(aPix, 0, w, 0, 0, w, h); 389 b.getPixels(bPix, 0, w, 0, 0, w, h); 390 return Arrays.equals(aPix, bPix); 391 } 392 } 393 findBitmapDifferences(Bitmap a, Bitmap b)394 void findBitmapDifferences(Bitmap a, Bitmap b) { 395 if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) { 396 L("different sizes: %dx%d vs %dx%d", 397 a.getWidth(), a.getHeight(), b.getWidth(), b.getHeight()); 398 return; 399 } 400 401 final int w = a.getWidth(); 402 final int h = a.getHeight(); 403 int[] aPix = new int[w * h]; 404 int[] bPix = new int[w * h]; 405 406 a.getPixels(aPix, 0, w, 0, 0, w, h); 407 b.getPixels(bPix, 0, w, 0, 0, w, h); 408 409 L("bitmap a (%dx%d)", w, h); 410 printBits(aPix, w, h); 411 L("bitmap b (%dx%d)", w, h); 412 printBits(bPix, w, h); 413 414 StringBuffer sb = new StringBuffer("Different pixels: "); 415 for (int i=0; i<w; i++) { 416 for (int j=0; j<h; j++) { 417 if (aPix[i+w*j] != bPix[i+w*j]) { 418 sb.append(" ").append(i).append(",").append(j).append("<") 419 .append(aPix[i+w*j]).append(",").append(bPix[i+w*j]).append(">"); 420 } 421 } 422 } 423 L(sb.toString()); 424 } 425 printBits(int[] a, int w, int h)426 static void printBits(int[] a, int w, int h) { 427 final StringBuilder sb = new StringBuilder(); 428 for (int i=0; i<w; i++) { 429 for (int j=0; j<h; j++) { 430 sb.append(colorToChar(a[i+w*j])); 431 } 432 sb.append('\n'); 433 } 434 L(sb.toString()); 435 } 436 colorToChar(int color)437 static char colorToChar(int color) { 438 int sum = ((color >> 16) & 0xff) 439 + ((color >> 8) & 0xff) 440 + ((color) & 0xff); 441 return GRADIENT[sum * (GRADIENT.length-1) / (3*0xff)]; 442 } 443 static final char[] GRADIENT = " .:;+=xX$#".toCharArray(); 444 } 445