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     private static final String BUILD_TAGS_PROPERTY = "ro.build.tags";
38     private static final String BUILD_TYPE_PROPERTY = "ro.build.type";
39     private static final String MANUFACTURER_PROPERTY = "ro.product.manufacturer";
40     private static final String TAG_DEV_KEYS = "dev-keys";
41     private static final String VNDK_VERSION = "ro.vndk.version";
42 
43     /** Value to be returned by getPropertyInt() if property is not found */
44     public static final int INT_VALUE_IF_UNSET = -1;
45 
46     public static final String GOOGLE_SETTINGS_QUERY =
47             "content query --uri content://com.google.settings/partner";
48 
49     /** Returns whether the device build is a user build */
isUserBuild(ITestDevice device)50     public static boolean isUserBuild(ITestDevice device) throws DeviceNotAvailableException {
51         return propertyEquals(device, BUILD_TYPE_PROPERTY, "user");
52     }
53 
54     /** Returns whether this build is built with dev-keys */
isDevKeysBuild(ITestDevice device)55     public static boolean isDevKeysBuild(ITestDevice device) throws DeviceNotAvailableException {
56         String buildTags = device.getProperty(BUILD_TAGS_PROPERTY);
57         for (String tag : buildTags.split(",")) {
58             if (TAG_DEV_KEYS.equals(tag.trim())) {
59                 return true;
60             }
61         }
62         return false;
63     }
64 
65     /**
66      * Return the first API level for this product. If the read-only property is unset,
67      * this means the first API level is the current API level, and the current API level
68      * is returned.
69      */
getFirstApiLevel(ITestDevice device)70     public static int getFirstApiLevel(ITestDevice device) throws DeviceNotAvailableException {
71         String propString = device.getProperty(FIRST_API_LEVEL);
72         return (propString == null) ? device.getApiLevel() : Integer.parseInt(propString);
73     }
74 
75     /**
76      * Return whether the SDK version of the vendor partiton is newer than the given API level.
77      * If the property is set to non-integer value, this means the vendor partition is using
78      * current API level and true is returned.
79      */
isVendorApiLevelNewerThan(ITestDevice device, int apiLevel)80     public static boolean isVendorApiLevelNewerThan(ITestDevice device, int apiLevel)
81             throws DeviceNotAvailableException {
82         int vendorApiLevel = getPropertyInt(device, VNDK_VERSION);
83         if (vendorApiLevel == INT_VALUE_IF_UNSET) {
84             return true;
85         }
86         return vendorApiLevel > apiLevel;
87     }
88 
89     /**
90      * Return the manufacturer of this product. If unset, return null.
91      */
getManufacturer(ITestDevice device)92     public static String getManufacturer(ITestDevice device) throws DeviceNotAvailableException {
93         return device.getProperty(MANUFACTURER_PROPERTY);
94     }
95 
96     /** Returns a mapping from client ID names to client ID values */
getClientIds(ITestDevice device)97     public static Map<String, String> getClientIds(ITestDevice device)
98             throws DeviceNotAvailableException {
99         Map<String,String> clientIds = new HashMap<>();
100         String queryOutput = device.executeShellCommand(GOOGLE_SETTINGS_QUERY);
101         for (String line : queryOutput.split("[\\r?\\n]+")) {
102             // Expected line format: "Row: 1 _id=123, name=<property_name>, value=<property_value>"
103             Pattern pattern = Pattern.compile("name=([a-z_]*), value=(.*)$");
104             Matcher matcher = pattern.matcher(line);
105             if (matcher.find()) {
106                 String name = matcher.group(1);
107                 String value = matcher.group(2);
108                 if (name.contains("client_id")) {
109                     clientIds.put(name, value); // only add name-value pair for client ids
110                 }
111             }
112         }
113         return clientIds;
114     }
115 
116     /** Returns whether the property exists on this device */
propertyExists(ITestDevice device, String property)117     public static boolean propertyExists(ITestDevice device, String property)
118             throws DeviceNotAvailableException {
119         return device.getProperty(property) != null;
120     }
121 
122     /** Returns whether the property value is equal to a given string */
propertyEquals(ITestDevice device, String property, String value)123     public static boolean propertyEquals(ITestDevice device, String property, String value)
124             throws DeviceNotAvailableException {
125         if (value == null) {
126             return !propertyExists(device, property); // null value implies property does not exist
127         }
128         return value.equals(device.getProperty(property));
129     }
130 
131     /**
132      * Returns whether the property value matches a given regular expression. The method uses
133      * String.matches(), requiring a complete match (i.e. expression matches entire value string)
134      */
propertyMatches(ITestDevice device, String property, String regex)135     public static boolean propertyMatches(ITestDevice device, String property, String regex)
136             throws DeviceNotAvailableException {
137         if (regex == null || regex.isEmpty()) {
138             // null or empty pattern implies property does not exist
139             return !propertyExists(device, property);
140         }
141         String value = device.getProperty(property);
142         return (value == null) ? false : value.matches(regex);
143     }
144 
145     /**
146      * Retrieves the desired integer property, returning INT_VALUE_IF_UNSET if not found.
147      */
getPropertyInt(ITestDevice device, String property)148     public static int getPropertyInt(ITestDevice device, String property)
149             throws DeviceNotAvailableException {
150         String value = device.getProperty(property);
151         if (value == null) {
152             return INT_VALUE_IF_UNSET;
153         }
154         try {
155             return Integer.parseInt(value);
156         } catch (NumberFormatException e) {
157             return INT_VALUE_IF_UNSET;
158         }
159     }
160 }
161