Loading core/java/android/app/Notification.java +16 −10 Original line number Diff line number Diff line Loading @@ -83,6 +83,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; import android.util.TypedValue; import android.util.proto.ProtoOutputStream; import android.view.ContextThemeWrapper; import android.view.Gravity; Loading Loading @@ -4920,7 +4921,8 @@ public class Notification implements Parcelable setTextViewColorPrimary(contentView, R.id.title, p); contentView.setViewLayoutWidth(R.id.title, showProgress ? ViewGroup.LayoutParams.WRAP_CONTENT : ViewGroup.LayoutParams.MATCH_PARENT); : ViewGroup.LayoutParams.MATCH_PARENT, TypedValue.COMPLEX_UNIT_PX); } if (p.text != null && p.text.length() != 0) { int textId = showProgress ? com.android.internal.R.id.text_line_1 Loading Loading @@ -5365,8 +5367,9 @@ public class Notification implements Parcelable final boolean snoozeEnabled = mContext.getContentResolver() != null && (Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1); big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, snoozeEnabled ? 0 : R.dimen.notification_content_margin); int bottomMarginDimen = snoozeEnabled ? 0 : R.dimen.notification_content_margin; big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, RemoteViews.MARGIN_BOTTOM, bottomMarginDimen); } private static List<Notification.Action> filterOutContextualActions( Loading Loading @@ -5398,7 +5401,8 @@ public class Notification implements Parcelable if (N > 0) { big.setViewVisibility(R.id.actions_container, View.VISIBLE); big.setViewVisibility(R.id.actions, View.VISIBLE); big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0); big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, RemoteViews.MARGIN_BOTTOM, 0); if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS; for (int i=0; i<N; i++) { Action action = nonContextualActions.get(i); Loading Loading @@ -7797,8 +7801,8 @@ public class Notification implements Parcelable // also update the end margin if there is an image // NOTE: This template doesn't support moving this icon to the left, so we don't // need to fully apply the MarginSet contentView.setViewLayoutMarginEnd(R.id.notification_messaging, bindResult.mHeadingExtraMarginSet.getValue()); contentView.setViewLayoutMargin(R.id.notification_messaging, RemoteViews.MARGIN_END, bindResult.mHeadingExtraMarginSet.getValue(), TypedValue.COMPLEX_UNIT_PX); } contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", mBuilder.isColorized(p) Loading Loading @@ -8622,7 +8626,8 @@ public class Notification implements Parcelable if (mBuilder.mN.hasLargeIcon()) { endMargin = R.dimen.notification_media_image_margin_end; } view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); view.setViewLayoutMarginDimen(R.id.notification_main_column, RemoteViews.MARGIN_END, endMargin); return view; } Loading Loading @@ -8659,8 +8664,8 @@ public class Notification implements Parcelable private void handleImage(RemoteViews contentView) { if (mBuilder.mN.hasLargeIcon()) { contentView.setViewLayoutMarginEndDimen(R.id.line1, 0); contentView.setViewLayoutMarginEndDimen(R.id.text, 0); contentView.setViewLayoutMarginDimen(R.id.line1, RemoteViews.MARGIN_END, 0); contentView.setViewLayoutMarginDimen(R.id.text, RemoteViews.MARGIN_END, 0); } } Loading Loading @@ -11089,7 +11094,8 @@ public class Notification implements Parcelable if (viewId == R.id.notification_header) { views.setInt(R.id.notification_header, "setTopLineExtraMarginEnd", marginEnd); } else { views.setViewLayoutMarginEnd(viewId, marginEnd); views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, marginEnd, TypedValue.COMPLEX_UNIT_PX); } if (mRightIconVisible) { views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible, Loading core/java/android/util/TypedValue.java +143 −1 Original line number Diff line number Diff line Loading @@ -17,8 +17,14 @@ package android.util; import android.annotation.AnyRes; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; import android.content.pm.ActivityInfo.Config; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Container for a dynamically typed data value. Primarily used with * {@link android.content.res.Resources} for holding resource values. Loading Loading @@ -95,6 +101,18 @@ public class TypedValue { * defined below. */ public static final int COMPLEX_UNIT_MASK = 0xf; /** @hide **/ @IntDef(prefix = "COMPLEX_UNIT_", value = { COMPLEX_UNIT_PX, COMPLEX_UNIT_DIP, COMPLEX_UNIT_SP, COMPLEX_UNIT_PT, COMPLEX_UNIT_IN, COMPLEX_UNIT_MM, }) @Retention(RetentionPolicy.SOURCE) public @interface ComplexDimensionUnit {} /** {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. */ public static final int COMPLEX_UNIT_PX = 0; /** {@link #TYPE_DIMENSION} complex unit: Value is Device Independent Loading Loading @@ -381,7 +399,7 @@ public class TypedValue { * @return The complex floating point value multiplied by the appropriate * metrics depending on its unit. */ public static float applyDimension(int unit, float value, public static float applyDimension(@ComplexDimensionUnit int unit, float value, DisplayMetrics metrics) { switch (unit) { Loading Loading @@ -416,6 +434,130 @@ public class TypedValue { return complexToDimension(data, metrics); } /** * Construct a complex data integer. This validates the radix and the magnitude of the * mantissa, and sets the {@link TypedValue#COMPLEX_MANTISSA_MASK} and * {@link TypedValue#COMPLEX_RADIX_MASK} components as provided. The units are not set. ** * @param mantissa an integer representing the mantissa. * @param radix a radix option, e.g. {@link TypedValue#COMPLEX_RADIX_23p0}. * @return A complex data integer representing the value. * @hide */ private static int createComplex(@IntRange(from = -0x800000, to = 0x7FFFFF) int mantissa, int radix) { if (mantissa < -0x800000 || mantissa >= 0x800000) { throw new IllegalArgumentException("Magnitude of mantissa is too large: " + mantissa); } if (radix < TypedValue.COMPLEX_RADIX_23p0 || radix > TypedValue.COMPLEX_RADIX_0p23) { throw new IllegalArgumentException("Invalid radix: " + radix); } return ((mantissa & TypedValue.COMPLEX_MANTISSA_MASK) << TypedValue.COMPLEX_MANTISSA_SHIFT) | (radix << TypedValue.COMPLEX_RADIX_SHIFT); } /** * Convert a base value to a complex data integer. This sets the {@link * TypedValue#COMPLEX_MANTISSA_MASK} and {@link TypedValue#COMPLEX_RADIX_MASK} fields of the * data to create a floating point representation of the given value. The units are not set. * * <p>This is the inverse of {@link TypedValue#complexToFloat(int)}. * * @param value An integer value. * @return A complex data integer representing the value. * @hide */ public static int intToComplex(int value) { if (value < -0x800000 || value >= 0x800000) { throw new IllegalArgumentException("Magnitude of the value is too large: " + value); } return createComplex(value, TypedValue.COMPLEX_RADIX_23p0); } /** * Convert a base value to a complex data integer. This sets the {@link * TypedValue#COMPLEX_MANTISSA_MASK} and {@link TypedValue#COMPLEX_RADIX_MASK} fields of the * data to create a floating point representation of the given value. The units are not set. * * <p>This is the inverse of {@link TypedValue#complexToFloat(int)}. * * @param value A floating point value. * @return A complex data integer representing the value. * @hide */ public static int floatToComplex(@FloatRange(from = -0x800000, to = 0x7FFFFF) float value) { // validate that the magnitude fits in this representation if (value < (float) -0x800000 - .5f || value >= (float) 0x800000 - .5f) { throw new IllegalArgumentException("Magnitude of the value is too large: " + value); } try { // If there's no fraction, use integer representation, as that's clearer if (value == (float) (int) value) { return createComplex((int) value, TypedValue.COMPLEX_RADIX_23p0); } float absValue = Math.abs(value); // If the magnitude is 0, we don't need any magnitude digits if (absValue < 1f) { return createComplex(Math.round(value * (1 << 23)), TypedValue.COMPLEX_RADIX_0p23); } // If the magnitude is less than 2^8, use 8 magnitude digits if (absValue < (float) (1 << 8)) { return createComplex(Math.round(value * (1 << 15)), TypedValue.COMPLEX_RADIX_8p15); } // If the magnitude is less than 2^16, use 16 magnitude digits if (absValue < (float) (1 << 16)) { return createComplex(Math.round(value * (1 << 7)), TypedValue.COMPLEX_RADIX_16p7); } // The magnitude requires all 23 digits return createComplex(Math.round(value), TypedValue.COMPLEX_RADIX_23p0); } catch (IllegalArgumentException ex) { // Wrap exception so as to include the value argument in the message. throw new IllegalArgumentException("Unable to convert value to complex: " + value, ex); } } /** * <p>Creates a complex data integer that stores a dimension value and units. * * <p>The resulting value can be passed to e.g. * {@link TypedValue#complexToDimensionPixelOffset(int, DisplayMetrics)} to calculate the pixel * value for the dimension. * * @param value the value of the dimension * @param units the units of the dimension, e.g. {@link TypedValue#COMPLEX_UNIT_DIP} * @return A complex data integer representing the value and units of the dimension. * @hide */ public static int createComplexDimension( @IntRange(from = -0x800000, to = 0x7FFFFF) int value, @ComplexDimensionUnit int units) { if (units < TypedValue.COMPLEX_UNIT_PX || units > TypedValue.COMPLEX_UNIT_MM) { throw new IllegalArgumentException("Must be a valid COMPLEX_UNIT_*: " + units); } return intToComplex(value) | units; } /** * <p>Creates a complex data integer that stores a dimension value and units. * * <p>The resulting value can be passed to e.g. * {@link TypedValue#complexToDimensionPixelOffset(int, DisplayMetrics)} to calculate the pixel * value for the dimension. * * @param value the value of the dimension * @param units the units of the dimension, e.g. {@link TypedValue#COMPLEX_UNIT_DIP} * @return A complex data integer representing the value and units of the dimension. * @hide */ public static int createComplexDimension( @FloatRange(from = -0x800000, to = 0x7FFFFF) float value, @ComplexDimensionUnit int units) { if (units < TypedValue.COMPLEX_UNIT_PX || units > TypedValue.COMPLEX_UNIT_MM) { throw new IllegalArgumentException("Must be a valid COMPLEX_UNIT_*: " + units); } return floatToComplex(value) | units; } /** * Converts a complex data value holding a fraction to its final floating * point value. The given <var>data</var> must be structured as a Loading core/java/android/widget/RemoteViews.java +205 −55 File changed.Preview size limit exceeded, changes collapsed. Show changes core/tests/coretests/src/android/util/TypedValueTest.kt 0 → 100644 +155 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.util import androidx.test.filters.LargeTest import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock import kotlin.math.abs import kotlin.math.min import kotlin.math.roundToInt @RunWith(AndroidJUnit4::class) class TypedValueTest { @LargeTest @Test fun testFloatToComplex() { fun assertRoundTripEquals(value: Float, expectedRadix: Int? = null) { val complex = TypedValue.floatToComplex(value) // Ensure values are accurate within .5% of the original value and within .5 val delta = min(abs(value) / 512f, .5f) assertEquals(value, TypedValue.complexToFloat(complex), delta) // If expectedRadix is provided, validate it if (expectedRadix != null) { val actualRadix = ((complex shr TypedValue.COMPLEX_RADIX_SHIFT) and TypedValue.COMPLEX_RADIX_MASK) assertEquals("Incorrect radix for $value:", expectedRadix, actualRadix) } } assertRoundTripEquals(0f, TypedValue.COMPLEX_RADIX_23p0) assertRoundTripEquals(0.5f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(0.05f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(0.005f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(0.0005f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(0.00005f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(1.5f, TypedValue.COMPLEX_RADIX_8p15) assertRoundTripEquals(10.5f, TypedValue.COMPLEX_RADIX_8p15) assertRoundTripEquals(100.5f, TypedValue.COMPLEX_RADIX_8p15) assertRoundTripEquals(255.5f, TypedValue.COMPLEX_RADIX_8p15) // 2^8 - .5 assertRoundTripEquals(256.5f, TypedValue.COMPLEX_RADIX_16p7) // 2^8 + .5 assertRoundTripEquals(1000.5f, TypedValue.COMPLEX_RADIX_16p7) assertRoundTripEquals(10000.5f, TypedValue.COMPLEX_RADIX_16p7) assertRoundTripEquals(65535.5f, TypedValue.COMPLEX_RADIX_16p7) // 2^16 - .5 assertRoundTripEquals(65536.5f, TypedValue.COMPLEX_RADIX_23p0) // 2^16 + .5 assertRoundTripEquals(100000.5f, TypedValue.COMPLEX_RADIX_23p0) assertRoundTripEquals(1000000.5f, TypedValue.COMPLEX_RADIX_23p0) assertRoundTripEquals(8388607.2f, TypedValue.COMPLEX_RADIX_23p0) // 2^23 -.8 assertRoundTripEquals(-0.5f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(-0.05f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(-0.005f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(-0.0005f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(-0.00005f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(-1.5f, TypedValue.COMPLEX_RADIX_8p15) assertRoundTripEquals(-10.5f, TypedValue.COMPLEX_RADIX_8p15) assertRoundTripEquals(-100.5f, TypedValue.COMPLEX_RADIX_8p15) assertRoundTripEquals(-255.5f, TypedValue.COMPLEX_RADIX_8p15) // -2^8 + .5 // NOTE: -256.5f fits in COMPLEX_RADIX_8p15 but is stored with COMPLEX_RADIX_16p7 for // simplicity of the algorithm. However, it's better not to enforce that with a test. assertRoundTripEquals(-257.5f, TypedValue.COMPLEX_RADIX_16p7) // -2^8 - 1.5 assertRoundTripEquals(-1000.5f, TypedValue.COMPLEX_RADIX_16p7) assertRoundTripEquals(-10000.5f, TypedValue.COMPLEX_RADIX_16p7) assertRoundTripEquals(-65535.5f, TypedValue.COMPLEX_RADIX_16p7) // -2^16 + .5 // NOTE: -65536.5f fits in COMPLEX_RADIX_16p7 but is stored with COMPLEX_RADIX_23p0 for // simplicity of the algorithm. However, it's better not to enforce that with a test. assertRoundTripEquals(-65537.5f, TypedValue.COMPLEX_RADIX_23p0) // -2^16 - 1.5 assertRoundTripEquals(-100000.5f, TypedValue.COMPLEX_RADIX_23p0) assertRoundTripEquals(-1000000.5f, TypedValue.COMPLEX_RADIX_23p0) assertRoundTripEquals(-8388607.5f, TypedValue.COMPLEX_RADIX_23p0) // 2^23 -.5 // Test for every integer value in the range... for (i: Int in -(1 shl 23) until (1 shl 23)) { // ... that true integers are stored as the precise integer assertRoundTripEquals(i.toFloat(), TypedValue.COMPLEX_RADIX_23p0) // ... that values round up when just below an integer assertRoundTripEquals(i - .1f) // ... that values round down when just above an integer assertRoundTripEquals(i + .1f) } } @SmallTest @Test(expected = IllegalArgumentException::class) fun testFloatToComplex_failsIfValueTooLarge() { TypedValue.floatToComplex(8388607.5f) // 2^23 - .5 } @SmallTest @Test(expected = IllegalArgumentException::class) fun testFloatToComplex_failsIfValueTooSmall() { TypedValue.floatToComplex(8388608.5f) // -2^23 - .5 } @LargeTest @Test fun testIntToComplex() { // Validates every single valid value for (value: Int in -(1 shl 23) until (1 shl 23)) { assertEquals(value.toFloat(), TypedValue.complexToFloat(TypedValue.intToComplex(value))) } } @SmallTest @Test(expected = IllegalArgumentException::class) fun testIntToComplex_failsIfValueTooLarge() { TypedValue.intToComplex(0x800000) } @SmallTest @Test(expected = IllegalArgumentException::class) fun testIntToComplex_failsIfValueTooSmall() { TypedValue.intToComplex(-0x800001) } @SmallTest @Test fun testCreateComplexDimension_appliesUnits() { val metrics: DisplayMetrics = mock(DisplayMetrics::class.java) metrics.density = 3.25f val height = 52 * metrics.density val widthFloat = height * 16 / 9 val widthDimen = TypedValue.createComplexDimension( widthFloat / metrics.density, TypedValue.COMPLEX_UNIT_DIP ) val widthPx = TypedValue.complexToDimensionPixelSize(widthDimen, metrics) assertEquals(widthFloat.roundToInt(), widthPx) } } No newline at end of file Loading
core/java/android/app/Notification.java +16 −10 Original line number Diff line number Diff line Loading @@ -83,6 +83,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; import android.util.TypedValue; import android.util.proto.ProtoOutputStream; import android.view.ContextThemeWrapper; import android.view.Gravity; Loading Loading @@ -4920,7 +4921,8 @@ public class Notification implements Parcelable setTextViewColorPrimary(contentView, R.id.title, p); contentView.setViewLayoutWidth(R.id.title, showProgress ? ViewGroup.LayoutParams.WRAP_CONTENT : ViewGroup.LayoutParams.MATCH_PARENT); : ViewGroup.LayoutParams.MATCH_PARENT, TypedValue.COMPLEX_UNIT_PX); } if (p.text != null && p.text.length() != 0) { int textId = showProgress ? com.android.internal.R.id.text_line_1 Loading Loading @@ -5365,8 +5367,9 @@ public class Notification implements Parcelable final boolean snoozeEnabled = mContext.getContentResolver() != null && (Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1); big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, snoozeEnabled ? 0 : R.dimen.notification_content_margin); int bottomMarginDimen = snoozeEnabled ? 0 : R.dimen.notification_content_margin; big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, RemoteViews.MARGIN_BOTTOM, bottomMarginDimen); } private static List<Notification.Action> filterOutContextualActions( Loading Loading @@ -5398,7 +5401,8 @@ public class Notification implements Parcelable if (N > 0) { big.setViewVisibility(R.id.actions_container, View.VISIBLE); big.setViewVisibility(R.id.actions, View.VISIBLE); big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0); big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, RemoteViews.MARGIN_BOTTOM, 0); if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS; for (int i=0; i<N; i++) { Action action = nonContextualActions.get(i); Loading Loading @@ -7797,8 +7801,8 @@ public class Notification implements Parcelable // also update the end margin if there is an image // NOTE: This template doesn't support moving this icon to the left, so we don't // need to fully apply the MarginSet contentView.setViewLayoutMarginEnd(R.id.notification_messaging, bindResult.mHeadingExtraMarginSet.getValue()); contentView.setViewLayoutMargin(R.id.notification_messaging, RemoteViews.MARGIN_END, bindResult.mHeadingExtraMarginSet.getValue(), TypedValue.COMPLEX_UNIT_PX); } contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", mBuilder.isColorized(p) Loading Loading @@ -8622,7 +8626,8 @@ public class Notification implements Parcelable if (mBuilder.mN.hasLargeIcon()) { endMargin = R.dimen.notification_media_image_margin_end; } view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); view.setViewLayoutMarginDimen(R.id.notification_main_column, RemoteViews.MARGIN_END, endMargin); return view; } Loading Loading @@ -8659,8 +8664,8 @@ public class Notification implements Parcelable private void handleImage(RemoteViews contentView) { if (mBuilder.mN.hasLargeIcon()) { contentView.setViewLayoutMarginEndDimen(R.id.line1, 0); contentView.setViewLayoutMarginEndDimen(R.id.text, 0); contentView.setViewLayoutMarginDimen(R.id.line1, RemoteViews.MARGIN_END, 0); contentView.setViewLayoutMarginDimen(R.id.text, RemoteViews.MARGIN_END, 0); } } Loading Loading @@ -11089,7 +11094,8 @@ public class Notification implements Parcelable if (viewId == R.id.notification_header) { views.setInt(R.id.notification_header, "setTopLineExtraMarginEnd", marginEnd); } else { views.setViewLayoutMarginEnd(viewId, marginEnd); views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, marginEnd, TypedValue.COMPLEX_UNIT_PX); } if (mRightIconVisible) { views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible, Loading
core/java/android/util/TypedValue.java +143 −1 Original line number Diff line number Diff line Loading @@ -17,8 +17,14 @@ package android.util; import android.annotation.AnyRes; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; import android.content.pm.ActivityInfo.Config; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Container for a dynamically typed data value. Primarily used with * {@link android.content.res.Resources} for holding resource values. Loading Loading @@ -95,6 +101,18 @@ public class TypedValue { * defined below. */ public static final int COMPLEX_UNIT_MASK = 0xf; /** @hide **/ @IntDef(prefix = "COMPLEX_UNIT_", value = { COMPLEX_UNIT_PX, COMPLEX_UNIT_DIP, COMPLEX_UNIT_SP, COMPLEX_UNIT_PT, COMPLEX_UNIT_IN, COMPLEX_UNIT_MM, }) @Retention(RetentionPolicy.SOURCE) public @interface ComplexDimensionUnit {} /** {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. */ public static final int COMPLEX_UNIT_PX = 0; /** {@link #TYPE_DIMENSION} complex unit: Value is Device Independent Loading Loading @@ -381,7 +399,7 @@ public class TypedValue { * @return The complex floating point value multiplied by the appropriate * metrics depending on its unit. */ public static float applyDimension(int unit, float value, public static float applyDimension(@ComplexDimensionUnit int unit, float value, DisplayMetrics metrics) { switch (unit) { Loading Loading @@ -416,6 +434,130 @@ public class TypedValue { return complexToDimension(data, metrics); } /** * Construct a complex data integer. This validates the radix and the magnitude of the * mantissa, and sets the {@link TypedValue#COMPLEX_MANTISSA_MASK} and * {@link TypedValue#COMPLEX_RADIX_MASK} components as provided. The units are not set. ** * @param mantissa an integer representing the mantissa. * @param radix a radix option, e.g. {@link TypedValue#COMPLEX_RADIX_23p0}. * @return A complex data integer representing the value. * @hide */ private static int createComplex(@IntRange(from = -0x800000, to = 0x7FFFFF) int mantissa, int radix) { if (mantissa < -0x800000 || mantissa >= 0x800000) { throw new IllegalArgumentException("Magnitude of mantissa is too large: " + mantissa); } if (radix < TypedValue.COMPLEX_RADIX_23p0 || radix > TypedValue.COMPLEX_RADIX_0p23) { throw new IllegalArgumentException("Invalid radix: " + radix); } return ((mantissa & TypedValue.COMPLEX_MANTISSA_MASK) << TypedValue.COMPLEX_MANTISSA_SHIFT) | (radix << TypedValue.COMPLEX_RADIX_SHIFT); } /** * Convert a base value to a complex data integer. This sets the {@link * TypedValue#COMPLEX_MANTISSA_MASK} and {@link TypedValue#COMPLEX_RADIX_MASK} fields of the * data to create a floating point representation of the given value. The units are not set. * * <p>This is the inverse of {@link TypedValue#complexToFloat(int)}. * * @param value An integer value. * @return A complex data integer representing the value. * @hide */ public static int intToComplex(int value) { if (value < -0x800000 || value >= 0x800000) { throw new IllegalArgumentException("Magnitude of the value is too large: " + value); } return createComplex(value, TypedValue.COMPLEX_RADIX_23p0); } /** * Convert a base value to a complex data integer. This sets the {@link * TypedValue#COMPLEX_MANTISSA_MASK} and {@link TypedValue#COMPLEX_RADIX_MASK} fields of the * data to create a floating point representation of the given value. The units are not set. * * <p>This is the inverse of {@link TypedValue#complexToFloat(int)}. * * @param value A floating point value. * @return A complex data integer representing the value. * @hide */ public static int floatToComplex(@FloatRange(from = -0x800000, to = 0x7FFFFF) float value) { // validate that the magnitude fits in this representation if (value < (float) -0x800000 - .5f || value >= (float) 0x800000 - .5f) { throw new IllegalArgumentException("Magnitude of the value is too large: " + value); } try { // If there's no fraction, use integer representation, as that's clearer if (value == (float) (int) value) { return createComplex((int) value, TypedValue.COMPLEX_RADIX_23p0); } float absValue = Math.abs(value); // If the magnitude is 0, we don't need any magnitude digits if (absValue < 1f) { return createComplex(Math.round(value * (1 << 23)), TypedValue.COMPLEX_RADIX_0p23); } // If the magnitude is less than 2^8, use 8 magnitude digits if (absValue < (float) (1 << 8)) { return createComplex(Math.round(value * (1 << 15)), TypedValue.COMPLEX_RADIX_8p15); } // If the magnitude is less than 2^16, use 16 magnitude digits if (absValue < (float) (1 << 16)) { return createComplex(Math.round(value * (1 << 7)), TypedValue.COMPLEX_RADIX_16p7); } // The magnitude requires all 23 digits return createComplex(Math.round(value), TypedValue.COMPLEX_RADIX_23p0); } catch (IllegalArgumentException ex) { // Wrap exception so as to include the value argument in the message. throw new IllegalArgumentException("Unable to convert value to complex: " + value, ex); } } /** * <p>Creates a complex data integer that stores a dimension value and units. * * <p>The resulting value can be passed to e.g. * {@link TypedValue#complexToDimensionPixelOffset(int, DisplayMetrics)} to calculate the pixel * value for the dimension. * * @param value the value of the dimension * @param units the units of the dimension, e.g. {@link TypedValue#COMPLEX_UNIT_DIP} * @return A complex data integer representing the value and units of the dimension. * @hide */ public static int createComplexDimension( @IntRange(from = -0x800000, to = 0x7FFFFF) int value, @ComplexDimensionUnit int units) { if (units < TypedValue.COMPLEX_UNIT_PX || units > TypedValue.COMPLEX_UNIT_MM) { throw new IllegalArgumentException("Must be a valid COMPLEX_UNIT_*: " + units); } return intToComplex(value) | units; } /** * <p>Creates a complex data integer that stores a dimension value and units. * * <p>The resulting value can be passed to e.g. * {@link TypedValue#complexToDimensionPixelOffset(int, DisplayMetrics)} to calculate the pixel * value for the dimension. * * @param value the value of the dimension * @param units the units of the dimension, e.g. {@link TypedValue#COMPLEX_UNIT_DIP} * @return A complex data integer representing the value and units of the dimension. * @hide */ public static int createComplexDimension( @FloatRange(from = -0x800000, to = 0x7FFFFF) float value, @ComplexDimensionUnit int units) { if (units < TypedValue.COMPLEX_UNIT_PX || units > TypedValue.COMPLEX_UNIT_MM) { throw new IllegalArgumentException("Must be a valid COMPLEX_UNIT_*: " + units); } return floatToComplex(value) | units; } /** * Converts a complex data value holding a fraction to its final floating * point value. The given <var>data</var> must be structured as a Loading
core/java/android/widget/RemoteViews.java +205 −55 File changed.Preview size limit exceeded, changes collapsed. Show changes
core/tests/coretests/src/android/util/TypedValueTest.kt 0 → 100644 +155 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.util import androidx.test.filters.LargeTest import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock import kotlin.math.abs import kotlin.math.min import kotlin.math.roundToInt @RunWith(AndroidJUnit4::class) class TypedValueTest { @LargeTest @Test fun testFloatToComplex() { fun assertRoundTripEquals(value: Float, expectedRadix: Int? = null) { val complex = TypedValue.floatToComplex(value) // Ensure values are accurate within .5% of the original value and within .5 val delta = min(abs(value) / 512f, .5f) assertEquals(value, TypedValue.complexToFloat(complex), delta) // If expectedRadix is provided, validate it if (expectedRadix != null) { val actualRadix = ((complex shr TypedValue.COMPLEX_RADIX_SHIFT) and TypedValue.COMPLEX_RADIX_MASK) assertEquals("Incorrect radix for $value:", expectedRadix, actualRadix) } } assertRoundTripEquals(0f, TypedValue.COMPLEX_RADIX_23p0) assertRoundTripEquals(0.5f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(0.05f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(0.005f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(0.0005f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(0.00005f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(1.5f, TypedValue.COMPLEX_RADIX_8p15) assertRoundTripEquals(10.5f, TypedValue.COMPLEX_RADIX_8p15) assertRoundTripEquals(100.5f, TypedValue.COMPLEX_RADIX_8p15) assertRoundTripEquals(255.5f, TypedValue.COMPLEX_RADIX_8p15) // 2^8 - .5 assertRoundTripEquals(256.5f, TypedValue.COMPLEX_RADIX_16p7) // 2^8 + .5 assertRoundTripEquals(1000.5f, TypedValue.COMPLEX_RADIX_16p7) assertRoundTripEquals(10000.5f, TypedValue.COMPLEX_RADIX_16p7) assertRoundTripEquals(65535.5f, TypedValue.COMPLEX_RADIX_16p7) // 2^16 - .5 assertRoundTripEquals(65536.5f, TypedValue.COMPLEX_RADIX_23p0) // 2^16 + .5 assertRoundTripEquals(100000.5f, TypedValue.COMPLEX_RADIX_23p0) assertRoundTripEquals(1000000.5f, TypedValue.COMPLEX_RADIX_23p0) assertRoundTripEquals(8388607.2f, TypedValue.COMPLEX_RADIX_23p0) // 2^23 -.8 assertRoundTripEquals(-0.5f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(-0.05f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(-0.005f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(-0.0005f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(-0.00005f, TypedValue.COMPLEX_RADIX_0p23) assertRoundTripEquals(-1.5f, TypedValue.COMPLEX_RADIX_8p15) assertRoundTripEquals(-10.5f, TypedValue.COMPLEX_RADIX_8p15) assertRoundTripEquals(-100.5f, TypedValue.COMPLEX_RADIX_8p15) assertRoundTripEquals(-255.5f, TypedValue.COMPLEX_RADIX_8p15) // -2^8 + .5 // NOTE: -256.5f fits in COMPLEX_RADIX_8p15 but is stored with COMPLEX_RADIX_16p7 for // simplicity of the algorithm. However, it's better not to enforce that with a test. assertRoundTripEquals(-257.5f, TypedValue.COMPLEX_RADIX_16p7) // -2^8 - 1.5 assertRoundTripEquals(-1000.5f, TypedValue.COMPLEX_RADIX_16p7) assertRoundTripEquals(-10000.5f, TypedValue.COMPLEX_RADIX_16p7) assertRoundTripEquals(-65535.5f, TypedValue.COMPLEX_RADIX_16p7) // -2^16 + .5 // NOTE: -65536.5f fits in COMPLEX_RADIX_16p7 but is stored with COMPLEX_RADIX_23p0 for // simplicity of the algorithm. However, it's better not to enforce that with a test. assertRoundTripEquals(-65537.5f, TypedValue.COMPLEX_RADIX_23p0) // -2^16 - 1.5 assertRoundTripEquals(-100000.5f, TypedValue.COMPLEX_RADIX_23p0) assertRoundTripEquals(-1000000.5f, TypedValue.COMPLEX_RADIX_23p0) assertRoundTripEquals(-8388607.5f, TypedValue.COMPLEX_RADIX_23p0) // 2^23 -.5 // Test for every integer value in the range... for (i: Int in -(1 shl 23) until (1 shl 23)) { // ... that true integers are stored as the precise integer assertRoundTripEquals(i.toFloat(), TypedValue.COMPLEX_RADIX_23p0) // ... that values round up when just below an integer assertRoundTripEquals(i - .1f) // ... that values round down when just above an integer assertRoundTripEquals(i + .1f) } } @SmallTest @Test(expected = IllegalArgumentException::class) fun testFloatToComplex_failsIfValueTooLarge() { TypedValue.floatToComplex(8388607.5f) // 2^23 - .5 } @SmallTest @Test(expected = IllegalArgumentException::class) fun testFloatToComplex_failsIfValueTooSmall() { TypedValue.floatToComplex(8388608.5f) // -2^23 - .5 } @LargeTest @Test fun testIntToComplex() { // Validates every single valid value for (value: Int in -(1 shl 23) until (1 shl 23)) { assertEquals(value.toFloat(), TypedValue.complexToFloat(TypedValue.intToComplex(value))) } } @SmallTest @Test(expected = IllegalArgumentException::class) fun testIntToComplex_failsIfValueTooLarge() { TypedValue.intToComplex(0x800000) } @SmallTest @Test(expected = IllegalArgumentException::class) fun testIntToComplex_failsIfValueTooSmall() { TypedValue.intToComplex(-0x800001) } @SmallTest @Test fun testCreateComplexDimension_appliesUnits() { val metrics: DisplayMetrics = mock(DisplayMetrics::class.java) metrics.density = 3.25f val height = 52 * metrics.density val widthFloat = height * 16 / 9 val widthDimen = TypedValue.createComplexDimension( widthFloat / metrics.density, TypedValue.COMPLEX_UNIT_DIP ) val widthPx = TypedValue.complexToDimensionPixelSize(widthDimen, metrics) assertEquals(widthFloat.roundToInt(), widthPx) } } No newline at end of file