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 BUILD_TYPE_PROPERTY = "ro.build.type";
42     private static final String MANUFACTURER_PROPERTY = "ro.product.manufacturer";
43     private static final String TAG_DEV_KEYS = "dev-keys";
44     private static final String VENDOR_API_LEVEL = "ro.board.api_level";
45     private static final String VENDOR_FIRST_API_LEVEL = "ro.board.first_api_level";
46     private static final String VNDK_VERSION = "ro.vndk.version";
47     private static final String CAMERAX_EXTENSIONS_ENABLED = "ro.camerax.extensions.enabled";
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 of the vendor partition. It will read the following properties in order
93      * and returns the value of the first defined property. If none of them are defined, or the
94      * value is a VERSION CODENAME, returns the current API level which is defined in
95      * API_LEVEL_CURRENT.
96      *
97      * <ul>
98      *   <li> ro.board.api_level
99      *   <li> ro.board.first_api_level
100      *   <li> ro.vndk.version
101      * </ul>
102      */
getVendorApiLevel()103     public static int getVendorApiLevel() {
104         String[] vendorApiLevelProps = {
105             // Use the properties in order.
106             VENDOR_API_LEVEL, VENDOR_FIRST_API_LEVEL, VNDK_VERSION,
107         };
108         for (String prop : vendorApiLevelProps) {
109             int apiLevel = getPropertyInt(prop);
110             if (apiLevel != INT_VALUE_IF_UNSET) {
111                 return apiLevel;
112             }
113         }
114         return API_LEVEL_CURRENT;
115     }
116 
117     /**
118      * Return whether the API level of the vendor partition is newer than the given API level.
119      */
isVendorApiLevelNewerThan(int apiLevel)120     public static boolean isVendorApiLevelNewerThan(int apiLevel) {
121         return getVendorApiLevel() > apiLevel;
122     }
123 
124     /**
125      * Return whether the API level of the vendor partition is same or newer than the
126      * given API level.
127      */
isVendorApiLevelAtLeast(int apiLevel)128     public static boolean isVendorApiLevelAtLeast(int apiLevel) {
129         return getVendorApiLevel() >= apiLevel;
130     }
131 
132     /**
133      * Return whether the VNDK version of the vendor partition is newer than the given API level.
134      * If the property is set to non-integer value, this means the vendor partition is using
135      * current API level and true is returned.
136      */
isVndkApiLevelNewerThan(int apiLevel)137     public static boolean isVndkApiLevelNewerThan(int apiLevel) {
138         int vndkApiLevel = getPropertyInt(VNDK_VERSION);
139         if (vndkApiLevel == INT_VALUE_IF_UNSET) {
140             return true;
141         }
142         return vndkApiLevel > apiLevel;
143     }
144 
145     /**
146      * Return whether the VNDK version of the vendor partition is same or newer than the
147      * given API level.
148      * If the property is set to non-integer value, this means the vendor partition is using
149      * current API level and true is returned.
150      */
isVndkApiLevelAtLeast(int apiLevel)151     public static boolean isVndkApiLevelAtLeast(int apiLevel) {
152         int vndkApiLevel = getPropertyInt(VNDK_VERSION);
153         if (vndkApiLevel == INT_VALUE_IF_UNSET) {
154             return true;
155         }
156         return vndkApiLevel >= apiLevel;
157     }
158 
159     /**
160      * Return the manufacturer of this product. If unset, return null.
161      */
getManufacturer()162     public static String getManufacturer() {
163         return getProperty(MANUFACTURER_PROPERTY);
164     }
165 
166     /** Returns a mapping from client ID names to client ID values */
getClientIds()167     public static Map<String, String> getClientIds() throws IOException {
168         Map<String,String> clientIds = new HashMap<>();
169         String queryOutput = SystemUtil.runShellCommand(
170                 InstrumentationRegistry.getInstrumentation(), GOOGLE_SETTINGS_QUERY);
171         for (String line : queryOutput.split("[\\r?\\n]+")) {
172             // Expected line format: "Row: 1 _id=123, name=<property_name>, value=<property_value>"
173             Pattern pattern = Pattern.compile("name=([a-z_]*), value=(.*)$");
174             Matcher matcher = pattern.matcher(line);
175             if (matcher.find()) {
176                 String name = matcher.group(1);
177                 String value = matcher.group(2);
178                 if (name.contains("client_id")) {
179                     clientIds.put(name, value); // only add name-value pair for client ids
180                 }
181             }
182         }
183         return clientIds;
184     }
185 
186     /** Returns whether the property exists on this device */
propertyExists(String property)187     public static boolean propertyExists(String property) {
188         return getProperty(property) != null;
189     }
190 
191     /** Returns whether the property value is equal to a given string */
propertyEquals(String property, String value)192     public static boolean propertyEquals(String property, String value) {
193         if (value == null) {
194             return !propertyExists(property); // null value implies property does not exist
195         }
196         return value.equals(getProperty(property));
197     }
198 
199     /**
200      * Returns whether the property value matches a given regular expression. The method uses
201      * String.matches(), requiring a complete match (i.e. expression matches entire value string)
202      */
propertyMatches(String property, String regex)203     public static boolean propertyMatches(String property, String regex) {
204         if (regex == null || regex.isEmpty()) {
205             // null or empty pattern implies property does not exist
206             return !propertyExists(property);
207         }
208         String value = getProperty(property);
209         return (value == null) ? false : value.matches(regex);
210     }
211 
212     /**
213      * Retrieves the desired boolean property, returning false if not found.
214      */
getPropertyBoolean(String property)215     public static boolean getPropertyBoolean(String property) {
216         String value = getProperty(property);
217         if (value == null) {
218             return false;
219         }
220         return Boolean.parseBoolean(value);
221     }
222 
223     /**
224      * Retrieves the desired integer property, returning INT_VALUE_IF_UNSET if not found.
225      */
getPropertyInt(String property)226     public static int getPropertyInt(String property) {
227         String value = getProperty(property);
228         if (value == null) {
229             return INT_VALUE_IF_UNSET;
230         }
231         try {
232             return Integer.parseInt(value);
233         } catch (NumberFormatException e) {
234             return INT_VALUE_IF_UNSET;
235         }
236     }
237 
238     /** Retrieves the desired property value in string form */
getProperty(String property)239     public static String getProperty(String property) {
240         Scanner scanner = null;
241         try {
242             Process process = new ProcessBuilder("getprop", property).start();
243             scanner = new Scanner(process.getInputStream());
244             String value = scanner.nextLine().trim();
245             return (value.isEmpty()) ? null : value;
246         } catch (IOException e) {
247             return null;
248         } finally {
249             if (scanner != null) {
250                 scanner.close();
251             }
252         }
253     }
254 }
255