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