summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Tyler Freeman <fuego@google.com> 2023-12-08 15:31:37 -0800
committer Tyler Freeman <fuego@google.com> 2023-12-08 15:31:37 -0800
commitf23efb69cebaf3f51966fdfe7007d99dff2f0ab9 (patch)
tree4a0157478d3357b73a4a9dcc3bee0fa215ba2c47
parent44366c963140081c76d02f15ff6d3f11ed5b3b03 (diff)
feat(non linear font scaling): make FontScaleConverter APIs public
This allows other UI frameworks (Compose, Flutter, etc) to use the conversions directly without needing a Context or display density. Moved some tests over to CTS. Bug: 239736383 Flag: android.content.res.font_scale_converter_public Test: atest FrameworksCoreTests:android.content.res.FontScaleConverterActivityTest && atest FrameworksCoreTests:android.content.res.FontScaleConverterTest && atest FrameworksCoreTests:android.content.res.FontScaleConverterFactoryTest && atest cts/tests/tests/content/src/android/content/res/cts/FontScaleConverterFactoryTest.kt Change-Id: I824a9e7dbd8489bf316064eadd62c1c082a12de5
-rw-r--r--core/api/current.txt10
-rw-r--r--core/java/android/content/res/FontScaleConverter.java140
-rw-r--r--core/java/android/content/res/FontScaleConverterFactory.java29
-rw-r--r--core/java/android/content/res/FontScaleConverterImpl.java164
-rw-r--r--core/java/android/content/res/flags.aconfig9
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt9
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt4
7 files changed, 219 insertions, 146 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index e8a6ac944076..4fd8cf045007 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13578,6 +13578,16 @@ package android.content.res {
field public int uiMode;
}
+ @FlaggedApi("android.content.res.font_scale_converter_public") public interface FontScaleConverter {
+ method public float convertDpToSp(float);
+ method public float convertSpToDp(float);
+ }
+
+ @FlaggedApi("android.content.res.font_scale_converter_public") public class FontScaleConverterFactory {
+ method @FlaggedApi("android.content.res.font_scale_converter_public") @Nullable public static android.content.res.FontScaleConverter forScale(float);
+ method @FlaggedApi("android.content.res.font_scale_converter_public") public static boolean isNonLinearFontScalingActive(float);
+ }
+
public class ObbInfo implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java
index 939f85dc6c5f..088949e7eec2 100644
--- a/core/java/android/content/res/FontScaleConverter.java
+++ b/core/java/android/content/res/FontScaleConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -16,15 +16,11 @@
package android.content.res;
-import android.annotation.NonNull;
-import android.util.MathUtils;
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Arrays;
+import android.annotation.FlaggedApi;
/**
- * A lookup table for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
+ * A converter for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
* "dp" dimension according to a non-linear curve.
*
* <p>This is meant to improve readability at larger font scales: larger fonts will scale up more
@@ -32,134 +28,16 @@ import java.util.Arrays;
*
* <p>The thinking here is that large fonts are already big enough to read, but we still want to
* scale them slightly to preserve the visual hierarchy when compared to smaller fonts.
- *
- * @hide
*/
-public class FontScaleConverter {
-
- /** @hide */
- @VisibleForTesting
- public final float[] mFromSpValues;
-
- /** @hide */
- @VisibleForTesting
- public final float[] mToDpValues;
-
- /**
- * Creates a lookup table for the given conversions.
- *
- * <p>Any "sp" value not in the lookup table will be derived via linear interpolation.
- *
- * <p>The arrays must be sorted ascending and monotonically increasing.
- *
- * @param fromSp array of dimensions in SP
- * @param toDp array of dimensions in DP that correspond to an SP value in fromSp
- *
- * @throws IllegalArgumentException if the array lengths don't match or are empty
- * @hide
- */
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public FontScaleConverter(@NonNull float[] fromSp, @NonNull float[] toDp) {
- if (fromSp.length != toDp.length || fromSp.length == 0) {
- throw new IllegalArgumentException("Array lengths must match and be nonzero");
- }
-
- mFromSpValues = fromSp;
- mToDpValues = toDp;
- }
-
+@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
+public interface FontScaleConverter {
/**
- * Convert a dimension in "dp" back to "sp" using the lookup table.
- *
- * @hide
+ * Converts a dimension in "sp" to "dp".
*/
- public float convertDpToSp(float dp) {
- return lookupAndInterpolate(dp, mToDpValues, mFromSpValues);
- }
+ float convertSpToDp(float sp);
/**
- * Convert a dimension in "sp" to "dp" using the lookup table.
- *
- * @hide
+ * Converts a dimension in "dp" back to "sp".
*/
- public float convertSpToDp(float sp) {
- return lookupAndInterpolate(sp, mFromSpValues, mToDpValues);
- }
-
- private static float lookupAndInterpolate(
- float sourceValue,
- float[] sourceValues,
- float[] targetValues
- ) {
- final float sourceValuePositive = Math.abs(sourceValue);
- // TODO(b/247861374): find a match at a higher index?
- final float sign = Math.signum(sourceValue);
- // 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(sourceValues, sourceValuePositive);
- if (index >= 0) {
- // exact match, return the matching dp
- return sign * targetValues[index];
- } else {
- // must be a value in between index and index + 1: interpolate.
- final int lowerIndex = -(index + 1) - 1;
-
- final float startSp;
- final float endSp;
- final float startDp;
- final float endDp;
-
- if (lowerIndex >= sourceValues.length - 1) {
- // It's past our lookup table. Determine the last elements' scaling factor and use.
- startSp = sourceValues[sourceValues.length - 1];
- startDp = targetValues[sourceValues.length - 1];
-
- if (startSp == 0) return 0;
-
- final float scalingFactor = startDp / startSp;
- return sourceValue * scalingFactor;
- } else if (lowerIndex == -1) {
- // It's smaller than the smallest value in our table. Interpolate from 0.
- startSp = 0;
- startDp = 0;
- endSp = sourceValues[0];
- endDp = targetValues[0];
- } else {
- startSp = sourceValues[lowerIndex];
- endSp = sourceValues[lowerIndex + 1];
- startDp = targetValues[lowerIndex];
- endDp = targetValues[lowerIndex + 1];
- }
-
- return sign
- * MathUtils.constrainedMap(startDp, endDp, startSp, endSp, sourceValuePositive);
- }
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null) return false;
- if (!(o instanceof FontScaleConverter)) return false;
- FontScaleConverter that = (FontScaleConverter) o;
- return Arrays.equals(mFromSpValues, that.mFromSpValues)
- && Arrays.equals(mToDpValues, that.mToDpValues);
- }
-
- @Override
- public int hashCode() {
- int result = Arrays.hashCode(mFromSpValues);
- result = 31 * result + Arrays.hashCode(mToDpValues);
- return result;
- }
-
- @Override
- public String toString() {
- return "FontScaleConverter{"
- + "fromSpValues="
- + Arrays.toString(mFromSpValues)
- + ", toDpValues="
- + Arrays.toString(mToDpValues)
- + '}';
- }
+ float convertDpToSp(float dp);
}
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index 5f90faf6c72f..df1cfe3aa9e6 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -16,6 +16,7 @@
package android.content.res;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.MathUtils;
@@ -24,10 +25,14 @@ import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
/**
- * Stores lookup tables for creating {@link FontScaleConverter}s at various scales.
+ * Creates {@link FontScaleConverter}s at various scales.
*
- * @hide
+ * Generally you shouldn't need this; you can use {@link
+ * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do the
+ * scaling conversion for you. But for UI frameworks or other situations where you need to do the
+ * conversion without an Android Context, you can use this class.
*/
+@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
public class FontScaleConverterFactory {
private static final float SCALE_KEY_MULTIPLIER = 100f;
@@ -42,7 +47,7 @@ public class FontScaleConverterFactory {
// manually tweaked for optimum readability.
put(
/* scaleKey= */ 1.15f,
- new FontScaleConverter(
+ new FontScaleConverterImpl(
/* fromSp= */
new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
/* toDp= */
@@ -51,7 +56,7 @@ public class FontScaleConverterFactory {
put(
/* scaleKey= */ 1.3f,
- new FontScaleConverter(
+ new FontScaleConverterImpl(
/* fromSp= */
new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
/* toDp= */
@@ -60,7 +65,7 @@ public class FontScaleConverterFactory {
put(
/* scaleKey= */ 1.5f,
- new FontScaleConverter(
+ new FontScaleConverterImpl(
/* fromSp= */
new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
/* toDp= */
@@ -69,7 +74,7 @@ public class FontScaleConverterFactory {
put(
/* scaleKey= */ 1.8f,
- new FontScaleConverter(
+ new FontScaleConverterImpl(
/* fromSp= */
new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
/* toDp= */
@@ -78,7 +83,7 @@ public class FontScaleConverterFactory {
put(
/* scaleKey= */ 2f,
- new FontScaleConverter(
+ new FontScaleConverterImpl(
/* fromSp= */
new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
/* toDp= */
@@ -101,9 +106,8 @@ public class FontScaleConverterFactory {
*
* <p>Example usage:
* <code>isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code>
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
public static boolean isNonLinearFontScalingActive(float fontScale) {
return fontScale >= sMinScaleBeforeCurvesApplied;
}
@@ -114,9 +118,8 @@ public class FontScaleConverterFactory {
* @param fontScale the scale factor, usually from {@link Configuration#fontScale}.
*
* @return a converter for the given scale, or null if non-linear scaling should not be used.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
@Nullable
public static FontScaleConverter forScale(float fontScale) {
if (!isNonLinearFontScalingActive(fontScale)) {
@@ -142,7 +145,7 @@ public class FontScaleConverterFactory {
// them a straight linear table instead.
// This works because when FontScaleConverter encounters a size beyond its bounds, it
// calculates a linear fontScale factor using the ratio of the last element pair.
- return new FontScaleConverter(new float[] {1f}, new float[] {fontScale});
+ return new FontScaleConverterImpl(new float[] {1f}, new float[] {fontScale});
} else {
float startScale = getScaleFromKey(LOOKUP_TABLES.keyAt(lowerIndex));
float endScale = getScaleFromKey(LOOKUP_TABLES.keyAt(higherIndex));
@@ -176,7 +179,7 @@ public class FontScaleConverterFactory {
dpInterpolated[i] = MathUtils.lerp(startDp, endDp, interpolationPoint);
}
- return new FontScaleConverter(commonSpSizes, dpInterpolated);
+ return new FontScaleConverterImpl(commonSpSizes, dpInterpolated);
}
private static int getKey(float fontScale) {
diff --git a/core/java/android/content/res/FontScaleConverterImpl.java b/core/java/android/content/res/FontScaleConverterImpl.java
new file mode 100644
index 000000000000..1968c4e53109
--- /dev/null
+++ b/core/java/android/content/res/FontScaleConverterImpl.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2022 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 android.content.res;
+
+import android.annotation.NonNull;
+import android.util.MathUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * A lookup table for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
+ * "dp" dimension according to a non-linear curve by interpolating values in a lookup table.
+ *
+ * {@see FontScaleConverter}
+ *
+ * @hide
+ */
+// Needs to be public so the Kotlin test can see it
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class FontScaleConverterImpl implements FontScaleConverter {
+
+ /** @hide */
+ @VisibleForTesting
+ public final float[] mFromSpValues;
+ /** @hide */
+ @VisibleForTesting
+ public final float[] mToDpValues;
+
+ /**
+ * Creates a lookup table for the given conversions.
+ *
+ * <p>Any "sp" value not in the lookup table will be derived via linear interpolation.
+ *
+ * <p>The arrays must be sorted ascending and monotonically increasing.
+ *
+ * @param fromSp array of dimensions in SP
+ * @param toDp array of dimensions in DP that correspond to an SP value in fromSp
+ *
+ * @throws IllegalArgumentException if the array lengths don't match or are empty
+ * @hide
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public FontScaleConverterImpl(@NonNull float[] fromSp, @NonNull float[] toDp) {
+ if (fromSp.length != toDp.length || fromSp.length == 0) {
+ throw new IllegalArgumentException("Array lengths must match and be nonzero");
+ }
+
+ mFromSpValues = fromSp;
+ mToDpValues = toDp;
+ }
+
+ /**
+ * Convert a dimension in "dp" back to "sp" using the lookup table.
+ *
+ * @hide
+ */
+ @Override
+ public float convertDpToSp(float dp) {
+ return lookupAndInterpolate(dp, mToDpValues, mFromSpValues);
+ }
+
+ /**
+ * Convert a dimension in "sp" to "dp" using the lookup table.
+ *
+ * @hide
+ */
+ @Override
+ public float convertSpToDp(float sp) {
+ return lookupAndInterpolate(sp, mFromSpValues, mToDpValues);
+ }
+
+ private static float lookupAndInterpolate(
+ float sourceValue,
+ float[] sourceValues,
+ float[] targetValues
+ ) {
+ final float sourceValuePositive = Math.abs(sourceValue);
+ // TODO(b/247861374): find a match at a higher index?
+ final float sign = Math.signum(sourceValue);
+ // 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(sourceValues, sourceValuePositive);
+ if (index >= 0) {
+ // exact match, return the matching dp
+ return sign * targetValues[index];
+ } else {
+ // must be a value in between index and index + 1: interpolate.
+ final int lowerIndex = -(index + 1) - 1;
+
+ final float startSp;
+ final float endSp;
+ final float startDp;
+ final float endDp;
+
+ if (lowerIndex >= sourceValues.length - 1) {
+ // It's past our lookup table. Determine the last elements' scaling factor and use.
+ startSp = sourceValues[sourceValues.length - 1];
+ startDp = targetValues[sourceValues.length - 1];
+
+ if (startSp == 0) return 0;
+
+ final float scalingFactor = startDp / startSp;
+ return sourceValue * scalingFactor;
+ } else if (lowerIndex == -1) {
+ // It's smaller than the smallest value in our table. Interpolate from 0.
+ startSp = 0;
+ startDp = 0;
+ endSp = sourceValues[0];
+ endDp = targetValues[0];
+ } else {
+ startSp = sourceValues[lowerIndex];
+ endSp = sourceValues[lowerIndex + 1];
+ startDp = targetValues[lowerIndex];
+ endDp = targetValues[lowerIndex + 1];
+ }
+
+ return sign
+ * MathUtils.constrainedMap(startDp, endDp, startSp, endSp, sourceValuePositive);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof FontScaleConverterImpl)) return false;
+ FontScaleConverterImpl that = (FontScaleConverterImpl) o;
+ return Arrays.equals(mFromSpValues, that.mFromSpValues)
+ && Arrays.equals(mToDpValues, that.mToDpValues);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(mFromSpValues);
+ result = 31 * result + Arrays.hashCode(mToDpValues);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "FontScaleConverter{"
+ + "fromSpValues="
+ + Arrays.toString(mFromSpValues)
+ + ", toDpValues="
+ + Arrays.toString(mToDpValues)
+ + '}';
+ }
+}
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 1b8eb0748737..c599adaa4f30 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -10,6 +10,15 @@ flag {
}
flag {
+ name: "font_scale_converter_public"
+ namespace: "accessibility"
+ description: "Enables the public API for FontScaleConverter, including enabling thread-safe caching."
+ bug: "239736383"
+ # fixed_read_only or device wont boot because of permission issues accessing flags during boot
+ is_fixed_read_only: true
+}
+
+flag {
name: "asset_file_descriptor_frro"
namespace: "resource_manager"
description: "Feature flag for passing in an AssetFileDescriptor to create an frro"
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 8079dad7615e..9976fc25c806 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -28,8 +28,13 @@ import kotlin.math.ceil
import kotlin.math.floor
import org.junit.Test
import org.junit.runner.RunWith
+import java.lang.IllegalStateException
import kotlin.random.Random.Default.nextFloat
+/**
+ * Unit tests for FontScaleConverterFactory. Note that some similar tests are in
+ * cts/tests/tests/content/src/android/content/res/cts/FontScaleConverterFactoryTest.kt
+ */
@Presubmit
@RunWith(AndroidJUnit4::class)
class FontScaleConverterFactoryTest {
@@ -102,6 +107,10 @@ class FontScaleConverterFactoryTest {
@Test
fun tablesMatchAndAreMonotonicallyIncreasing() {
FontScaleConverterFactory.LOOKUP_TABLES.forEach { _, lookupTable ->
+ if (lookupTable !is FontScaleConverterImpl) {
+ throw IllegalStateException("Didn't return a FontScaleConverterImpl")
+ }
+
assertThat(lookupTable.mToDpValues).hasLength(lookupTable.mFromSpValues.size)
assertThat(lookupTable.mToDpValues).isNotEmpty()
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
index bfa8c9ada911..2c614424a9a5 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
@@ -86,13 +86,13 @@ class FontScaleConverterTest {
}
private fun createTable(vararg pairs: Pair<Float, Float>) =
- FontScaleConverter(
+ FontScaleConverterImpl(
pairs.map { it.first }.toFloatArray(),
pairs.map { it.second }.toFloatArray()
)
private fun verifyConversionBothWays(
- table: FontScaleConverter,
+ table: FontScaleConverterImpl,
expectedDp: Float,
spToConvert: Float
) {