• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 com.android.compatibility.common.util;
18 
19 import android.os.Build;
20 
21 import androidx.test.InstrumentationRegistry;
22 
23 import java.io.IOException;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.Scanner;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29 
30 /**
31  * Device-side utility class for reading properties and gathering information for testing
32  * Android device compatibility.
33  */
34 public class PropertyUtil {
35 
36     /**
37      * Name of read-only property detailing the first API level for which the product was
38      * shipped. Property should be undefined for factory ROM products.
39      */
40     public static final String FIRST_API_LEVEL = "ro.product.first_api_level";
41     private static final String BOARD_API_LEVEL = "ro.board.api_level";
42     private static final String BOARD_FIRST_API_LEVEL = "ro.board.first_api_level";
43     private static final String BUILD_TYPE_PROPERTY = "ro.build.type";
44     private static final String CAMERAX_EXTENSIONS_ENABLED = "ro.camerax.extensions.enabled";
45     private static final String MANUFACTURER_PROPERTY = "ro.product.manufacturer";
46     private static final String TAG_DEV_KEYS = "dev-keys";
47     private static final String VNDK_VERSION = "ro.vndk.version";
48 
49     public static final String GOOGLE_SETTINGS_QUERY =
50             "content query --uri content://com.google.settings/partner";
51 
52     /** Value to be returned by getPropertyInt() if property is not found */
53     public static int INT_VALUE_IF_UNSET = -1;
54 
55     /** API level for current in development */
56     public static final int API_LEVEL_CURRENT = 10000;
57 
58     /** Returns whether the device build is a user build */
isUserBuild()59     public static boolean isUserBuild() {
60         return propertyEquals(BUILD_TYPE_PROPERTY, "user");
61     }
62 
63     /** Returns whether this build is built with dev-keys */
isDevKeysBuild()64     public static boolean isDevKeysBuild() {
65         for (String tag : Build.TAGS.split(",")) {
66             if (TAG_DEV_KEYS.equals(tag.trim())) {
67                 return true;
68             }
69         }
70         return false;
71     }
72 
73     /**
74      * Return the CameraX extensions enabled property value. If the read-only property is unset,
75      * the default value returned will be 'false'.
76      */
areCameraXExtensionsEnabled()77     public static boolean areCameraXExtensionsEnabled() {
78         return getPropertyBoolean(CAMERAX_EXTENSIONS_ENABLED);
79     }
80 
81     /**
82      * Return the first API level for this product. If the read-only property is unset,
83      * this means the first API level is the current API level, and the current API level
84      * is returned.
85      */
getFirstApiLevel()86     public static int getFirstApiLevel() {
87         int firstApiLevel = getPropertyInt(FIRST_API_LEVEL);
88         return (firstApiLevel == INT_VALUE_IF_UNSET) ? Build.VERSION.SDK_INT : firstApiLevel;
89     }
90 
91     /**
92      * Return the API level that the VSR requirement must be fulfilled. It reads
93      * ro.product.first_api_level and ro.board.first_api_level to find the minimum required VSR
94      * api_level for the DUT.
95      */
getVsrApiLevel()96     public static int getVsrApiLevel() {
97         // Api level properties of the board. The order of the properties must be kept.
98         String[] boardApiLevelProps = {BOARD_API_LEVEL, BOARD_FIRST_API_LEVEL};
99         for (String apiLevelProp : boardApiLevelProps) {
100             int apiLevel = getPropertyInt(apiLevelProp);
101             if (apiLevel != INT_VALUE_IF_UNSET) {
102                 return Math.min(apiLevel, getFirstApiLevel());
103             }
104         }
105         return getFirstApiLevel();
106     }
107 
108     /**
109      * Return the API level of the vendor partition. It will read the following properties in order
110      * and returns the value of the first defined property. If none of them are defined, or the
111      * value is a VERSION CODENAME, returns the current API level which is defined in
112      * API_LEVEL_CURRENT.
113      *
114      * <ul>
115      *   <li> ro.board.api_level
116      *   <li> ro.board.first_api_level
117      *   <li> ro.vndk.version
118      * </ul>
119      */
getVendorApiLevel()120     public static int getVendorApiLevel() {
121         String[] vendorApiLevelProps = {
122             // Use the properties in order.
123             BOARD_API_LEVEL, BOARD_FIRST_API_LEVEL, VNDK_VERSION,
124         };
125         for (String prop : vendorApiLevelProps) {
126             int apiLevel = getPropertyInt(prop);
127             if (apiLevel != INT_VALUE_IF_UNSET) {
128                 return apiLevel;
129             }
130         }
131         return API_LEVEL_CURRENT;
132     }
133 
134     /**
135      * Return whether the API level of the vendor partition is newer than the given API level.
136      */
isVendorApiLevelNewerThan(int apiLevel)137     public static boolean isVendorApiLevelNewerThan(int apiLevel) {
138         return getVendorApiLevel() > apiLevel;
139     }
140 
141     /**
142      * Return whether the API level of the vendor partition is same or newer than the
143      * given API level.
144      */
isVendorApiLevelAtLeast(int apiLevel)145     public static boolean isVendorApiLevelAtLeast(int apiLevel) {
146         return getVendorApiLevel() >= apiLevel;
147     }
148 
149     /**
150      * Return whether the VNDK version of the vendor partition is newer than the given API level.
151      * If the property is set to non-integer value, this means the vendor partition is using
152      * current API level and true is returned.
153      */
isVndkApiLevelNewerThan(int apiLevel)154     public static boolean isVndkApiLevelNewerThan(int apiLevel) {
155         int vndkApiLevel = getPropertyInt(VNDK_VERSION);
156         if (vndkApiLevel == INT_VALUE_IF_UNSET) {
157             return true;
158         }
159         return vndkApiLevel > apiLevel;
160     }
161 
162     /**
163      * Return whether the VNDK version of the vendor partition is same or newer than the
164      * given API level.
165      * If the property is set to non-integer value, this means the vendor partition is using
166      * current API level and true is returned.
167      */
isVndkApiLevelAtLeast(int apiLevel)168     public static boolean isVndkApiLevelAtLeast(int apiLevel) {
169         int vndkApiLevel = getPropertyInt(VNDK_VERSION);
170         if (vndkApiLevel == INT_VALUE_IF_UNSET) {
171             return true;
172         }
173         return vndkApiLevel >= apiLevel;
174     }
175 
176     /**
177      * Return the manufacturer of this product. If unset, return null.
178      */
getManufacturer()179     public static String getManufacturer() {
180         return getProperty(MANUFACTURER_PROPERTY);
181     }
182 
183     /** Returns a mapping from client ID names to client ID values */
getClientIds()184     public static Map<String, String> getClientIds() throws IOException {
185         Map<String,String> clientIds = new HashMap<>();
186         String queryOutput = SystemUtil.runShellCommand(
187                 InstrumentationRegistry.getInstrumentation(), GOOGLE_SETTINGS_QUERY);
188         for (String line : queryOutput.split("[\\r?\\n]+")) {
189             // Expected line format: "Row: 1 _id=123, name=<property_name>, value=<property_value>"
190             Pattern pattern = Pattern.compile("name=([a-z_]*), value=(.*)$");
191             Matcher matcher = pattern.matcher(line);
192             if (matcher.find()) {
193                 String name = matcher.group(1);
194                 String value = matcher.group(2);
195                 if (name.contains("client_id")) {
196                     clientIds.put(name, value); // only add name-value pair for client ids
197                 }
198             }
199         }
200         return clientIds;
201     }
202 
203     /** Returns whether the property exists on this device */
propertyExists(String property)204     public static boolean propertyExists(String property) {
205         return getProperty(property) != null;
206     }
207 
208     /** Returns whether the property value is equal to a given string */
propertyEquals(String property, String value)209     public static boolean propertyEquals(String property, String value) {
210         if (value == null) {
211             return !propertyExists(property); // null value implies property does not exist
212         }
213         return value.equals(getProperty(property));
214     }
215 
216     /**
217      * Returns whether the property value matches a given regular expression. The method uses
218      * String.matches(), requiring a complete match (i.e. expression matches entire value string)
219      */
propertyMatches(String property, String regex)220     public static boolean propertyMatches(String property, String regex) {
221         if (regex == null || regex.isEmpty()) {
222             // null or empty pattern implies property does not exist
223             return !propertyExists(property);
224         }
225         String value = getProperty(property);
226         return (value == null) ? false : value.matches(regex);
227     }
228 
229     /**
230      * Retrieves the desired boolean property, returning false if not found.
231      */
getPropertyBoolean(String property)232     public static boolean getPropertyBoolean(String property) {
233         String value = getProperty(property);
234         if (value == null) {
235             return false;
236         }
237         return Boolean.parseBoolean(value);
238     }
239 
240     /**
241      * Retrieves the desired integer property, returning INT_VALUE_IF_UNSET if not found.
242      */
getPropertyInt(String property)243     public static int getPropertyInt(String property) {
244         String value = getProperty(property);
245         if (value == null) {
246             return INT_VALUE_IF_UNSET;
247         }
248         try {
249             return Integer.parseInt(value);
250         } catch (NumberFormatException e) {
251             return INT_VALUE_IF_UNSET;
252         }
253     }
254 
255     /** Retrieves the desired property value in string form */
getProperty(String property)256     public static String getProperty(String property) {
257         Scanner scanner = null;
258         try {
259             Process process = new ProcessBuilder("getprop", property).start();
260             scanner = new Scanner(process.getInputStream());
261             String value = scanner.nextLine().trim();
262             return (value.isEmpty()) ? null : value;
263         } catch (IOException e) {
264             return null;
265         } finally {
266             if (scanner != null) {
267                 scanner.close();
268             }
269         }
270     }
271 
272     /** Retrieves a map of prop to value for all props with the given prefix */
getPropertiesWithPrefix(String prefix)273     public static Map<String, String> getPropertiesWithPrefix(String prefix) {
274         Map<String, String> result = new HashMap<>();
275         Pattern pattern = Pattern.compile("\\[(.*)\\]: \\[(.*)\\]");
276         Scanner scanner = null;
277         try {
278             Process process = new ProcessBuilder("getprop").start();
279             scanner = new Scanner(process.getInputStream());
280             while (scanner.hasNextLine()) {
281                 String line = scanner.nextLine().trim();
282                 Matcher matcher = pattern.matcher(line);
283                 if (matcher.find()) {
284                     String prop = matcher.group(1);
285                     String value = matcher.group(2);
286                     if (prop.startsWith(prefix)) {
287                         result.put(prop, value);
288                     }
289                 }
290             }
291             return result;
292         } catch (IOException e) {
293             return result;
294         } finally {
295             if (scanner != null) {
296                 scanner.close();
297             }
298         }
299     }
300 }
301