Loading packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt +29 −2 Original line number Diff line number Diff line Loading @@ -35,7 +35,7 @@ class DrawableSizeTest : SysuiTestCase() { val drawable = BitmapDrawable( resources, Bitmap.createBitmap(resources.displayMetrics, 150, 150, Bitmap.Config.ARGB_8888) Bitmap.createBitmap(resources.displayMetrics, 150, 150, Bitmap.Config.ARGB_8888), ) val result = DrawableSize.downscaleToSize(resources, drawable, 300, 300) assertThat(result).isSameInstanceAs(drawable) Loading @@ -48,7 +48,7 @@ class DrawableSizeTest : SysuiTestCase() { val drawable = BitmapDrawable( resources, Bitmap.createBitmap(resources.displayMetrics, 150, 75, Bitmap.Config.ARGB_8888) Bitmap.createBitmap(resources.displayMetrics, 150, 75, Bitmap.Config.ARGB_8888), ) val result = DrawableSize.downscaleToSize(resources, drawable, 75, 75) Loading @@ -64,4 +64,31 @@ class DrawableSizeTest : SysuiTestCase() { val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1) assertThat(result).isSameInstanceAs(drawable) } @Test fun testDownscaleToSize_layerDrawable_allLayersSameType_resized() { val drawable = resources.getDrawable( com.android.systemui.tests.R.drawable.layer_drawable_all_same_type, resources.newTheme(), ) val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1) assertThat(result).isNotSameInstanceAs(drawable) } /** Regression test for b/244282477. */ @Test fun testDownscaleToSize_layerDrawable_layersAreDifferentTypes_unchanged() { val drawable = resources.getDrawable( com.android.systemui.tests.R.drawable.layer_drawable_different_types, resources.newTheme(), ) val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1) assertThat(result).isSameInstanceAs(drawable) } } packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt +59 −24 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ import android.graphics.drawable.AnimatedStateListDrawable import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable import android.util.Log import androidx.annotation.Px import com.android.app.tracing.traceSection Loading @@ -22,18 +23,17 @@ class DrawableSize { const val TAG = "SysUiDrawableSize" /** * Downscales passed Drawable to set maximum width and height. This will only * be done for Drawables that can be downscaled non-destructively - e.g. animated * and stateful drawables will no be downscaled. * Downscales passed Drawable to set maximum width and height. This will only be done for * Drawables that can be downscaled non-destructively - e.g. animated drawables, stateful * drawables, and drawables with mixed-type layers will not be downscaled. * * Downscaling will keep the aspect ratio. * This method will not touch drawables that already fit into size specification. * Downscaling will keep the aspect ratio. This method will not touch drawables that already * fit into size specification. * * @param resources Resources on which to base the density of resized drawable. * @param drawable Drawable to downscale. * @param maxWidth Maximum width of the downscaled drawable. * @param maxHeight Maximum height of the downscaled drawable. * * @return returns downscaled drawable if it's possible to downscale it or original if it's * not. */ Loading @@ -42,16 +42,16 @@ class DrawableSize { res: Resources, drawable: Drawable, @Px maxWidth: Int, @Px maxHeight: Int @Px maxHeight: Int, ): Drawable { traceSection("DrawableSize#downscaleToSize") { // Bitmap drawables can contain big bitmaps as their content while sneaking it past // us using density scaling. Inspect inside the Bitmap drawables for actual bitmap // size for those. val originalWidth = (drawable as? BitmapDrawable)?.bitmap?.width ?: drawable.intrinsicWidth val originalHeight = (drawable as? BitmapDrawable)?.bitmap?.height ?: drawable.intrinsicHeight val originalWidth = (drawable as? BitmapDrawable)?.bitmap?.width ?: drawable.intrinsicWidth val originalHeight = (drawable as? BitmapDrawable)?.bitmap?.height ?: drawable.intrinsicHeight // Don't touch drawable if we can't resolve sizes for whatever reason. if (originalWidth <= 0 || originalHeight <= 0) { Loading @@ -61,14 +61,18 @@ class DrawableSize { // Do not touch drawables that are already within bounds. if (originalWidth < maxWidth && originalHeight < maxHeight) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Not resizing $originalWidth x $originalHeight" + " " + "to $maxWidth x $maxHeight") Log.d( TAG, "Not resizing $originalWidth x $originalHeight" + " " + "to $maxWidth x $maxHeight", ) } return drawable } if (!isSimpleBitmap(drawable)) { if (isComplicatedBitmap(drawable)) { return drawable } Loading @@ -80,19 +84,25 @@ class DrawableSize { val height = (originalHeight * scale).toInt() if (width <= 0 || height <= 0) { Log.w(TAG, "Attempted to resize ${drawable.javaClass.simpleName} " + "from $originalWidth x $originalHeight to invalid $width x $height.") Log.w( TAG, "Attempted to resize ${drawable.javaClass.simpleName} " + "from $originalWidth x $originalHeight to invalid $width x $height.", ) return drawable } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Resizing large drawable (${drawable.javaClass.simpleName}) " + "from $originalWidth x $originalHeight to $width x $height") Log.d( TAG, "Resizing large drawable (${drawable.javaClass.simpleName}) " + "from $originalWidth x $originalHeight to $width x $height", ) } // We want to keep existing config if it's more efficient than 32-bit RGB. val config = (drawable as? BitmapDrawable)?.bitmap?.config ?: Bitmap.Config.ARGB_8888 val config = (drawable as? BitmapDrawable)?.bitmap?.config ?: Bitmap.Config.ARGB_8888 val scaledDrawableBitmap = Bitmap.createBitmap(width, height, config) val canvas = Canvas(scaledDrawableBitmap) Loading @@ -105,8 +115,8 @@ class DrawableSize { } } private fun isSimpleBitmap(drawable: Drawable): Boolean { return !(drawable.isStateful || isAnimated(drawable)) private fun isComplicatedBitmap(drawable: Drawable): Boolean { return drawable.isStateful || isAnimated(drawable) || hasComplicatedLayers(drawable) } private fun isAnimated(drawable: Drawable): Boolean { Loading @@ -119,5 +129,30 @@ class DrawableSize { drawable is AnimatedStateListDrawable || drawable is AnimatedVectorDrawable } private fun hasComplicatedLayers(drawable: Drawable): Boolean { if (drawable !is LayerDrawable) { return false } if (drawable.numberOfLayers == 1) { return false } val firstLayerType = drawable.getDrawable(0).javaClass for (i in 1..<drawable.numberOfLayers) { val layer = drawable.getDrawable(i) if (layer.javaClass != firstLayerType) { // If different layers have different drawable types, we shouldn't scale it down // because we may lose the level information if one of the layers is a bitmap // and another layer is a level-list. See b/244282477. return true } if (isComplicatedBitmap(layer)) { return true } } return false } } } packages/SystemUI/tests/res/drawable/layer_drawable_all_same_type.xml 0 → 100644 +19 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?><!-- ~ Copyright (C) 2024 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. --> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <item android:drawable="@drawable/ic_brightness"/> <item android:drawable="@drawable/ic_brightness"/> </layer-list> packages/SystemUI/tests/res/drawable/layer_drawable_different_types.xml 0 → 100644 +20 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?><!-- ~ Copyright (C) 2024 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. --> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <!-- dessert_flan is a PNG while ic_brightness is a level-list. --> <item android:drawable="@drawable/dessert_flan"/> <item android:drawable="@drawable/ic_brightness"/> </layer-list> Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt +29 −2 Original line number Diff line number Diff line Loading @@ -35,7 +35,7 @@ class DrawableSizeTest : SysuiTestCase() { val drawable = BitmapDrawable( resources, Bitmap.createBitmap(resources.displayMetrics, 150, 150, Bitmap.Config.ARGB_8888) Bitmap.createBitmap(resources.displayMetrics, 150, 150, Bitmap.Config.ARGB_8888), ) val result = DrawableSize.downscaleToSize(resources, drawable, 300, 300) assertThat(result).isSameInstanceAs(drawable) Loading @@ -48,7 +48,7 @@ class DrawableSizeTest : SysuiTestCase() { val drawable = BitmapDrawable( resources, Bitmap.createBitmap(resources.displayMetrics, 150, 75, Bitmap.Config.ARGB_8888) Bitmap.createBitmap(resources.displayMetrics, 150, 75, Bitmap.Config.ARGB_8888), ) val result = DrawableSize.downscaleToSize(resources, drawable, 75, 75) Loading @@ -64,4 +64,31 @@ class DrawableSizeTest : SysuiTestCase() { val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1) assertThat(result).isSameInstanceAs(drawable) } @Test fun testDownscaleToSize_layerDrawable_allLayersSameType_resized() { val drawable = resources.getDrawable( com.android.systemui.tests.R.drawable.layer_drawable_all_same_type, resources.newTheme(), ) val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1) assertThat(result).isNotSameInstanceAs(drawable) } /** Regression test for b/244282477. */ @Test fun testDownscaleToSize_layerDrawable_layersAreDifferentTypes_unchanged() { val drawable = resources.getDrawable( com.android.systemui.tests.R.drawable.layer_drawable_different_types, resources.newTheme(), ) val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1) assertThat(result).isSameInstanceAs(drawable) } }
packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt +59 −24 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ import android.graphics.drawable.AnimatedStateListDrawable import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable import android.util.Log import androidx.annotation.Px import com.android.app.tracing.traceSection Loading @@ -22,18 +23,17 @@ class DrawableSize { const val TAG = "SysUiDrawableSize" /** * Downscales passed Drawable to set maximum width and height. This will only * be done for Drawables that can be downscaled non-destructively - e.g. animated * and stateful drawables will no be downscaled. * Downscales passed Drawable to set maximum width and height. This will only be done for * Drawables that can be downscaled non-destructively - e.g. animated drawables, stateful * drawables, and drawables with mixed-type layers will not be downscaled. * * Downscaling will keep the aspect ratio. * This method will not touch drawables that already fit into size specification. * Downscaling will keep the aspect ratio. This method will not touch drawables that already * fit into size specification. * * @param resources Resources on which to base the density of resized drawable. * @param drawable Drawable to downscale. * @param maxWidth Maximum width of the downscaled drawable. * @param maxHeight Maximum height of the downscaled drawable. * * @return returns downscaled drawable if it's possible to downscale it or original if it's * not. */ Loading @@ -42,16 +42,16 @@ class DrawableSize { res: Resources, drawable: Drawable, @Px maxWidth: Int, @Px maxHeight: Int @Px maxHeight: Int, ): Drawable { traceSection("DrawableSize#downscaleToSize") { // Bitmap drawables can contain big bitmaps as their content while sneaking it past // us using density scaling. Inspect inside the Bitmap drawables for actual bitmap // size for those. val originalWidth = (drawable as? BitmapDrawable)?.bitmap?.width ?: drawable.intrinsicWidth val originalHeight = (drawable as? BitmapDrawable)?.bitmap?.height ?: drawable.intrinsicHeight val originalWidth = (drawable as? BitmapDrawable)?.bitmap?.width ?: drawable.intrinsicWidth val originalHeight = (drawable as? BitmapDrawable)?.bitmap?.height ?: drawable.intrinsicHeight // Don't touch drawable if we can't resolve sizes for whatever reason. if (originalWidth <= 0 || originalHeight <= 0) { Loading @@ -61,14 +61,18 @@ class DrawableSize { // Do not touch drawables that are already within bounds. if (originalWidth < maxWidth && originalHeight < maxHeight) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Not resizing $originalWidth x $originalHeight" + " " + "to $maxWidth x $maxHeight") Log.d( TAG, "Not resizing $originalWidth x $originalHeight" + " " + "to $maxWidth x $maxHeight", ) } return drawable } if (!isSimpleBitmap(drawable)) { if (isComplicatedBitmap(drawable)) { return drawable } Loading @@ -80,19 +84,25 @@ class DrawableSize { val height = (originalHeight * scale).toInt() if (width <= 0 || height <= 0) { Log.w(TAG, "Attempted to resize ${drawable.javaClass.simpleName} " + "from $originalWidth x $originalHeight to invalid $width x $height.") Log.w( TAG, "Attempted to resize ${drawable.javaClass.simpleName} " + "from $originalWidth x $originalHeight to invalid $width x $height.", ) return drawable } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Resizing large drawable (${drawable.javaClass.simpleName}) " + "from $originalWidth x $originalHeight to $width x $height") Log.d( TAG, "Resizing large drawable (${drawable.javaClass.simpleName}) " + "from $originalWidth x $originalHeight to $width x $height", ) } // We want to keep existing config if it's more efficient than 32-bit RGB. val config = (drawable as? BitmapDrawable)?.bitmap?.config ?: Bitmap.Config.ARGB_8888 val config = (drawable as? BitmapDrawable)?.bitmap?.config ?: Bitmap.Config.ARGB_8888 val scaledDrawableBitmap = Bitmap.createBitmap(width, height, config) val canvas = Canvas(scaledDrawableBitmap) Loading @@ -105,8 +115,8 @@ class DrawableSize { } } private fun isSimpleBitmap(drawable: Drawable): Boolean { return !(drawable.isStateful || isAnimated(drawable)) private fun isComplicatedBitmap(drawable: Drawable): Boolean { return drawable.isStateful || isAnimated(drawable) || hasComplicatedLayers(drawable) } private fun isAnimated(drawable: Drawable): Boolean { Loading @@ -119,5 +129,30 @@ class DrawableSize { drawable is AnimatedStateListDrawable || drawable is AnimatedVectorDrawable } private fun hasComplicatedLayers(drawable: Drawable): Boolean { if (drawable !is LayerDrawable) { return false } if (drawable.numberOfLayers == 1) { return false } val firstLayerType = drawable.getDrawable(0).javaClass for (i in 1..<drawable.numberOfLayers) { val layer = drawable.getDrawable(i) if (layer.javaClass != firstLayerType) { // If different layers have different drawable types, we shouldn't scale it down // because we may lose the level information if one of the layers is a bitmap // and another layer is a level-list. See b/244282477. return true } if (isComplicatedBitmap(layer)) { return true } } return false } } }
packages/SystemUI/tests/res/drawable/layer_drawable_all_same_type.xml 0 → 100644 +19 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?><!-- ~ Copyright (C) 2024 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. --> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <item android:drawable="@drawable/ic_brightness"/> <item android:drawable="@drawable/ic_brightness"/> </layer-list>
packages/SystemUI/tests/res/drawable/layer_drawable_different_types.xml 0 → 100644 +20 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?><!-- ~ Copyright (C) 2024 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. --> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <!-- dessert_flan is a PNG while ic_brightness is a level-list. --> <item android:drawable="@drawable/dessert_flan"/> <item android:drawable="@drawable/ic_brightness"/> </layer-list>