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