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.CODENAME; 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") 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 */ 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 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 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 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 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 */ 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 */ 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 */ 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 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 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 TAGS_PATTERN = 227 Pattern.compile("^([0-9A-Za-z.,_-]+)$"); 228 private static final Pattern TYPE_PATTERN = 229 Pattern.compile("^([0-9A-Za-z._-]+)$"); 230 231 /** Tests that check for valid values of constants in Build. */ 232 public void testBuildConstants() { 233 // Build.VERSION.* constants tested by BuildVersionTest 234 235 assertTrue(BOARD_PATTERN.matcher(Build.BOARD).matches()); 236 237 assertTrue(BRAND_PATTERN.matcher(Build.BRAND).matches()); 238 239 assertTrue(DEVICE_PATTERN.matcher(Build.DEVICE).matches()); 240 241 // Build.FINGERPRINT tested by BuildVersionTest 242 243 assertTrue(HARDWARE_PATTERN.matcher(Build.HARDWARE).matches()); 244 245 assertNotEmpty(Build.HOST); 246 247 assertTrue(ID_PATTERN.matcher(Build.ID).matches()); 248 249 assertNotEmpty(Build.MANUFACTURER); 250 251 assertNotEmpty(Build.MODEL); 252 253 assertTrue(PRODUCT_PATTERN.matcher(Build.PRODUCT).matches()); 254 255 assertTrue(SERIAL_NUMBER_PATTERN.matcher(Build.SERIAL).matches()); 256 257 assertTrue(TAGS_PATTERN.matcher(Build.TAGS).matches()); 258 259 // No format requirements stated in CDD for Build.TIME 260 261 assertTrue(TYPE_PATTERN.matcher(Build.TYPE).matches()); 262 263 assertNotEmpty(Build.USER); 264 265 // CUR_DEVELOPMENT must be larger than any released version. 266 Field[] fields = Build.VERSION_CODES.class.getDeclaredFields(); 267 for (Field field : fields) { 268 if (field.getType().equals(int.class) && Modifier.isStatic(field.getModifiers())) { 269 String fieldName = field.getName(); 270 final int fieldValue; 271 try { 272 fieldValue = field.getInt(null); 273 } catch (IllegalAccessException e) { 274 throw new AssertionError(e.getMessage()); 275 } 276 if (fieldName.equals("CUR_DEVELOPMENT")) { 277 // It should be okay to change the value of this constant in future, but it 278 // should at least be a conscious decision. 279 assertEquals(10000, fieldValue); 280 } else if (fieldName.equals(CODENAME) && !CODENAME.equals("REL")) { 281 // This is the current development version. Note that fieldName can 282 // become < CUR_DEVELOPMENT before CODENAME becomes "REL", so we 283 // can't assertEquals(CUR_DEVELOPMENT, fieldValue) here. 284 assertTrue("Expected " + fieldName + " value to be <= " + CUR_DEVELOPMENT 285 + ", got " + fieldValue, fieldValue <= CUR_DEVELOPMENT); 286 } else { 287 assertTrue("Expected " + fieldName + " value to be < " + CUR_DEVELOPMENT 288 + ", got " + fieldValue, fieldValue < CUR_DEVELOPMENT); 289 } 290 } 291 } 292 } 293 294 /** 295 * Verify that SDK versions are bounded by both high and low expected 296 * values. 297 */ 298 public void testSdkInt() { 299 assertTrue( 300 "Current SDK version " + Build.VERSION.SDK_INT 301 + " is invalid; must be at least VERSION_CODES.BASE", 302 Build.VERSION.SDK_INT >= Build.VERSION_CODES.BASE); 303 assertTrue( 304 "First SDK version " + Build.VERSION.FIRST_SDK_INT 305 + " is invalid; must be at least VERSION_CODES.BASE", 306 Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.BASE); 307 assertTrue( 308 "Current SDK version " + Build.VERSION.SDK_INT 309 + " must be at least first SDK version " + Build.VERSION.FIRST_SDK_INT, 310 Build.VERSION.SDK_INT >= Build.VERSION.FIRST_SDK_INT); 311 } 312 313 static final String RO_DEBUGGABLE = "ro.debuggable"; 314 private static final String RO_SECURE = "ro.secure"; 315 316 /** 317 * Assert that the device is a secure, not debuggable user build. 318 * 319 * Debuggable devices allow adb root and have the su command, allowing 320 * escalations to root and unauthorized access to application data. 321 * 322 * Note: This test will fail on userdebug / eng devices, but should pass 323 * on production (user) builds. 324 */ 325 @RestrictedBuildTest 326 @AppModeFull(reason = "Instant apps cannot access APIs") 327 public void testIsSecureUserBuild() throws IOException { 328 assertEquals("Must be a user build", "user", Build.TYPE); 329 assertProperty("Must be a non-debuggable build", RO_DEBUGGABLE, "0"); 330 assertProperty("Must be a secure build", RO_SECURE, "1"); 331 } 332 333 private void assertNotEmpty(String value) { 334 assertNotNull(value); 335 assertFalse(value.isEmpty()); 336 } 337 } 338