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 androidx.test.ext.junit.runners.AndroidJUnit4 13 import androidx.test.filters.SmallTest 14 import com.android.systemui.R 15 import com.android.systemui.SysuiTestCase 16 import com.google.common.truth.Truth.assertThat 17 import java.io.ByteArrayInputStream 18 import java.io.ByteArrayOutputStream 19 import java.io.File 20 import java.io.FileInputStream 21 import java.io.FileOutputStream 22 import kotlinx.coroutines.test.TestScope 23 import kotlinx.coroutines.test.UnconfinedTestDispatcher 24 import kotlinx.coroutines.test.runTest 25 import org.junit.After 26 import org.junit.Before 27 import org.junit.Test 28 import org.junit.runner.RunWith 29 30 @SmallTest 31 @kotlinx.coroutines.ExperimentalCoroutinesApi 32 @RunWith(AndroidJUnit4::class) 33 class ImageLoaderTest : SysuiTestCase() { 34 35 private val testDispatcher = UnconfinedTestDispatcher() 36 private val testScope = TestScope(testDispatcher) 37 private val imageLoader = ImageLoader(context, testDispatcher) 38 39 private lateinit var imgFile: File 40 41 @Before 42 fun setUp() { 43 val context = context.createPackageContext("com.android.systemui.tests", 0) 44 val bitmap = 45 BitmapFactory.decodeResource( 46 context.resources, 47 com.android.systemui.tests.R.drawable.romainguy_rockaway 48 ) 49 50 imgFile = File.createTempFile("image", ".png", context.cacheDir) 51 imgFile.deleteOnExit() 52 bitmap.compress(Bitmap.CompressFormat.PNG, 100, FileOutputStream(imgFile)) 53 } 54 55 @After 56 fun tearDown() { 57 imgFile.delete() 58 } 59 60 @Test 61 fun invalidResource_drawable_returnsNull() = 62 testScope.runTest { assertThat(imageLoader.loadDrawable(ImageLoader.Res(-1))).isNull() } 63 64 @Test 65 fun invalidResource_bitmap_returnsNull() = 66 testScope.runTest { assertThat(imageLoader.loadBitmap(ImageLoader.Res(-1))).isNull() } 67 68 @Test 69 fun invalidUri_returnsNull() = 70 testScope.runTest { 71 assertThat(imageLoader.loadBitmap(ImageLoader.Uri("this.is/bogus"))).isNull() 72 } 73 74 @Test 75 fun invalidFile_returnsNull() = 76 testScope.runTest { 77 assertThat(imageLoader.loadBitmap(ImageLoader.File("this is broken!"))).isNull() 78 } 79 80 @Test 81 fun invalidIcon_returnsNull() = 82 testScope.runTest { 83 assertThat(imageLoader.loadDrawable(Icon.createWithFilePath("this is broken"))).isNull() 84 } 85 86 @Test 87 fun invalidIS_returnsNull() = 88 testScope.runTest { 89 assertThat( 90 imageLoader.loadDrawable( 91 ImageLoader.InputStream(ByteArrayInputStream(ByteArray(0))) 92 ) 93 ) 94 .isNull() 95 } 96 97 @Test 98 fun validBitmapResource_loadDrawable_returnsBitmapDrawable() = 99 testScope.runTest { 100 val context = context.createPackageContext("com.android.systemui.tests", 0) 101 val bitmap = 102 BitmapFactory.decodeResource( 103 context.resources, 104 com.android.systemui.tests.R.drawable.romainguy_rockaway 105 ) 106 assertThat(bitmap).isNotNull() 107 val loadedDrawable = 108 imageLoader.loadDrawable( 109 ImageLoader.Res( 110 com.android.systemui.tests.R.drawable.romainguy_rockaway, 111 context 112 ) 113 ) 114 assertBitmapEqualToDrawable(loadedDrawable, bitmap) 115 } 116 117 @Test 118 fun validBitmapResource_loadBitmap_returnsBitmapDrawable() = 119 testScope.runTest { 120 val bitmap = 121 BitmapFactory.decodeResource( 122 context.resources, 123 R.drawable.dessert_zombiegingerbread 124 ) 125 val loadedBitmap = 126 imageLoader.loadBitmap(ImageLoader.Res(R.drawable.dessert_zombiegingerbread)) 127 assertBitmapEqualToBitmap(loadedBitmap, bitmap) 128 } 129 130 @Test 131 fun validBitmapUri_returnsBitmapDrawable() = 132 testScope.runTest { 133 val bitmap = 134 BitmapFactory.decodeResource( 135 context.resources, 136 R.drawable.dessert_zombiegingerbread 137 ) 138 139 val uri = 140 "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}" 141 val loadedBitmap = imageLoader.loadBitmap(ImageLoader.Uri(uri)) 142 assertBitmapEqualToBitmap(loadedBitmap, bitmap) 143 } 144 145 @Test 146 fun validBitmapFile_returnsBitmapDrawable() = 147 testScope.runTest { 148 val bitmap = BitmapFactory.decodeFile(imgFile.absolutePath) 149 val loadedBitmap = imageLoader.loadBitmap(ImageLoader.File(imgFile)) 150 assertBitmapEqualToBitmap(loadedBitmap, bitmap) 151 } 152 153 @Test 154 fun validInputStream_returnsBitmapDrawable() = 155 testScope.runTest { 156 val bitmap = BitmapFactory.decodeFile(imgFile.absolutePath) 157 val loadedBitmap = 158 imageLoader.loadBitmap(ImageLoader.InputStream(FileInputStream(imgFile))) 159 assertBitmapEqualToBitmap(loadedBitmap, bitmap) 160 } 161 162 @Test 163 fun validBitmapIcon_returnsBitmapDrawable() = 164 testScope.runTest { 165 val bitmap = 166 BitmapFactory.decodeResource( 167 context.resources, 168 R.drawable.dessert_zombiegingerbread 169 ) 170 val loadedDrawable = imageLoader.loadDrawable(Icon.createWithBitmap(bitmap)) 171 assertBitmapEqualToDrawable(loadedDrawable, bitmap) 172 } 173 174 @Test 175 fun validUriIcon_returnsBitmapDrawable() = 176 testScope.runTest { 177 val bitmap = 178 BitmapFactory.decodeResource( 179 context.resources, 180 R.drawable.dessert_zombiegingerbread 181 ) 182 val uri = 183 "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}" 184 val loadedDrawable = imageLoader.loadDrawable(Icon.createWithContentUri(Uri.parse(uri))) 185 assertBitmapEqualToDrawable(loadedDrawable, bitmap) 186 } 187 188 @Test 189 fun validDataIcon_returnsBitmapDrawable() = 190 testScope.runTest { 191 val bitmap = 192 BitmapFactory.decodeResource( 193 context.resources, 194 R.drawable.dessert_zombiegingerbread 195 ) 196 val bos = 197 ByteArrayOutputStream( 198 bitmap.byteCount * 2 199 ) // Compressed bitmap should be smaller than its source. 200 bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos) 201 202 val array = bos.toByteArray() 203 val loadedDrawable = imageLoader.loadDrawable(Icon.createWithData(array, 0, array.size)) 204 assertBitmapEqualToDrawable(loadedDrawable, bitmap) 205 } 206 207 @Test 208 fun validSystemResourceIcon_returnsBitmapDrawable() = 209 testScope.runTest { 210 val bitmap = 211 Resources.getSystem().getDrawable(android.R.drawable.ic_dialog_alert, context.theme) 212 val loadedDrawable = 213 imageLoader.loadDrawable( 214 Icon.createWithResource("android", android.R.drawable.ic_dialog_alert) 215 ) 216 assertBitmapEqualToDrawable(loadedDrawable, (bitmap as BitmapDrawable).bitmap) 217 } 218 219 @Test 220 fun invalidDifferentPackageResourceIcon_returnsNull() = 221 testScope.runTest { 222 val loadedDrawable = 223 imageLoader.loadDrawable( 224 Icon.createWithResource( 225 "noooope.wrong.package", 226 R.drawable.dessert_zombiegingerbread 227 ) 228 ) 229 assertThat(loadedDrawable).isNull() 230 } 231 232 @Test 233 fun validBitmapResource_widthMoreRestricted_downsizesKeepingAspectRatio() = 234 testScope.runTest { 235 val loadedDrawable = 236 imageLoader.loadDrawable(ImageLoader.File(imgFile), maxWidth = 160, maxHeight = 160) 237 val loadedBitmap = assertBitmapInDrawable(loadedDrawable) 238 assertThat(loadedBitmap.width).isEqualTo(160) 239 assertThat(loadedBitmap.height).isEqualTo(106) 240 } 241 242 @Test 243 fun validBitmapResource_heightMoreRestricted_downsizesKeepingAspectRatio() = 244 testScope.runTest { 245 val loadedDrawable = 246 imageLoader.loadDrawable(ImageLoader.File(imgFile), maxWidth = 160, maxHeight = 50) 247 val loadedBitmap = assertBitmapInDrawable(loadedDrawable) 248 assertThat(loadedBitmap.width).isEqualTo(74) 249 assertThat(loadedBitmap.height).isEqualTo(50) 250 } 251 252 @Test 253 fun validBitmapResource_onlyWidthRestricted_downsizesKeepingAspectRatio() = 254 testScope.runTest { 255 val loadedDrawable = 256 imageLoader.loadDrawable( 257 ImageLoader.File(imgFile), 258 maxWidth = 160, 259 maxHeight = ImageLoader.DO_NOT_RESIZE 260 ) 261 val loadedBitmap = assertBitmapInDrawable(loadedDrawable) 262 assertThat(loadedBitmap.width).isEqualTo(160) 263 assertThat(loadedBitmap.height).isEqualTo(106) 264 } 265 266 @Test 267 fun validBitmapResource_onlyHeightRestricted_downsizesKeepingAspectRatio() = 268 testScope.runTest { 269 val loadedDrawable = 270 imageLoader.loadDrawable( 271 ImageLoader.Res(R.drawable.bubble_thumbnail), 272 maxWidth = ImageLoader.DO_NOT_RESIZE, 273 maxHeight = 120 274 ) 275 val loadedBitmap = assertBitmapInDrawable(loadedDrawable) 276 assertThat(loadedBitmap.width).isEqualTo(123) 277 assertThat(loadedBitmap.height).isEqualTo(120) 278 } 279 280 @Test 281 fun validVectorDrawable_loadDrawable_successfullyLoaded() = 282 testScope.runTest { 283 val loadedDrawable = imageLoader.loadDrawable(ImageLoader.Res(R.drawable.ic_settings)) 284 assertThat(loadedDrawable).isNotNull() 285 assertThat(loadedDrawable).isInstanceOf(VectorDrawable::class.java) 286 } 287 288 @Test 289 fun validVectorDrawable_loadBitmap_returnsNull() = 290 testScope.runTest { 291 val loadedBitmap = imageLoader.loadBitmap(ImageLoader.Res(R.drawable.ic_settings)) 292 assertThat(loadedBitmap).isNull() 293 } 294 295 @Test 296 fun validVectorDrawableIcon_loadDrawable_successfullyLoaded() = 297 testScope.runTest { 298 val loadedDrawable = 299 imageLoader.loadDrawable(Icon.createWithResource(context, R.drawable.ic_settings)) 300 assertThat(loadedDrawable).isNotNull() 301 assertThat(loadedDrawable).isInstanceOf(VectorDrawable::class.java) 302 } 303 304 @Test 305 fun hardwareAllocator_returnsHardwareBitmap() = 306 testScope.runTest { 307 val loadedDrawable = 308 imageLoader.loadDrawable( 309 ImageLoader.File(imgFile), 310 allocator = ImageDecoder.ALLOCATOR_HARDWARE 311 ) 312 assertThat(loadedDrawable).isNotNull() 313 assertThat((loadedDrawable as BitmapDrawable).bitmap.config) 314 .isEqualTo(Bitmap.Config.HARDWARE) 315 } 316 317 @Test 318 fun softwareAllocator_returnsSoftwareBitmap() = 319 testScope.runTest { 320 val loadedDrawable = 321 imageLoader.loadDrawable( 322 ImageLoader.File(imgFile), 323 allocator = ImageDecoder.ALLOCATOR_SOFTWARE 324 ) 325 assertThat(loadedDrawable).isNotNull() 326 assertThat((loadedDrawable as BitmapDrawable).bitmap.config) 327 .isNotEqualTo(Bitmap.Config.HARDWARE) 328 } 329 330 private fun assertBitmapInDrawable(drawable: Drawable?): Bitmap { 331 assertThat(drawable).isNotNull() 332 assertThat(drawable).isInstanceOf(BitmapDrawable::class.java) 333 return (drawable as BitmapDrawable).bitmap 334 } 335 336 private fun assertBitmapEqualToDrawable(actual: Drawable?, expected: Bitmap) { 337 val actualBitmap = assertBitmapInDrawable(actual) 338 assertBitmapEqualToBitmap(actualBitmap, expected) 339 } 340 341 private fun assertBitmapEqualToBitmap(actual: Bitmap?, expected: Bitmap) { 342 assertThat(actual).isNotNull() 343 assertThat(actual?.width).isEqualTo(expected.width) 344 assertThat(actual?.height).isEqualTo(expected.height) 345 } 346 } 347