Restrict image size when loading via URI

If images are larger than the maximum, sets a target size in ImageDecoder.
Also moved the scaling logic shared with MediaControlPanel to a util method

Fixes: 261849187
Test: atest MediaDataUtilsTest MediaControlPanelTest
Test: manual - check hprof before/after change
Test: manual - verify both square and rectangular images are not distorted
Change-Id: I997adb11e5c9278bcb54e5f6d6d68c6c1845c709
This commit is contained in:
Beth Thibodeau
2023-04-12 14:17:34 -05:00
parent 844c5aec48
commit 49e5fb4854
4 changed files with 135 additions and 18 deletions

View File

@@ -49,6 +49,7 @@ import android.service.notification.StatusBarNotification
import android.support.v4.media.MediaMetadataCompat
import android.text.TextUtils
import android.util.Log
import android.util.Pair as APair
import androidx.media.utils.MediaConstants
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
@@ -217,6 +218,13 @@ class MediaDataManager(
private var smartspaceSession: SmartspaceSession? = null
private var allowMediaRecommendations = allowMediaRecommendations(context)
private val artworkWidth =
context.resources.getDimensionPixelSize(
com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize
)
private val artworkHeight =
context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
/** Check whether this notification is an RCN */
private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean {
return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
@@ -1250,9 +1258,21 @@ class MediaDataManager(
return null
}
val source = ImageDecoder.createSource(context.getContentResolver(), uri)
val source = ImageDecoder.createSource(context.contentResolver, uri)
return try {
ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
val width = info.size.width
val height = info.size.height
val scale =
MediaDataUtils.getScaleFactor(
APair(width, height),
APair(artworkWidth, artworkHeight)
)
// Downscale if needed
if (scale != 0f && scale < 1) {
decoder.setTargetSize((scale * width).toInt(), (scale * height).toInt())
}
decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
}
} catch (e: IOException) {

View File

@@ -56,6 +56,7 @@ import android.os.Process;
import android.os.Trace;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
@@ -122,6 +123,11 @@ import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
import dagger.Lazy;
import kotlin.Triple;
import kotlin.Unit;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
@@ -129,10 +135,6 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
import dagger.Lazy;
import kotlin.Triple;
import kotlin.Unit;
/**
* A view controller used for Media Playback.
*/
@@ -1000,18 +1002,9 @@ public class MediaControlPanel {
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
if (width == 0 || height == 0 || targetWidth == 0 || targetHeight == 0) {
return;
}
float scale;
if ((width / (float) height) > (targetWidth / (float) targetHeight)) {
// Drawable is wider than target view, scale to match height
scale = targetHeight / (float) height;
} else {
// Drawable is taller than target view, scale to match width
scale = targetWidth / (float) width;
}
float scale = MediaDataUtils.getScaleFactor(new Pair(width, height),
new Pair(targetWidth, targetHeight));
if (scale == 0) return;
transitionDrawable.setLayerSize(layer, (int) (scale * width), (int) (scale * height));
}

View File

@@ -22,6 +22,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Pair;
import androidx.core.math.MathUtils;
import androidx.media.utils.MediaConstants;
@@ -87,4 +88,32 @@ public class MediaDataUtils {
}
return null;
}
/**
* Calculate a scale factor that will allow the input to fill the target size.
*
* @param input width, height of the input view
* @param target width, height of the target view
* @return the scale factor; 0 if any given dimension is 0
*/
public static float getScaleFactor(Pair<Integer, Integer> input,
Pair<Integer, Integer> target) {
float width = (float) input.first;
float height = (float) input.second;
float targetWidth = (float) target.first;
float targetHeight = (float) target.second;
if (width == 0 || height == 0 || targetWidth == 0 || targetHeight == 0) {
return 0f;
}
if ((width / height) > (targetWidth / targetHeight)) {
// Input is wider than target view, scale to match height
return targetHeight / height;
} else {
// Input is taller than target view, scale to match width
return targetWidth / width;
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2023 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.
*/
package com.android.systemui.media.controls.util
import android.testing.AndroidTestingRunner
import android.util.Pair as APair
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidTestingRunner::class)
class MediaDataUtilsTest : SysuiTestCase() {
@Test
fun testScaleFactor_zeroInput_returnsZero() {
val input = APair(0, 0)
val target = APair(100, 100)
val scale = MediaDataUtils.getScaleFactor(input, target)
assertThat(scale).isEqualTo(0f)
}
@Test
fun testScaleFactor_tooWide_scaleDown() {
val input = APair(400, 200)
val target = APair(100, 100)
val scale = MediaDataUtils.getScaleFactor(input, target)
assertThat(scale).isEqualTo(0.5f)
}
@Test
fun testScaleFactor_tooTall_scaleDown() {
val input = APair(200, 400)
val target = APair(100, 100)
val scale = MediaDataUtils.getScaleFactor(input, target)
assertThat(scale).isEqualTo(0.5f)
}
@Test
fun testScaleFactor_lessWide_scaleUp() {
val input = APair(50, 100)
val target = APair(100, 100)
val scale = MediaDataUtils.getScaleFactor(input, target)
assertThat(scale).isEqualTo(2f)
}
@Test
fun testScaleFactor_lessTall_scaleUp() {
val input = APair(100, 50)
val target = APair(100, 100)
val scale = MediaDataUtils.getScaleFactor(input, target)
assertThat(scale).isEqualTo(2f)
}
}