• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.util
18 
19 import android.content.res.FontScaleConverterFactory
20 import android.platform.test.annotations.Presubmit
21 import androidx.test.ext.junit.runners.AndroidJUnit4
22 import androidx.test.filters.LargeTest
23 import androidx.test.filters.SmallTest
24 import com.google.common.truth.Truth.assertThat
25 import org.junit.Assert.assertEquals
26 import org.junit.Assert.assertThrows
27 import org.junit.Test
28 import org.junit.runner.RunWith
29 import org.mockito.Mockito.mock
30 import kotlin.math.abs
31 import kotlin.math.min
32 import kotlin.math.roundToInt
33 
34 @Presubmit
35 @RunWith(AndroidJUnit4::class)
36 class TypedValueTest {
37     @LargeTest
38     @Test
39     fun testFloatToComplex() {
40         fun assertRoundTripEquals(value: Float, expectedRadix: Int? = null) {
41             val complex = TypedValue.floatToComplex(value)
42             // Ensure values are accurate within .5% of the original value and within .5
43             val delta = min(abs(value) / 512f, .5f)
44             assertEquals(value, TypedValue.complexToFloat(complex), delta)
45             // If expectedRadix is provided, validate it
46             if (expectedRadix != null) {
47                 val actualRadix = ((complex shr TypedValue.COMPLEX_RADIX_SHIFT)
48                         and TypedValue.COMPLEX_RADIX_MASK)
49                 assertEquals("Incorrect radix for $value:", expectedRadix, actualRadix)
50             }
51         }
52 
53         assertRoundTripEquals(0f, TypedValue.COMPLEX_RADIX_23p0)
54 
55         assertRoundTripEquals(0.5f, TypedValue.COMPLEX_RADIX_0p23)
56         assertRoundTripEquals(0.05f, TypedValue.COMPLEX_RADIX_0p23)
57         assertRoundTripEquals(0.005f, TypedValue.COMPLEX_RADIX_0p23)
58         assertRoundTripEquals(0.0005f, TypedValue.COMPLEX_RADIX_0p23)
59         assertRoundTripEquals(0.00005f, TypedValue.COMPLEX_RADIX_0p23)
60 
61         assertRoundTripEquals(1.5f, TypedValue.COMPLEX_RADIX_8p15)
62         assertRoundTripEquals(10.5f, TypedValue.COMPLEX_RADIX_8p15)
63         assertRoundTripEquals(100.5f, TypedValue.COMPLEX_RADIX_8p15)
64         assertRoundTripEquals(255.5f, TypedValue.COMPLEX_RADIX_8p15) // 2^8 - .5
65 
66         assertRoundTripEquals(256.5f, TypedValue.COMPLEX_RADIX_16p7) // 2^8 + .5
67         assertRoundTripEquals(1000.5f, TypedValue.COMPLEX_RADIX_16p7)
68         assertRoundTripEquals(10000.5f, TypedValue.COMPLEX_RADIX_16p7)
69         assertRoundTripEquals(65535.5f, TypedValue.COMPLEX_RADIX_16p7) // 2^16 - .5
70 
71         assertRoundTripEquals(65536.5f, TypedValue.COMPLEX_RADIX_23p0) // 2^16 + .5
72         assertRoundTripEquals(100000.5f, TypedValue.COMPLEX_RADIX_23p0)
73         assertRoundTripEquals(1000000.5f, TypedValue.COMPLEX_RADIX_23p0)
74         assertRoundTripEquals(8388607.2f, TypedValue.COMPLEX_RADIX_23p0) // 2^23 -.8
75 
76         assertRoundTripEquals(-0.5f, TypedValue.COMPLEX_RADIX_0p23)
77         assertRoundTripEquals(-0.05f, TypedValue.COMPLEX_RADIX_0p23)
78         assertRoundTripEquals(-0.005f, TypedValue.COMPLEX_RADIX_0p23)
79         assertRoundTripEquals(-0.0005f, TypedValue.COMPLEX_RADIX_0p23)
80         assertRoundTripEquals(-0.00005f, TypedValue.COMPLEX_RADIX_0p23)
81 
82         assertRoundTripEquals(-1.5f, TypedValue.COMPLEX_RADIX_8p15)
83         assertRoundTripEquals(-10.5f, TypedValue.COMPLEX_RADIX_8p15)
84         assertRoundTripEquals(-100.5f, TypedValue.COMPLEX_RADIX_8p15)
85         assertRoundTripEquals(-255.5f, TypedValue.COMPLEX_RADIX_8p15) // -2^8 + .5
86 
87         // NOTE: -256.5f fits in COMPLEX_RADIX_8p15 but is stored with COMPLEX_RADIX_16p7 for
88         // simplicity of the algorithm.  However, it's better not to enforce that with a test.
89         assertRoundTripEquals(-257.5f, TypedValue.COMPLEX_RADIX_16p7) // -2^8 - 1.5
90         assertRoundTripEquals(-1000.5f, TypedValue.COMPLEX_RADIX_16p7)
91         assertRoundTripEquals(-10000.5f, TypedValue.COMPLEX_RADIX_16p7)
92         assertRoundTripEquals(-65535.5f, TypedValue.COMPLEX_RADIX_16p7) // -2^16 + .5
93 
94         // NOTE: -65536.5f fits in COMPLEX_RADIX_16p7 but is stored with COMPLEX_RADIX_23p0 for
95         // simplicity of the algorithm.  However, it's better not to enforce that with a test.
96         assertRoundTripEquals(-65537.5f, TypedValue.COMPLEX_RADIX_23p0) // -2^16 - 1.5
97         assertRoundTripEquals(-100000.5f, TypedValue.COMPLEX_RADIX_23p0)
98         assertRoundTripEquals(-1000000.5f, TypedValue.COMPLEX_RADIX_23p0)
99         assertRoundTripEquals(-8388607.5f, TypedValue.COMPLEX_RADIX_23p0) // 2^23 -.5
100 
101         // Test for every integer value in the range...
102         for (i: Int in -(1 shl 23) until (1 shl 23)) {
103             // ... that true integers are stored as the precise integer
104             assertRoundTripEquals(i.toFloat(), TypedValue.COMPLEX_RADIX_23p0)
105             // ... that values round up when just below an integer
106             assertRoundTripEquals(i - .1f)
107             // ... that values round down when just above an integer
108             assertRoundTripEquals(i + .1f)
109         }
110     }
111 
112     @SmallTest
113     @Test(expected = IllegalArgumentException::class)
114     fun testFloatToComplex_failsIfValueTooLarge() {
115         TypedValue.floatToComplex(8388607.5f) // 2^23 - .5
116     }
117 
118     @SmallTest
119     @Test(expected = IllegalArgumentException::class)
120     fun testFloatToComplex_failsIfValueTooSmall() {
121         TypedValue.floatToComplex(8388608.5f) // -2^23 - .5
122     }
123 
124     @LargeTest
125     @Test
126     fun testIntToComplex() {
127         // Validates every single valid value
128         for (value: Int in -(1 shl 23) until (1 shl 23)) {
129             assertEquals(value.toFloat(), TypedValue.complexToFloat(TypedValue.intToComplex(value)))
130         }
131     }
132 
133     @SmallTest
134     @Test(expected = IllegalArgumentException::class)
135     fun testIntToComplex_failsIfValueTooLarge() {
136         TypedValue.intToComplex(0x800000)
137     }
138 
139     @SmallTest
140     @Test(expected = IllegalArgumentException::class)
141     fun testIntToComplex_failsIfValueTooSmall() {
142         TypedValue.intToComplex(-0x800001)
143     }
144 
145     @SmallTest
146     @Test
147     fun testCreateComplexDimension_appliesUnits() {
148         val metrics: DisplayMetrics = mock(DisplayMetrics::class.java)
149         metrics.density = 3.25f
150 
151         val height = 52 * metrics.density
152         val widthFloat = height * 16 / 9
153         val widthDimen = TypedValue.createComplexDimension(
154                 widthFloat / metrics.density,
155                 TypedValue.COMPLEX_UNIT_DIP
156         )
157         val widthPx = TypedValue.complexToDimensionPixelSize(widthDimen, metrics)
158         assertEquals(widthFloat.roundToInt(), widthPx)
159     }
160 
161     @SmallTest
162     @Test
163     fun testNonLinearFontScaling_nullLookupFallsBackToScaledDensity() {
164         val metrics: DisplayMetrics = mock(DisplayMetrics::class.java)
165         val fontScale = 2f
166         metrics.density = 1f
167         metrics.xdpi = 2f
168         metrics.scaledDensity = fontScale * metrics.density
169         metrics.fontScaleConverter = null
170 
171         assertThat(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f, metrics))
172                 .isEqualTo(20f)
173         assertThat(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 50f, metrics))
174                 .isEqualTo(100f)
175         assertThat(TypedValue.deriveDimension(TypedValue.COMPLEX_UNIT_SP, 20f, metrics))
176                 .isEqualTo(10f)
177         assertThat(TypedValue.deriveDimension(TypedValue.COMPLEX_UNIT_SP, 100f, metrics))
178                 .isEqualTo(50f)
179     }
180 
181     @LargeTest
182     @Test
183     fun testNonLinearFontScalingIsNull_deriveDimensionInversesApplyDimension() {
184         val metrics: DisplayMetrics = mock(DisplayMetrics::class.java)
185         val fontScale = 2f
186         metrics.density = 1f
187         metrics.xdpi = 2f
188         metrics.scaledDensity = fontScale * metrics.density
189         metrics.fontScaleConverter = null
190 
191         verifyRoundTripsForEachUnitType(metrics)
192     }
193 
194     @LargeTest
195     @Test
196     fun testNonLinearFontScalingIs2_deriveDimensionInversesApplyDimension() {
197         val metrics: DisplayMetrics = mock(DisplayMetrics::class.java)
198         val fontScale = 2f
199         metrics.density = 1f
200         metrics.xdpi = 2f
201         metrics.scaledDensity = fontScale * metrics.density
202         metrics.fontScaleConverter = FontScaleConverterFactory.forScale(fontScale)
203 
204         verifyRoundTripsForEachUnitType(metrics)
205     }
206 
207     @Test
208     fun invalidUnitThrows() {
209         val metrics: DisplayMetrics = mock(DisplayMetrics::class.java)
210         val fontScale = 2f
211         metrics.density = 1f
212         metrics.xdpi = 2f
213         metrics.scaledDensity = fontScale * metrics.density
214 
215         assertThrows(IllegalArgumentException::class.java) {
216             TypedValue.deriveDimension(TypedValue.COMPLEX_UNIT_MM + 1, 23f, metrics)
217         }
218     }
219 
220     @Test
221     fun density0_deriveDoesNotCrash() {
222         val metrics: DisplayMetrics = mock(DisplayMetrics::class.java)
223         metrics.density = 0f
224         metrics.xdpi = 0f
225         metrics.scaledDensity = 0f
226 
227         listOf(
228             TypedValue.COMPLEX_UNIT_DIP,
229             TypedValue.COMPLEX_UNIT_SP,
230             TypedValue.COMPLEX_UNIT_PT,
231             TypedValue.COMPLEX_UNIT_IN,
232             TypedValue.COMPLEX_UNIT_MM
233         )
234             .forEach { dimenType ->
235                 assertThat(TypedValue.deriveDimension(dimenType, 23f, metrics))
236                     .isEqualTo(0)
237             }
238     }
239 
240     @Test
241     fun scaledDensity0_deriveSpDoesNotCrash() {
242         val metrics: DisplayMetrics = mock(DisplayMetrics::class.java)
243         metrics.density = 1f
244         metrics.xdpi = 2f
245         metrics.scaledDensity = 0f
246 
247         assertThat(TypedValue.deriveDimension(TypedValue.COMPLEX_UNIT_SP, 23f, metrics))
248             .isEqualTo(0)
249     }
250 
251     private fun verifyRoundTripsForEachUnitType(metrics: DisplayMetrics) {
252         listOf(
253             TypedValue.COMPLEX_UNIT_PX,
254             TypedValue.COMPLEX_UNIT_DIP,
255             TypedValue.COMPLEX_UNIT_SP,
256             TypedValue.COMPLEX_UNIT_PT,
257             TypedValue.COMPLEX_UNIT_IN,
258             TypedValue.COMPLEX_UNIT_MM
259         )
260             .forEach { dimenType ->
261                 for (i: Int in -10000 until 10000) {
262                     assertRoundTripIsEqual(i.toFloat(), dimenType, metrics)
263                     assertRoundTripIsEqual(i - .1f, dimenType, metrics)
264                     assertRoundTripIsEqual(i + .5f, dimenType, metrics)
265                 }
266             }
267     }
268 
269     private fun assertRoundTripIsEqual(
270         dimenValueToTest: Float,
271         @TypedValue.ComplexDimensionUnit dimenType: Int,
272         metrics: DisplayMetrics,
273     ) {
274         val actualPx = TypedValue.applyDimension(dimenType, dimenValueToTest, metrics)
275         val actualDimenValue = TypedValue.deriveDimension(dimenType, actualPx, metrics)
276         assertThat(dimenValueToTest)
277             .isWithin(0.05f)
278             .of(actualDimenValue)
279 
280         // Also test the alias functions
281         assertThat(TypedValue.convertDimensionToPixels(dimenType, dimenValueToTest, metrics))
282             .isWithin(0.05f)
283             .of(actualPx)
284         assertThat(TypedValue.convertPixelsToDimension(dimenType, actualPx, metrics))
285             .isWithin(0.05f)
286             .of(actualDimenValue)
287     }
288 }
289