summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author András Kurucz <kurucz@google.com> 2023-08-28 10:14:19 +0000
committer András Kurucz <kurucz@google.com> 2023-08-29 16:18:54 +0000
commit0f5a7ec757124d03dc2aaa51d7a84eebf4f398fb (patch)
treef54b44f9fd30cbf0662ae74a9b6975c509ab096d
parentc143935fa463b1d592adf0e8f2253b90e8ec91b9 (diff)
Add ability to decode only the image size with ImageLoader
Add methods to be able to read the image size without decoding the full image. Bug: 283082473 Test: atest ImageLoaderTest Change-Id: I9c5c7f7fd08b17b56dc6adf1ac173e8561bf8c13
-rw-r--r--packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt112
2 files changed, 158 insertions, 2 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
index c41b5e4d319b..285601116d0b 100644
--- a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -366,6 +366,52 @@ constructor(
}
}
+ /**
+ * Obtains the image size from the image header, without decoding the full image.
+ *
+ * @param icon an [Icon] representing the source of the image
+ * @return the [Size] if it could be determined from the image header, or `null` otherwise
+ */
+ suspend fun loadSize(icon: Icon, context: Context): Size? =
+ withContext(backgroundDispatcher) { loadSizeSync(icon, context) }
+
+ /**
+ * Obtains the image size from the image header, without decoding the full image.
+ *
+ * @param icon an [Icon] representing the source of the image
+ * @return the [Size] if it could be determined from the image header, or `null` otherwise
+ */
+ @WorkerThread
+ fun loadSizeSync(icon: Icon, context: Context): Size? {
+ return when (icon.type) {
+ Icon.TYPE_URI,
+ Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+ val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
+ loadSizeSync(source)
+ }
+ else -> null
+ }
+ }
+
+ /**
+ * Obtains the image size from the image header, without decoding the full image.
+ *
+ * @param source [ImageDecoder.Source] of the image
+ * @return the [Size] if it could be determined from the image header, or `null` otherwise
+ */
+ @WorkerThread
+ fun loadSizeSync(source: ImageDecoder.Source): Size? {
+ return try {
+ ImageDecoder.decodeHeader(source).size
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
+ }
+ }
+
companion object {
const val TAG = "ImageLoader"
@@ -452,7 +498,7 @@ constructor(
* originate from other processes so we need to make sure we load them from the right
* package source.
*
- * @return [Resources] to load the icon drawble or null if icon doesn't carry a resource or
+ * @return [Resources] to load the icon drawable or null if icon doesn't carry a resource or
* the resource package couldn't be resolved.
*/
@WorkerThread
diff --git a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
index ccd631ec37d0..8f6634478617 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
@@ -9,6 +9,7 @@ import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.graphics.drawable.VectorDrawable
import android.net.Uri
+import android.util.Size
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -78,12 +79,19 @@ class ImageLoaderTest : SysuiTestCase() {
}
@Test
- fun invalidIcon_returnsNull() =
+ fun invalidIcon_loadDrawable_returnsNull() =
testScope.runTest {
assertThat(imageLoader.loadDrawable(Icon.createWithFilePath("this is broken"))).isNull()
}
@Test
+ fun invalidIcon_loadSize_returnsNull() =
+ testScope.runTest {
+ assertThat(imageLoader.loadSize(Icon.createWithFilePath("this is broken"), context))
+ .isNull()
+ }
+
+ @Test
fun invalidIS_returnsNull() =
testScope.runTest {
assertThat(
@@ -172,6 +180,17 @@ class ImageLoaderTest : SysuiTestCase() {
}
@Test
+ fun validBitmapIcon_loadSize_returnsNull() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ assertThat(imageLoader.loadSize(Icon.createWithBitmap(bitmap), context)).isNull()
+ }
+
+ @Test
fun validUriIcon_returnsBitmapDrawable() =
testScope.runTest {
val bitmap =
@@ -186,6 +205,17 @@ class ImageLoaderTest : SysuiTestCase() {
}
@Test
+ fun validUriIcon_returnsSize() =
+ testScope.runTest {
+ val drawable = context.resources.getDrawable(R.drawable.dessert_zombiegingerbread)
+ val uri =
+ "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}"
+ val loadedSize =
+ imageLoader.loadSize(Icon.createWithContentUri(Uri.parse(uri)), context)
+ assertSizeEqualToDrawableSize(loadedSize, drawable)
+ }
+
+ @Test
fun validDataIcon_returnsBitmapDrawable() =
testScope.runTest {
val bitmap =
@@ -205,6 +235,54 @@ class ImageLoaderTest : SysuiTestCase() {
}
@Test
+ fun validDataIcon_loadSize_returnsNull() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val bos =
+ ByteArrayOutputStream(
+ bitmap.byteCount * 2
+ ) // Compressed bitmap should be smaller than its source.
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos)
+
+ val array = bos.toByteArray()
+ assertThat(imageLoader.loadSize(Icon.createWithData(array, 0, array.size), context))
+ .isNull()
+ }
+
+ @Test
+ fun validResourceIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap = context.resources.getDrawable(R.drawable.dessert_zombiegingerbread)
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ Icon.createWithResource(
+ "com.android.systemui.tests",
+ R.drawable.dessert_zombiegingerbread
+ )
+ )
+ assertBitmapEqualToDrawable(loadedDrawable, (bitmap as BitmapDrawable).bitmap)
+ }
+
+ @Test
+ fun validResourceIcon_loadSize_returnsNull() =
+ testScope.runTest {
+ assertThat(
+ imageLoader.loadSize(
+ Icon.createWithResource(
+ "com.android.systemui.tests",
+ R.drawable.dessert_zombiegingerbread
+ ),
+ context
+ )
+ )
+ .isNull()
+ }
+
+ @Test
fun validSystemResourceIcon_returnsBitmapDrawable() =
testScope.runTest {
val bitmap =
@@ -217,6 +295,18 @@ class ImageLoaderTest : SysuiTestCase() {
}
@Test
+ fun validSystemResourceIcon_loadSize_returnsNull() =
+ testScope.runTest {
+ assertThat(
+ imageLoader.loadSize(
+ Icon.createWithResource("android", android.R.drawable.ic_dialog_alert),
+ context
+ )
+ )
+ .isNull()
+ }
+
+ @Test
fun invalidDifferentPackageResourceIcon_returnsNull() =
testScope.runTest {
val loadedDrawable =
@@ -230,6 +320,20 @@ class ImageLoaderTest : SysuiTestCase() {
}
@Test
+ fun invalidDifferentPackageResourceIcon_loadSize_returnsNull() =
+ testScope.runTest {
+ assertThat(
+ imageLoader.loadDrawable(
+ Icon.createWithResource(
+ "noooope.wrong.package",
+ R.drawable.dessert_zombiegingerbread
+ )
+ )
+ )
+ .isNull()
+ }
+
+ @Test
fun validBitmapResource_widthMoreRestricted_downsizesKeepingAspectRatio() =
testScope.runTest {
val loadedDrawable =
@@ -343,4 +447,10 @@ class ImageLoaderTest : SysuiTestCase() {
assertThat(actual?.width).isEqualTo(expected.width)
assertThat(actual?.height).isEqualTo(expected.height)
}
+
+ private fun assertSizeEqualToDrawableSize(actual: Size?, expected: Drawable) {
+ assertThat(actual).isNotNull()
+ assertThat(actual?.width).isEqualTo(expected.intrinsicWidth)
+ assertThat(actual?.height).isEqualTo(expected.intrinsicHeight)
+ }
}