1 /* 2 * Copyright (C) 2010 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.os.cts; 18 19 import static android.os.Build.VERSION.ACTIVE_CODENAMES; 20 import static android.os.Build.VERSION_CODES.CUR_DEVELOPMENT; 21 22 import android.os.Build; 23 import android.platform.test.annotations.AppModeFull; 24 import android.platform.test.annotations.RestrictedBuildTest; 25 26 import junit.framework.TestCase; 27 28 import java.io.IOException; 29 import java.lang.reflect.Field; 30 import java.lang.reflect.Modifier; 31 import java.util.Arrays; 32 import java.util.HashSet; 33 import java.util.List; 34 import java.util.Scanner; 35 import java.util.Set; 36 import java.util.regex.Pattern; 37 38 public class BuildTest extends TestCase { 39 40 private static final String RO_PRODUCT_CPU_ABILIST = "ro.product.cpu.abilist"; 41 private static final String RO_PRODUCT_CPU_ABILIST32 = "ro.product.cpu.abilist32"; 42 private static final String RO_PRODUCT_CPU_ABILIST64 = "ro.product.cpu.abilist64"; 43 44 /** 45 * Verify that the values of the various CPU ABI fields are consistent. 46 */ 47 @AppModeFull(reason = "Instant apps cannot access APIs") testCpuAbi()48 public void testCpuAbi() throws Exception { 49 runTestCpuAbiCommon(); 50 if (android.os.Process.is64Bit()) { 51 runTestCpuAbi64(); 52 } else { 53 runTestCpuAbi32(); 54 } 55 } 56 57 /** 58 * Verify that the CPU ABI fields on device match the permitted ABIs defined by CDD. 59 */ testCpuAbi_valuesMatchPermitted()60 public void testCpuAbi_valuesMatchPermitted() throws Exception { 61 // The permitted ABIs are listed in https://developer.android.com/ndk/guides/abis. 62 Set<String> just32 = new HashSet<>(Arrays.asList("armeabi", "armeabi-v7a", "x86")); 63 Set<String> just64 = new HashSet<>(Arrays.asList("x86_64", "arm64-v8a")); 64 Set<String> all = new HashSet<>(); 65 all.addAll(just32); 66 all.addAll(just64); 67 Set<String> allAndEmpty = new HashSet<>(all); 68 allAndEmpty.add(""); 69 70 // The cpu abi fields on the device must match the permitted values. 71 assertValueIsAllowed(all, Build.CPU_ABI); 72 // CPU_ABI2 will be empty when the device does not support a secondary CPU architecture. 73 assertValueIsAllowed(allAndEmpty, Build.CPU_ABI2); 74 75 // The supported abi fields on the device must match the permitted values. 76 assertValuesAreAllowed(all, Build.SUPPORTED_ABIS); 77 assertValuesAreAllowed(just32, Build.SUPPORTED_32_BIT_ABIS); 78 assertValuesAreAllowed(just64, Build.SUPPORTED_64_BIT_ABIS); 79 } 80 runTestCpuAbiCommon()81 private void runTestCpuAbiCommon() throws Exception { 82 // The build property must match Build.SUPPORTED_ABIS exactly. 83 final String[] abiListProperty = getStringList(RO_PRODUCT_CPU_ABILIST); 84 assertEquals(Arrays.toString(abiListProperty), Arrays.toString(Build.SUPPORTED_ABIS)); 85 86 List<String> abiList = Arrays.asList(abiListProperty); 87 88 // Every device must support at least one 32 bit ABI. 89 assertTrue(Build.SUPPORTED_32_BIT_ABIS.length > 0); 90 91 // Every supported 32 bit ABI must be present in Build.SUPPORTED_ABIS. 92 for (String abi : Build.SUPPORTED_32_BIT_ABIS) { 93 assertTrue(abiList.contains(abi)); 94 assertFalse(Build.is64BitAbi(abi)); 95 } 96 97 // Every supported 64 bit ABI must be present in Build.SUPPORTED_ABIS. 98 for (String abi : Build.SUPPORTED_64_BIT_ABIS) { 99 assertTrue(abiList.contains(abi)); 100 assertTrue(Build.is64BitAbi(abi)); 101 } 102 103 // Build.CPU_ABI and Build.CPU_ABI2 must be present in Build.SUPPORTED_ABIS. 104 assertTrue(abiList.contains(Build.CPU_ABI)); 105 if (!Build.CPU_ABI2.isEmpty()) { 106 assertTrue(abiList.contains(Build.CPU_ABI2)); 107 } 108 } 109 runTestCpuAbi32()110 private void runTestCpuAbi32() throws Exception { 111 List<String> abi32 = Arrays.asList(Build.SUPPORTED_32_BIT_ABIS); 112 assertTrue(abi32.contains(Build.CPU_ABI)); 113 114 if (!Build.CPU_ABI2.isEmpty()) { 115 assertTrue(abi32.contains(Build.CPU_ABI2)); 116 } 117 } 118 runTestCpuAbi64()119 private void runTestCpuAbi64() { 120 List<String> abi64 = Arrays.asList(Build.SUPPORTED_64_BIT_ABIS); 121 assertTrue(abi64.contains(Build.CPU_ABI)); 122 123 if (!Build.CPU_ABI2.isEmpty()) { 124 assertTrue(abi64.contains(Build.CPU_ABI2)); 125 } 126 } 127 getStringList(String property)128 private String[] getStringList(String property) throws IOException { 129 String value = getProperty(property); 130 if (value.isEmpty()) { 131 return new String[0]; 132 } else { 133 return value.split(","); 134 } 135 } 136 137 /** 138 * @param property name passed to getprop 139 */ getProperty(String property)140 static String getProperty(String property) 141 throws IOException { 142 Process process = new ProcessBuilder("getprop", property).start(); 143 Scanner scanner = null; 144 String line = ""; 145 try { 146 scanner = new Scanner(process.getInputStream()); 147 line = scanner.nextLine(); 148 } finally { 149 if (scanner != null) { 150 scanner.close(); 151 } 152 } 153 return line; 154 } 155 /** 156 * @param message shown when the test fails 157 * @param property name passed to getprop 158 * @param expected value of the property 159 */ assertProperty(String message, String property, String expected)160 private void assertProperty(String message, String property, String expected) 161 throws IOException { 162 Process process = new ProcessBuilder("getprop", property).start(); 163 Scanner scanner = null; 164 try { 165 scanner = new Scanner(process.getInputStream()); 166 String line = scanner.nextLine(); 167 assertEquals(message + " Value found: " + line , expected, line); 168 assertFalse(scanner.hasNext()); 169 } finally { 170 if (scanner != null) { 171 scanner.close(); 172 } 173 } 174 } 175 176 /** 177 * Check that a property is not set by scanning through the list of properties returned by 178 * getprop, since calling getprop on an property set to "" and on a non-existent property 179 * yields the same output. 180 * 181 * @param message shown when the test fails 182 * @param property name passed to getprop 183 */ assertNoPropertySet(String message, String property)184 private void assertNoPropertySet(String message, String property) throws IOException { 185 Process process = new ProcessBuilder("getprop").start(); 186 Scanner scanner = null; 187 try { 188 scanner = new Scanner(process.getInputStream()); 189 while (scanner.hasNextLine()) { 190 String line = scanner.nextLine(); 191 assertFalse(message + "Property found: " + line, 192 line.startsWith("[" + property + "]")); 193 } 194 } finally { 195 if (scanner != null) { 196 scanner.close(); 197 } 198 } 199 } 200 assertValueIsAllowed(Set<String> allowedValues, String actualValue)201 private static void assertValueIsAllowed(Set<String> allowedValues, String actualValue) { 202 assertTrue("Expected one of " + allowedValues + ", but was: '" + actualValue + "'", 203 allowedValues.contains(actualValue)); 204 } 205 assertValuesAreAllowed(Set<String> allowedValues, String[] actualValues)206 private static void assertValuesAreAllowed(Set<String> allowedValues, String[] actualValues) { 207 for (String actualValue : actualValues) { 208 assertValueIsAllowed(allowedValues, actualValue); 209 } 210 } 211 212 private static final Pattern BOARD_PATTERN = 213 Pattern.compile("^([0-9A-Za-z._-]+)$"); 214 private static final Pattern BRAND_PATTERN = 215 Pattern.compile("^([0-9A-Za-z._-]+)$"); 216 private static final Pattern DEVICE_PATTERN = 217 Pattern.compile("^([0-9A-Za-z._-]+)$"); 218 private static final Pattern ID_PATTERN = 219 Pattern.compile("^([0-9A-Za-z._-]+)$"); 220 private static final Pattern HARDWARE_PATTERN = 221 Pattern.compile("^([0-9A-Za-z.,_-]+)$"); 222 private static final Pattern PRODUCT_PATTERN = 223 Pattern.compile("^([0-9A-Za-z._-]+)$"); 224 private static final Pattern SERIAL_NUMBER_PATTERN = 225 Pattern.compile("^([0-9A-Za-z]{6,20})$"); 226 private static final Pattern SKU_PATTERN = 227 Pattern.compile("^([0-9A-Za-z.,_-]+)$"); 228 private static final Pattern TAGS_PATTERN = 229 Pattern.compile("^([0-9A-Za-z.,_-]+)$"); 230 private static final Pattern TYPE_PATTERN = 231 Pattern.compile("^([0-9A-Za-z._-]+)$"); 232 233 /** Tests that check for valid values of constants in Build. */ testBuildConstants()234 public void testBuildConstants() { 235 // Build.VERSION.* constants tested by BuildVersionTest 236 237 assertTrue(BOARD_PATTERN.matcher(Build.BOARD).matches()); 238 239 assertTrue(BRAND_PATTERN.matcher(Build.BRAND).matches()); 240 241 assertTrue(DEVICE_PATTERN.matcher(Build.DEVICE).matches()); 242 243 // Build.FINGERPRINT tested by BuildVersionTest 244 245 assertTrue(HARDWARE_PATTERN.matcher(Build.HARDWARE).matches()); 246 247 assertNotEmpty(Build.HOST); 248 249 assertTrue(ID_PATTERN.matcher(Build.ID).matches()); 250 251 assertNotEmpty(Build.MANUFACTURER); 252 253 assertNotEmpty(Build.MODEL); 254 255 assertTrue(PRODUCT_PATTERN.matcher(Build.PRODUCT).matches()); 256 257 assertTrue(SERIAL_NUMBER_PATTERN.matcher(Build.SERIAL).matches()); 258 259 assertTrue(SKU_PATTERN.matcher(Build.SKU).matches()); 260 261 assertTrue(TAGS_PATTERN.matcher(Build.TAGS).matches()); 262 263 // No format requirements stated in CDD for Build.TIME 264 265 assertTrue(TYPE_PATTERN.matcher(Build.TYPE).matches()); 266 267 assertNotEmpty(Build.USER); 268 269 // CUR_DEVELOPMENT must be larger than any released version. 270 Field[] fields = Build.VERSION_CODES.class.getDeclaredFields(); 271 List<String> codenames = Arrays.asList(ACTIVE_CODENAMES); 272 for (Field field : fields) { 273 if (field.getType().equals(int.class) && Modifier.isStatic(field.getModifiers())) { 274 String fieldName = field.getName(); 275 final int fieldValue; 276 try { 277 fieldValue = field.getInt(null); 278 } catch (IllegalAccessException e) { 279 throw new AssertionError(e.getMessage()); 280 } 281 if (fieldName.equals("CUR_DEVELOPMENT")) { 282 // It should be okay to change the value of this constant in future, but it 283 // should at least be a conscious decision. 284 assertEquals(10000, fieldValue); 285 } else if (codenames.contains(fieldName)) { 286 // This is the current development version. Note that fieldName can 287 // become < CUR_DEVELOPMENT before CODENAME becomes "REL", so we 288 // can't assertEquals(CUR_DEVELOPMENT, fieldValue) here. 289 assertTrue("Expected " + fieldName + " value to be <= " + CUR_DEVELOPMENT 290 + ", got " + fieldValue, fieldValue <= CUR_DEVELOPMENT); 291 } else { 292 assertTrue("Expected " + fieldName + " value to be < " + CUR_DEVELOPMENT 293 + ", got " + fieldValue, fieldValue < CUR_DEVELOPMENT); 294 } 295 } 296 } 297 } 298 299 /** 300 * Verify that SDK versions are bounded by both high and low expected 301 * values. 302 */ testSdkInt()303 public void testSdkInt() { 304 assertTrue( 305 "Current SDK version " + Build.VERSION.SDK_INT 306 + " is invalid; must be at least VERSION_CODES.BASE", 307 Build.VERSION.SDK_INT >= Build.VERSION_CODES.BASE); 308 assertTrue( 309 "First SDK version " + Build.VERSION.FIRST_SDK_INT 310 + " is invalid; must be at least VERSION_CODES.BASE", 311 Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.BASE); 312 assertTrue( 313 "Current SDK version " + Build.VERSION.SDK_INT 314 + " must be at least first SDK version " + Build.VERSION.FIRST_SDK_INT, 315 Build.VERSION.SDK_INT >= Build.VERSION.FIRST_SDK_INT); 316 } 317 318 static final String RO_DEBUGGABLE = "ro.debuggable"; 319 private static final String RO_SECURE = "ro.secure"; 320 321 /** 322 * Assert that the device is a secure, not debuggable user build. 323 * 324 * Debuggable devices allow adb root and have the su command, allowing 325 * escalations to root and unauthorized access to application data. 326 * 327 * Note: This test will fail on userdebug / eng devices, but should pass 328 * on production (user) builds. 329 */ 330 @RestrictedBuildTest 331 @AppModeFull(reason = "Instant apps cannot access APIs") testIsSecureUserBuild()332 public void testIsSecureUserBuild() throws IOException { 333 assertEquals("Must be a user build", "user", Build.TYPE); 334 assertProperty("Must be a non-debuggable build", RO_DEBUGGABLE, "0"); 335 assertProperty("Must be a secure build", RO_SECURE, "1"); 336 } 337 assertNotEmpty(String value)338 private void assertNotEmpty(String value) { 339 assertNotNull(value); 340 assertFalse(value.isEmpty()); 341 } 342 } 343