fix(non linear font scaling): fix crash with certain negative SP values

The crash was due to some old logic that was looking for near matches.
Now we only return exact matches, and let the interpolation deal with
near matches.

Add test to make sure it doesn't crash at any value passed in.

Test: atest FrameworksCoreTests:android.content.res.FontScaleConverterTest

Bug: b/260984829

Change-Id: Ic90d1c5b48862e4f78b90be5926c9ce6d468acf9
This commit is contained in:
Tyler Freeman
2022-12-01 13:29:05 -08:00
parent da9efd894e
commit bae1102a51
2 changed files with 71 additions and 8 deletions

View File

@@ -36,11 +36,6 @@ import java.util.Arrays;
* @hide
*/
public class FontScaleConverter {
/**
* How close the given SP should be to a canonical SP in the array before they are considered
* the same for lookup purposes.
*/
private static final float THRESHOLD_FOR_MATCHING_SP = 0.02f;
@VisibleForTesting
final float[] mFromSpValues;
@@ -78,10 +73,11 @@ public class FontScaleConverter {
public float convertSpToDp(float sp) {
final float spPositive = Math.abs(sp);
// TODO(b/247861374): find a match at a higher index?
final int spRounded = Math.round(spPositive);
final float sign = Math.signum(sp);
final int index = Arrays.binarySearch(mFromSpValues, spRounded);
if (index >= 0 && Math.abs(spRounded - spPositive) < THRESHOLD_FOR_MATCHING_SP) {
// We search for exact matches only, even if it's just a little off. The interpolation will
// handle any non-exact matches.
final int index = Arrays.binarySearch(mFromSpValues, spPositive);
if (index >= 0) {
// exact match, return the matching dp
return sign * mToDpValues[index];
} else {

View File

@@ -18,8 +18,12 @@ package android.content.res
import androidx.core.util.forEach
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlin.math.ceil
import kotlin.math.floor
import org.junit.Test
import org.junit.runner.RunWith
@@ -72,7 +76,70 @@ class FontScaleConverterFactoryTest {
}
}
@LargeTest
@Test
fun allFeasibleScalesAndConversionsDoNotCrash() {
generateSequenceOfFractions(-10000f..10000f, step = 0.01f)
.mapNotNull{ FontScaleConverterFactory.forScale(it) }
.flatMap{ table ->
generateSequenceOfFractions(-10000f..10000f, step = 0.01f)
.map{ Pair(table, it) }
}
.forEach { (table, sp) ->
try {
assertWithMessage(
"convertSpToDp(%s) on table: %s",
sp.toString(),
table.toString()
)
.that(table.convertSpToDp(sp))
.isFinite()
} catch (e: Exception) {
throw AssertionError("Exception during convertSpToDp($sp) on table: $table", e)
}
}
}
@Test
fun testGenerateSequenceOfFractions() {
val fractions = generateSequenceOfFractions(-1000f..1000f, step = 0.1f)
.toList()
fractions.forEach {
assertThat(it).isAtLeast(-1000f)
assertThat(it).isAtMost(1000f)
}
assertThat(fractions).isInStrictOrder()
assertThat(fractions).hasSize(1000 * 2 * 10 + 1) // Don't forget the 0 in the middle!
assertThat(fractions).contains(100f)
assertThat(fractions).contains(500.1f)
assertThat(fractions).contains(500.2f)
assertThat(fractions).contains(0.2f)
assertThat(fractions).contains(0f)
assertThat(fractions).contains(-10f)
assertThat(fractions).contains(-10f)
assertThat(fractions).contains(-10.3f)
assertThat(fractions).doesNotContain(-10.31f)
assertThat(fractions).doesNotContain(0.35f)
assertThat(fractions).doesNotContain(0.31f)
assertThat(fractions).doesNotContain(-.35f)
}
companion object {
private const val CONVERSION_TOLERANCE = 0.05f
}
}
fun generateSequenceOfFractions(
range: ClosedFloatingPointRange<Float>,
step: Float
): Sequence<Float> {
val multiplier = 1f / step
val start = floor(range.start * multiplier).toInt()
val endInclusive = ceil(range.endInclusive * multiplier).toInt()
return generateSequence(start) { it + 1 }
.takeWhile { it <= endInclusive }
.map{ it.toFloat() / multiplier }
}