Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0f5a7ec7 authored by András Kurucz's avatar András Kurucz
Browse files

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
parent c143935f
Loading
Loading
Loading
Loading
+47 −1
Original line number Original line Diff line number Diff line
@@ -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 {
    companion object {
        const val TAG = "ImageLoader"
        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
         * originate from other processes so we need to make sure we load them from the right
         * package source.
         * 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.
         *   the resource package couldn't be resolved.
         */
         */
        @WorkerThread
        @WorkerThread
+111 −1
Original line number Original line Diff line number Diff line
@@ -9,6 +9,7 @@ import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.graphics.drawable.Icon
import android.graphics.drawable.VectorDrawable
import android.graphics.drawable.VectorDrawable
import android.net.Uri
import android.net.Uri
import android.util.Size
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.R
@@ -78,11 +79,18 @@ class ImageLoaderTest : SysuiTestCase() {
        }
        }


    @Test
    @Test
    fun invalidIcon_returnsNull() =
    fun invalidIcon_loadDrawable_returnsNull() =
        testScope.runTest {
        testScope.runTest {
            assertThat(imageLoader.loadDrawable(Icon.createWithFilePath("this is broken"))).isNull()
            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
    @Test
    fun invalidIS_returnsNull() =
    fun invalidIS_returnsNull() =
        testScope.runTest {
        testScope.runTest {
@@ -171,6 +179,17 @@ class ImageLoaderTest : SysuiTestCase() {
            assertBitmapEqualToDrawable(loadedDrawable, bitmap)
            assertBitmapEqualToDrawable(loadedDrawable, bitmap)
        }
        }


    @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
    @Test
    fun validUriIcon_returnsBitmapDrawable() =
    fun validUriIcon_returnsBitmapDrawable() =
        testScope.runTest {
        testScope.runTest {
@@ -185,6 +204,17 @@ class ImageLoaderTest : SysuiTestCase() {
            assertBitmapEqualToDrawable(loadedDrawable, bitmap)
            assertBitmapEqualToDrawable(loadedDrawable, bitmap)
        }
        }


    @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
    @Test
    fun validDataIcon_returnsBitmapDrawable() =
    fun validDataIcon_returnsBitmapDrawable() =
        testScope.runTest {
        testScope.runTest {
@@ -204,6 +234,54 @@ class ImageLoaderTest : SysuiTestCase() {
            assertBitmapEqualToDrawable(loadedDrawable, bitmap)
            assertBitmapEqualToDrawable(loadedDrawable, bitmap)
        }
        }


    @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
    @Test
    fun validSystemResourceIcon_returnsBitmapDrawable() =
    fun validSystemResourceIcon_returnsBitmapDrawable() =
        testScope.runTest {
        testScope.runTest {
@@ -216,6 +294,18 @@ class ImageLoaderTest : SysuiTestCase() {
            assertBitmapEqualToDrawable(loadedDrawable, (bitmap as BitmapDrawable).bitmap)
            assertBitmapEqualToDrawable(loadedDrawable, (bitmap as BitmapDrawable).bitmap)
        }
        }


    @Test
    fun validSystemResourceIcon_loadSize_returnsNull() =
        testScope.runTest {
            assertThat(
                    imageLoader.loadSize(
                        Icon.createWithResource("android", android.R.drawable.ic_dialog_alert),
                        context
                    )
                )
                .isNull()
        }

    @Test
    @Test
    fun invalidDifferentPackageResourceIcon_returnsNull() =
    fun invalidDifferentPackageResourceIcon_returnsNull() =
        testScope.runTest {
        testScope.runTest {
@@ -229,6 +319,20 @@ class ImageLoaderTest : SysuiTestCase() {
            assertThat(loadedDrawable).isNull()
            assertThat(loadedDrawable).isNull()
        }
        }


    @Test
    fun invalidDifferentPackageResourceIcon_loadSize_returnsNull() =
        testScope.runTest {
            assertThat(
                    imageLoader.loadDrawable(
                        Icon.createWithResource(
                            "noooope.wrong.package",
                            R.drawable.dessert_zombiegingerbread
                        )
                    )
                )
                .isNull()
        }

    @Test
    @Test
    fun validBitmapResource_widthMoreRestricted_downsizesKeepingAspectRatio() =
    fun validBitmapResource_widthMoreRestricted_downsizesKeepingAspectRatio() =
        testScope.runTest {
        testScope.runTest {
@@ -343,4 +447,10 @@ class ImageLoaderTest : SysuiTestCase() {
        assertThat(actual?.width).isEqualTo(expected.width)
        assertThat(actual?.width).isEqualTo(expected.width)
        assertThat(actual?.height).isEqualTo(expected.height)
        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)
    }
}
}