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