1 package com.android.systemui.graphics 2 3 import android.content.res.Resources 4 import android.graphics.Bitmap 5 import android.graphics.BitmapFactory 6 import android.graphics.ImageDecoder 7 import android.graphics.drawable.BitmapDrawable 8 import android.graphics.drawable.Drawable 9 import android.graphics.drawable.Icon 10 import android.graphics.drawable.VectorDrawable 11 import android.net.Uri 12 import android.util.Size 13 import androidx.test.ext.junit.runners.AndroidJUnit4 14 import androidx.test.filters.SmallTest 15 import com.android.systemui.res.R 16 import com.android.systemui.SysuiTestCase 17 import com.google.common.truth.Truth.assertThat 18 import java.io.ByteArrayInputStream 19 import java.io.ByteArrayOutputStream 20 import java.io.File 21 import java.io.FileInputStream 22 import java.io.FileOutputStream 23 import kotlinx.coroutines.test.TestScope 24 import kotlinx.coroutines.test.UnconfinedTestDispatcher 25 import kotlinx.coroutines.test.runTest 26 import org.junit.After 27 import org.junit.Before 28 import org.junit.Test 29 import org.junit.runner.RunWith 30 31 @SmallTest 32 @kotlinx.coroutines.ExperimentalCoroutinesApi 33 @RunWith(AndroidJUnit4::class) 34 class ImageLoaderTest : SysuiTestCase() { 35 36 private val testDispatcher = UnconfinedTestDispatcher() 37 private val testScope = TestScope(testDispatcher) 38 private val imageLoader = ImageLoader(context, testDispatcher) 39 40 private lateinit var imgFile: File 41 42 @Before setUpnull43 fun setUp() { 44 val context = context.createPackageContext("com.android.systemui.tests", 0) 45 val bitmap = 46 BitmapFactory.decodeResource( 47 context.resources, 48 com.android.systemui.tests.R.drawable.romainguy_rockaway 49 ) 50 51 imgFile = File.createTempFile("image", ".png", context.cacheDir) 52 imgFile.deleteOnExit() 53 bitmap.compress(Bitmap.CompressFormat.PNG, 100, FileOutputStream(imgFile)) 54 } 55 56 @After tearDownnull57 fun tearDown() { 58 imgFile.delete() 59 } 60 61 @Test invalidResource_drawable_returnsNullnull62 fun invalidResource_drawable_returnsNull() = 63 testScope.runTest { assertThat(imageLoader.loadDrawable(ImageLoader.Res(-1))).isNull() } 64 65 @Test invalidResource_bitmap_returnsNullnull66 fun invalidResource_bitmap_returnsNull() = 67 testScope.runTest { assertThat(imageLoader.loadBitmap(ImageLoader.Res(-1))).isNull() } 68 69 @Test invalidUri_returnsNullnull70 fun invalidUri_returnsNull() = 71 testScope.runTest { 72 assertThat(imageLoader.loadBitmap(ImageLoader.Uri("this.is/bogus"))).isNull() 73 } 74 75 @Test invalidFile_returnsNullnull76 fun invalidFile_returnsNull() = 77 testScope.runTest { 78 assertThat(imageLoader.loadBitmap(ImageLoader.File("this is broken!"))).isNull() 79 } 80 81 @Test invalidIcon_loadDrawable_returnsNullnull82 fun invalidIcon_loadDrawable_returnsNull() = 83 testScope.runTest { 84 assertThat(imageLoader.loadDrawable(Icon.createWithFilePath("this is broken"))).isNull() 85 } 86 87 @Test invalidIcon_loadSize_returnsNullnull88 fun invalidIcon_loadSize_returnsNull() = 89 testScope.runTest { 90 assertThat(imageLoader.loadSize(Icon.createWithFilePath("this is broken"), context)) 91 .isNull() 92 } 93 94 @Test invalidIS_returnsNullnull95 fun invalidIS_returnsNull() = 96 testScope.runTest { 97 assertThat( 98 imageLoader.loadDrawable( 99 ImageLoader.InputStream(ByteArrayInputStream(ByteArray(0))) 100 ) 101 ) 102 .isNull() 103 } 104 105 @Test validBitmapResource_loadDrawable_returnsBitmapDrawablenull106 fun validBitmapResource_loadDrawable_returnsBitmapDrawable() = 107 testScope.runTest { 108 val context = context.createPackageContext("com.android.systemui.tests", 0) 109 val bitmap = 110 BitmapFactory.decodeResource( 111 context.resources, 112 com.android.systemui.tests.R.drawable.romainguy_rockaway 113 ) 114 assertThat(bitmap).isNotNull() 115 val loadedDrawable = 116 imageLoader.loadDrawable( 117 ImageLoader.Res( 118 com.android.systemui.tests.R.drawable.romainguy_rockaway, 119 context 120 ) 121 ) 122 assertBitmapEqualToDrawable(loadedDrawable, bitmap) 123 } 124 125 @Test validBitmapResource_loadBitmap_returnsBitmapDrawablenull126 fun validBitmapResource_loadBitmap_returnsBitmapDrawable() = 127 testScope.runTest { 128 val bitmap = 129 BitmapFactory.decodeResource( 130 context.resources, 131 R.drawable.dessert_zombiegingerbread 132 ) 133 val loadedBitmap = 134 imageLoader.loadBitmap(ImageLoader.Res(R.drawable.dessert_zombiegingerbread)) 135 assertBitmapEqualToBitmap(loadedBitmap, bitmap) 136 } 137 138 @Test validBitmapUri_returnsBitmapDrawablenull139 fun validBitmapUri_returnsBitmapDrawable() = 140 testScope.runTest { 141 val bitmap = 142 BitmapFactory.decodeResource( 143 context.resources, 144 R.drawable.dessert_zombiegingerbread 145 ) 146 147 val uri = 148 "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}" 149 val loadedBitmap = imageLoader.loadBitmap(ImageLoader.Uri(uri)) 150 assertBitmapEqualToBitmap(loadedBitmap, bitmap) 151 } 152 153 @Test validBitmapFile_returnsBitmapDrawablenull154 fun validBitmapFile_returnsBitmapDrawable() = 155 testScope.runTest { 156 val bitmap = BitmapFactory.decodeFile(imgFile.absolutePath) 157 val loadedBitmap = imageLoader.loadBitmap(ImageLoader.File(imgFile)) 158 assertBitmapEqualToBitmap(loadedBitmap, bitmap) 159 } 160 161 @Test validInputStream_returnsBitmapDrawablenull162 fun validInputStream_returnsBitmapDrawable() = 163 testScope.runTest { 164 val bitmap = BitmapFactory.decodeFile(imgFile.absolutePath) 165 val loadedBitmap = 166 imageLoader.loadBitmap(ImageLoader.InputStream(FileInputStream(imgFile))) 167 assertBitmapEqualToBitmap(loadedBitmap, bitmap) 168 } 169 170 @Test validBitmapIcon_returnsBitmapDrawablenull171 fun validBitmapIcon_returnsBitmapDrawable() = 172 testScope.runTest { 173 val bitmap = 174 BitmapFactory.decodeResource( 175 context.resources, 176 R.drawable.dessert_zombiegingerbread 177 ) 178 val loadedDrawable = imageLoader.loadDrawable(Icon.createWithBitmap(bitmap)) 179 assertBitmapEqualToDrawable(loadedDrawable, bitmap) 180 } 181 182 @Test validBitmapIcon_loadSize_returnsNullnull183 fun validBitmapIcon_loadSize_returnsNull() = 184 testScope.runTest { 185 val bitmap = 186 BitmapFactory.decodeResource( 187 context.resources, 188 R.drawable.dessert_zombiegingerbread 189 ) 190 assertThat(imageLoader.loadSize(Icon.createWithBitmap(bitmap), context)).isNull() 191 } 192 193 @Test validUriIcon_returnsBitmapDrawablenull194 fun validUriIcon_returnsBitmapDrawable() = 195 testScope.runTest { 196 val bitmap = 197 BitmapFactory.decodeResource( 198 context.resources, 199 R.drawable.dessert_zombiegingerbread 200 ) 201 val uri = 202 "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}" 203 val loadedDrawable = imageLoader.loadDrawable(Icon.createWithContentUri(Uri.parse(uri))) 204 assertBitmapEqualToDrawable(loadedDrawable, bitmap) 205 } 206 207 @Test validUriIcon_returnsSizenull208 fun validUriIcon_returnsSize() = 209 testScope.runTest { 210 val drawable = context.resources.getDrawable(R.drawable.dessert_zombiegingerbread) 211 val uri = 212 "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}" 213 val loadedSize = 214 imageLoader.loadSize(Icon.createWithContentUri(Uri.parse(uri)), context) 215 assertSizeEqualToDrawableSize(loadedSize, drawable) 216 } 217 218 @Test validDataIcon_returnsBitmapDrawablenull219 fun validDataIcon_returnsBitmapDrawable() = 220 testScope.runTest { 221 val bitmap = 222 BitmapFactory.decodeResource( 223 context.resources, 224 R.drawable.dessert_zombiegingerbread 225 ) 226 val bos = 227 ByteArrayOutputStream( 228 bitmap.byteCount * 2 229 ) // Compressed bitmap should be smaller than its source. 230 bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos) 231 232 val array = bos.toByteArray() 233 val loadedDrawable = imageLoader.loadDrawable(Icon.createWithData(array, 0, array.size)) 234 assertBitmapEqualToDrawable(loadedDrawable, bitmap) 235 } 236 237 @Test validDataIcon_loadSize_returnsNullnull238 fun validDataIcon_loadSize_returnsNull() = 239 testScope.runTest { 240 val bitmap = 241 BitmapFactory.decodeResource( 242 context.resources, 243 R.drawable.dessert_zombiegingerbread 244 ) 245 val bos = 246 ByteArrayOutputStream( 247 bitmap.byteCount * 2 248 ) // Compressed bitmap should be smaller than its source. 249 bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos) 250 251 val array = bos.toByteArray() 252 assertThat(imageLoader.loadSize(Icon.createWithData(array, 0, array.size), context)) 253 .isNull() 254 } 255 256 @Test validResourceIcon_returnsBitmapDrawablenull257 fun validResourceIcon_returnsBitmapDrawable() = 258 testScope.runTest { 259 val bitmap = context.resources.getDrawable(R.drawable.dessert_zombiegingerbread) 260 val loadedDrawable = 261 imageLoader.loadDrawable( 262 Icon.createWithResource( 263 "com.android.systemui.tests", 264 R.drawable.dessert_zombiegingerbread 265 ) 266 ) 267 assertBitmapEqualToDrawable(loadedDrawable, (bitmap as BitmapDrawable).bitmap) 268 } 269 270 @Test validResourceIcon_loadSize_returnsNullnull271 fun validResourceIcon_loadSize_returnsNull() = 272 testScope.runTest { 273 assertThat( 274 imageLoader.loadSize( 275 Icon.createWithResource( 276 "com.android.systemui.tests", 277 R.drawable.dessert_zombiegingerbread 278 ), 279 context 280 ) 281 ) 282 .isNull() 283 } 284 285 @Test validSystemResourceIcon_returnsBitmapDrawablenull286 fun validSystemResourceIcon_returnsBitmapDrawable() = 287 testScope.runTest { 288 val bitmap = 289 Resources.getSystem().getDrawable(android.R.drawable.ic_dialog_alert, context.theme) 290 val loadedDrawable = 291 imageLoader.loadDrawable( 292 Icon.createWithResource("android", android.R.drawable.ic_dialog_alert) 293 ) 294 assertBitmapEqualToDrawable(loadedDrawable, (bitmap as BitmapDrawable).bitmap) 295 } 296 297 @Test validSystemResourceIcon_loadSize_returnsNullnull298 fun validSystemResourceIcon_loadSize_returnsNull() = 299 testScope.runTest { 300 assertThat( 301 imageLoader.loadSize( 302 Icon.createWithResource("android", android.R.drawable.ic_dialog_alert), 303 context 304 ) 305 ) 306 .isNull() 307 } 308 309 @Test invalidDifferentPackageResourceIcon_returnsNullnull310 fun invalidDifferentPackageResourceIcon_returnsNull() = 311 testScope.runTest { 312 val loadedDrawable = 313 imageLoader.loadDrawable( 314 Icon.createWithResource( 315 "noooope.wrong.package", 316 R.drawable.dessert_zombiegingerbread 317 ) 318 ) 319 assertThat(loadedDrawable).isNull() 320 } 321 322 @Test invalidDifferentPackageResourceIcon_loadSize_returnsNullnull323 fun invalidDifferentPackageResourceIcon_loadSize_returnsNull() = 324 testScope.runTest { 325 assertThat( 326 imageLoader.loadDrawable( 327 Icon.createWithResource( 328 "noooope.wrong.package", 329 R.drawable.dessert_zombiegingerbread 330 ) 331 ) 332 ) 333 .isNull() 334 } 335 336 @Test validBitmapResource_widthMoreRestricted_downsizesKeepingAspectRationull337 fun validBitmapResource_widthMoreRestricted_downsizesKeepingAspectRatio() = 338 testScope.runTest { 339 val loadedDrawable = 340 imageLoader.loadDrawable(ImageLoader.File(imgFile), maxWidth = 160, maxHeight = 160) 341 val loadedBitmap = assertBitmapInDrawable(loadedDrawable) 342 assertThat(loadedBitmap.width).isEqualTo(160) 343 assertThat(loadedBitmap.height).isEqualTo(106) 344 } 345 346 @Test validBitmapResource_heightMoreRestricted_downsizesKeepingAspectRationull347 fun validBitmapResource_heightMoreRestricted_downsizesKeepingAspectRatio() = 348 testScope.runTest { 349 val loadedDrawable = 350 imageLoader.loadDrawable(ImageLoader.File(imgFile), maxWidth = 160, maxHeight = 50) 351 val loadedBitmap = assertBitmapInDrawable(loadedDrawable) 352 assertThat(loadedBitmap.width).isEqualTo(74) 353 assertThat(loadedBitmap.height).isEqualTo(50) 354 } 355 356 @Test validBitmapResource_onlyWidthRestricted_downsizesKeepingAspectRationull357 fun validBitmapResource_onlyWidthRestricted_downsizesKeepingAspectRatio() = 358 testScope.runTest { 359 val loadedDrawable = 360 imageLoader.loadDrawable( 361 ImageLoader.File(imgFile), 362 maxWidth = 160, 363 maxHeight = ImageLoader.DO_NOT_RESIZE 364 ) 365 val loadedBitmap = assertBitmapInDrawable(loadedDrawable) 366 assertThat(loadedBitmap.width).isEqualTo(160) 367 assertThat(loadedBitmap.height).isEqualTo(106) 368 } 369 370 @Test validBitmapResource_onlyHeightRestricted_downsizesKeepingAspectRationull371 fun validBitmapResource_onlyHeightRestricted_downsizesKeepingAspectRatio() = 372 testScope.runTest { 373 val loadedDrawable = 374 imageLoader.loadDrawable( 375 ImageLoader.Res(R.drawable.bubble_thumbnail), 376 maxWidth = ImageLoader.DO_NOT_RESIZE, 377 maxHeight = 120 378 ) 379 val loadedBitmap = assertBitmapInDrawable(loadedDrawable) 380 assertThat(loadedBitmap.width).isEqualTo(123) 381 assertThat(loadedBitmap.height).isEqualTo(120) 382 } 383 384 @Test validVectorDrawable_loadDrawable_successfullyLoadednull385 fun validVectorDrawable_loadDrawable_successfullyLoaded() = 386 testScope.runTest { 387 val loadedDrawable = imageLoader.loadDrawable(ImageLoader.Res(R.drawable.ic_settings)) 388 assertThat(loadedDrawable).isNotNull() 389 assertThat(loadedDrawable).isInstanceOf(VectorDrawable::class.java) 390 } 391 392 @Test validVectorDrawable_loadBitmap_returnsNullnull393 fun validVectorDrawable_loadBitmap_returnsNull() = 394 testScope.runTest { 395 val loadedBitmap = imageLoader.loadBitmap(ImageLoader.Res(R.drawable.ic_settings)) 396 assertThat(loadedBitmap).isNull() 397 } 398 399 @Test validVectorDrawableIcon_loadDrawable_successfullyLoadednull400 fun validVectorDrawableIcon_loadDrawable_successfullyLoaded() = 401 testScope.runTest { 402 val loadedDrawable = 403 imageLoader.loadDrawable(Icon.createWithResource(context, R.drawable.ic_settings)) 404 assertThat(loadedDrawable).isNotNull() 405 assertThat(loadedDrawable).isInstanceOf(VectorDrawable::class.java) 406 } 407 408 @Test hardwareAllocator_returnsHardwareBitmapnull409 fun hardwareAllocator_returnsHardwareBitmap() = 410 testScope.runTest { 411 val loadedDrawable = 412 imageLoader.loadDrawable( 413 ImageLoader.File(imgFile), 414 allocator = ImageDecoder.ALLOCATOR_HARDWARE 415 ) 416 assertThat(loadedDrawable).isNotNull() 417 assertThat((loadedDrawable as BitmapDrawable).bitmap.config) 418 .isEqualTo(Bitmap.Config.HARDWARE) 419 } 420 421 @Test softwareAllocator_returnsSoftwareBitmapnull422 fun softwareAllocator_returnsSoftwareBitmap() = 423 testScope.runTest { 424 val loadedDrawable = 425 imageLoader.loadDrawable( 426 ImageLoader.File(imgFile), 427 allocator = ImageDecoder.ALLOCATOR_SOFTWARE 428 ) 429 assertThat(loadedDrawable).isNotNull() 430 assertThat((loadedDrawable as BitmapDrawable).bitmap.config) 431 .isNotEqualTo(Bitmap.Config.HARDWARE) 432 } 433 assertBitmapInDrawablenull434 private fun assertBitmapInDrawable(drawable: Drawable?): Bitmap { 435 assertThat(drawable).isNotNull() 436 assertThat(drawable).isInstanceOf(BitmapDrawable::class.java) 437 return (drawable as BitmapDrawable).bitmap 438 } 439 assertBitmapEqualToDrawablenull440 private fun assertBitmapEqualToDrawable(actual: Drawable?, expected: Bitmap) { 441 val actualBitmap = assertBitmapInDrawable(actual) 442 assertBitmapEqualToBitmap(actualBitmap, expected) 443 } 444 assertBitmapEqualToBitmapnull445 private fun assertBitmapEqualToBitmap(actual: Bitmap?, expected: Bitmap) { 446 assertThat(actual).isNotNull() 447 assertThat(actual?.width).isEqualTo(expected.width) 448 assertThat(actual?.height).isEqualTo(expected.height) 449 } 450 assertSizeEqualToDrawableSizenull451 private fun assertSizeEqualToDrawableSize(actual: Size?, expected: Drawable) { 452 assertThat(actual).isNotNull() 453 assertThat(actual?.width).isEqualTo(expected.intrinsicWidth) 454 assertThat(actual?.height).isEqualTo(expected.intrinsicHeight) 455 } 456 } 457