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 package com.android.compatibility.common.util;
17 
18 import com.android.tradefed.device.DeviceNotAvailableException;
19 import com.android.tradefed.device.ITestDevice;
20 
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25 
26 /**
27  * Host-side utility class for reading properties and gathering information for testing
28  * Android device compatibility.
29  */
30 public class PropertyUtil {
31 
32     /**
33      * Name of read-only property detailing the first API level for which the product was
34      * shipped. Property should be undefined for factory ROM products.
35      */
36     public static final String FIRST_API_LEVEL = "ro.product.first_api_level";
37 
38     private static final String BOARD_API_LEVEL = "ro.board.api_level";
39     private static final String BOARD_FIRST_API_LEVEL = "ro.board.first_api_level";
40     private static final String BUILD_TAGS_PROPERTY = "ro.build.tags";
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.vendor.api_level";
45     private static final String VNDK_VERSION = "ro.vndk.version";
46 
47     /** Value to be returned by getPropertyInt() if property is not found */
48     public static final int INT_VALUE_IF_UNSET = -1;
49 
50     /** API level for current in development */
51     public static final int API_LEVEL_CURRENT = 10000;
52 
53     public static final String GOOGLE_SETTINGS_QUERY =
54             "content query --uri content://com.google.settings/partner";
55 
PropertyUtil()56     private PropertyUtil() {}
57 
58     /** Returns whether the device build is a user build */
isUserBuild(ITestDevice device)59     public static boolean isUserBuild(ITestDevice device) throws DeviceNotAvailableException {
60         return propertyEquals(device, BUILD_TYPE_PROPERTY, "user");
61     }
62 
63     /** Returns whether this build is built with dev-keys */
isDevKeysBuild(ITestDevice device)64     public static boolean isDevKeysBuild(ITestDevice device) throws DeviceNotAvailableException {
65         String buildTags = device.getProperty(BUILD_TAGS_PROPERTY);
66         for (String tag : buildTags.split(",")) {
67             if (TAG_DEV_KEYS.equals(tag.trim())) {
68                 return true;
69             }
70         }
71         return false;
72     }
73 
74     /**
75      * Return the first API level for this product. If the read-only property is unset, this means
76      * the first API level is the current API level, and the current API level is returned.
77      */
getFirstApiLevel(ITestDevice device)78     public static int getFirstApiLevel(ITestDevice device) throws DeviceNotAvailableException {
79         int firstApiLevel = getPropertyInt(device, FIRST_API_LEVEL);
80         return (firstApiLevel == INT_VALUE_IF_UNSET) ? device.getApiLevel() : firstApiLevel;
81     }
82 
83     /**
84      * Return the API level that the VSR requirement must be fulfilled. It reads
85      * ro.vendor.api_level. If not provided for old devices, read ro.product.first_api_level,
86      * ro.board.api_level and ro.board.first_api_level to find the minimum required VSR api level of
87      * the DUT.
88      */
getVsrApiLevel(ITestDevice device)89     public static int getVsrApiLevel(ITestDevice device) throws DeviceNotAvailableException {
90         int vendorApiLevel = getPropertyInt(device, VENDOR_API_LEVEL);
91         if (vendorApiLevel != INT_VALUE_IF_UNSET) {
92             return vendorApiLevel;
93         }
94         // Fallback to api level calculation for old devices.
95         String[] boardApiLevelProps = {BOARD_API_LEVEL, BOARD_FIRST_API_LEVEL};
96         for (String apiLevelProp : boardApiLevelProps) {
97             int apiLevel = getPropertyInt(device, apiLevelProp);
98             if (apiLevel != INT_VALUE_IF_UNSET) {
99                 return Math.min(apiLevel, getFirstApiLevel(device));
100             }
101         }
102         return getFirstApiLevel(device);
103     }
104 
105     /**
106      * Return the API level of the vendor partition. It will read the following properties in order
107      * and returns the value of the first defined property. If none of them are defined, or the
108      * value is a VERSION CODENAME, returns the current API level which is defined in
109      * API_LEVEL_CURRENT.
110      *
111      * <ul>
112      *   <li>ro.board.api_level
113      *   <li>ro.board.first_api_level
114      *   <li>ro.vndk.version
115      * </ul>
116      */
getVendorApiLevel(ITestDevice device)117     public static int getVendorApiLevel(ITestDevice device) throws DeviceNotAvailableException {
118         String[] vendorApiLevelProps = {
119             // Use the properties in order.
120             BOARD_API_LEVEL, BOARD_FIRST_API_LEVEL, VNDK_VERSION,
121         };
122         for (String prop : vendorApiLevelProps) {
123             int apiLevel = getPropertyInt(device, prop);
124             if (apiLevel != INT_VALUE_IF_UNSET) {
125                 return apiLevel;
126             }
127         }
128         return API_LEVEL_CURRENT;
129     }
130 
131     /** Return whether the API level of the vendor partition is newer than the given API level. */
isVendorApiLevelNewerThan(ITestDevice device, int apiLevel)132     public static boolean isVendorApiLevelNewerThan(ITestDevice device, int apiLevel)
133             throws DeviceNotAvailableException {
134         return getVendorApiLevel(device) > apiLevel;
135     }
136 
137     /**
138      * Return whether the API level of the vendor partition is same or newer than the given API
139      * level.
140      */
isVendorApiLevelAtLeast(ITestDevice device, int apiLevel)141     public static boolean isVendorApiLevelAtLeast(ITestDevice device, int apiLevel)
142             throws DeviceNotAvailableException {
143         return getVendorApiLevel(device) >= apiLevel;
144     }
145 
146     /**
147      * Return the manufacturer of this product. If unset, return null.
148      */
getManufacturer(ITestDevice device)149     public static String getManufacturer(ITestDevice device) throws DeviceNotAvailableException {
150         return device.getProperty(MANUFACTURER_PROPERTY);
151     }
152 
153     /** Returns a mapping from client ID names to client ID values */
getClientIds(ITestDevice device)154     public static Map<String, String> getClientIds(ITestDevice device)
155             throws DeviceNotAvailableException {
156         Map<String,String> clientIds = new HashMap<>();
157         String queryOutput = device.executeShellCommand(GOOGLE_SETTINGS_QUERY);
158         for (String line : queryOutput.split("[\\r?\\n]+")) {
159             // Expected line format: "Row: 1 _id=123, name=<property_name>, value=<property_value>"
160             Pattern pattern = Pattern.compile("name=([a-z_]*), value=(.*)$");
161             Matcher matcher = pattern.matcher(line);
162             if (matcher.find()) {
163                 String name = matcher.group(1);
164                 String value = matcher.group(2);
165                 if (name.contains("client_id")) {
166                     clientIds.put(name, value); // only add name-value pair for client ids
167                 }
168             }
169         }
170         return clientIds;
171     }
172 
173     /** Returns whether the property exists on this device */
propertyExists(ITestDevice device, String property)174     public static boolean propertyExists(ITestDevice device, String property)
175             throws DeviceNotAvailableException {
176         return device.getProperty(property) != null;
177     }
178 
179     /** Returns whether the property value is equal to a given string */
propertyEquals(ITestDevice device, String property, String value)180     public static boolean propertyEquals(ITestDevice device, String property, String value)
181             throws DeviceNotAvailableException {
182         if (value == null) {
183             return !propertyExists(device, property); // null value implies property does not exist
184         }
185         return value.equals(device.getProperty(property));
186     }
187 
188     /**
189      * Returns whether the property value matches a given regular expression. The method uses
190      * String.matches(), requiring a complete match (i.e. expression matches entire value string)
191      */
propertyMatches(ITestDevice device, String property, String regex)192     public static boolean propertyMatches(ITestDevice device, String property, String regex)
193             throws DeviceNotAvailableException {
194         if (regex == null || regex.isEmpty()) {
195             // null or empty pattern implies property does not exist
196             return !propertyExists(device, property);
197         }
198         String value = device.getProperty(property);
199         return (value == null) ? false : value.matches(regex);
200     }
201 
202     /**
203      * Retrieves the desired integer property, returning INT_VALUE_IF_UNSET if not found.
204      */
getPropertyInt(ITestDevice device, String property)205     public static int getPropertyInt(ITestDevice device, String property)
206             throws DeviceNotAvailableException {
207         String value = device.getProperty(property);
208         if (value == null) {
209             return INT_VALUE_IF_UNSET;
210         }
211         try {
212             return Integer.parseInt(value);
213         } catch (NumberFormatException e) {
214             return INT_VALUE_IF_UNSET;
215         }
216     }
217 }
218