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