1 /* 2 * Copyright (C) 2011 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.cts.tradefed.result; 17 18 import com.android.tradefed.log.LogUtil.CLog; 19 20 import org.kxml2.io.KXmlSerializer; 21 import org.xmlpull.v1.XmlPullParser; 22 import org.xmlpull.v1.XmlPullParserException; 23 24 import android.tests.getinfo.DeviceInfoConstants; 25 26 import java.io.IOException; 27 import java.util.Collections; 28 import java.util.HashMap; 29 import java.util.HashSet; 30 import java.util.Map; 31 import java.util.Set; 32 33 /** 34 * Data structure for the device info collected by CTS. 35 * <p/> 36 * Provides methods to serialize and deserialize from XML, as well as checks for consistency 37 * when multiple devices are used to generate the report. 38 */ 39 class DeviceInfoResult extends AbstractXmlPullParser { 40 static final String TAG = "DeviceInfo"; 41 private static final String ns = CtsXmlResultReporter.ns; 42 static final String BUILD_TAG = "BuildInfo"; 43 private static final String PHONE_TAG = "PhoneSubInfo"; 44 private static final String SCREEN_TAG = "Screen"; 45 46 private static final String FEATURE_INFO_TAG = "FeatureInfo"; 47 private static final String FEATURE_TAG = "Feature"; 48 private static final String FEATURE_ATTR_DELIM = ":"; 49 private static final String FEATURE_DELIM = ";"; 50 51 private static final String OPENGL_TEXTURE_FORMATS_INFO_TAG = 52 "OpenGLCompressedTextureFormatsInfo"; 53 private static final String OPENGL_TEXTURE_FORMAT_TAG = "TextureFormat"; 54 private static final String OPENGL_TEXTURE_FORMAT_DELIM = ";"; 55 56 private static final String OPENGL_EXTENSIONS_TAG = "OpenGlExtensions"; 57 private static final String OPENGL_EXTENSION_TAG = "OpenGlExtension"; 58 private static final String OPENGL_EXTENSION_DELIM = ";"; 59 60 private static final String SYSLIB_INFO_TAG = "SystemLibrariesInfo"; 61 private static final String SYSLIB_TAG = "Library"; 62 private static final String SYSLIB_DELIM = ";"; 63 64 private static final String PROCESS_INFO_TAG = "ProcessInfo"; 65 private static final String PROCESS_TAG = "Process"; 66 private static final String PROCESS_DELIM = ";"; 67 private static final String PROCESS_ATTR_DELIM = ":"; 68 69 private Map<String, String> mMetrics = new HashMap<String, String>(); 70 71 /** 72 * Serialize this object and all its contents to XML. 73 * 74 * @param serializer 75 * @throws IOException 76 */ serialize(KXmlSerializer serializer)77 public void serialize(KXmlSerializer serializer) throws IOException { 78 serializer.startTag(ns, TAG); 79 80 if (mMetrics.isEmpty()) { 81 // this might be expected, if device info collection was turned off 82 CLog.d("Could not find device info"); 83 serializer.endTag(ns, TAG); 84 return; 85 } 86 87 // Extract metrics that need extra handling, and then dump the remainder into BuildInfo 88 Map<String, String> metricsCopy = new HashMap<String, String>(mMetrics); 89 serializer.startTag(ns, SCREEN_TAG); 90 serializer.attribute(ns, DeviceInfoConstants.RESOLUTION, 91 getMetric(metricsCopy, DeviceInfoConstants.RESOLUTION)); 92 serializer.attribute(ns, DeviceInfoConstants.SCREEN_DENSITY, 93 getMetric(metricsCopy, DeviceInfoConstants.SCREEN_DENSITY)); 94 serializer.attribute(ns, DeviceInfoConstants.SCREEN_DENSITY_BUCKET, 95 getMetric(metricsCopy, DeviceInfoConstants.SCREEN_DENSITY_BUCKET)); 96 serializer.attribute(ns, DeviceInfoConstants.SCREEN_SIZE, 97 getMetric(metricsCopy, DeviceInfoConstants.SCREEN_SIZE)); 98 serializer.attribute(ns, DeviceInfoConstants.SMALLEST_SCREEN_WIDTH_DP, 99 getMetric(metricsCopy, DeviceInfoConstants.SMALLEST_SCREEN_WIDTH_DP)); 100 serializer.endTag(ns, SCREEN_TAG); 101 102 serializer.startTag(ns, PHONE_TAG); 103 serializer.attribute(ns, DeviceInfoConstants.PHONE_NUMBER, 104 getMetric(metricsCopy, DeviceInfoConstants.PHONE_NUMBER)); 105 serializer.endTag(ns, PHONE_TAG); 106 107 String featureData = getMetric(metricsCopy, DeviceInfoConstants.FEATURES); 108 String processData = getMetric(metricsCopy, DeviceInfoConstants.PROCESSES); 109 String sysLibData = getMetric(metricsCopy, DeviceInfoConstants.SYS_LIBRARIES); 110 String textureData = getMetric(metricsCopy, 111 DeviceInfoConstants.OPEN_GL_COMPRESSED_TEXTURE_FORMATS); 112 String openGlExtensionData = getMetric(metricsCopy, 113 DeviceInfoConstants.OPEN_GL_EXTENSIONS); 114 115 // dump the remaining metrics without translation 116 serializer.startTag(ns, BUILD_TAG); 117 for (Map.Entry<String, String> metricEntry : metricsCopy.entrySet()) { 118 serializer.attribute(ns, metricEntry.getKey(), metricEntry.getValue()); 119 } 120 serializer.endTag(ns, BUILD_TAG); 121 122 serializeFeatureInfo(serializer, featureData); 123 serializeProcessInfo(serializer, processData); 124 serializeSystemLibrariesInfo(serializer, sysLibData); 125 serializeOpenGLCompressedTextureFormatsInfo(serializer, textureData); 126 serializeOpenGLExtensions(serializer, openGlExtensionData); 127 // End 128 serializer.endTag(ns, TAG); 129 } 130 131 /** 132 * Fetch and remove given metric from hashmap. 133 * 134 * @return the metric value or empty string if it was not present in map. 135 */ getMetric(Map<String, String> metrics, String metricName )136 private String getMetric(Map<String, String> metrics, String metricName ) { 137 String value = metrics.remove(metricName); 138 if (value == null) { 139 value = ""; 140 } 141 return value; 142 } 143 serializeFeatureInfo(KXmlSerializer serializer, String featureData)144 private void serializeFeatureInfo(KXmlSerializer serializer, String featureData) 145 throws IOException { 146 serialize(serializer, FEATURE_INFO_TAG, FEATURE_TAG, FEATURE_DELIM, FEATURE_ATTR_DELIM, 147 featureData, "name", "type", "available"); 148 } 149 serializeProcessInfo(KXmlSerializer serializer, String rootProcesses)150 private void serializeProcessInfo(KXmlSerializer serializer, String rootProcesses) 151 throws IOException { 152 serialize(serializer, PROCESS_INFO_TAG, PROCESS_TAG, PROCESS_DELIM, PROCESS_ATTR_DELIM, 153 rootProcesses, "name", "uid"); 154 } 155 serializeOpenGLCompressedTextureFormatsInfo(KXmlSerializer serializer, String formats)156 private void serializeOpenGLCompressedTextureFormatsInfo(KXmlSerializer serializer, 157 String formats) throws IOException { 158 serialize(serializer, OPENGL_TEXTURE_FORMATS_INFO_TAG, OPENGL_TEXTURE_FORMAT_TAG, 159 OPENGL_TEXTURE_FORMAT_DELIM, null, formats, "name"); 160 } 161 serializeOpenGLExtensions(KXmlSerializer serializer, String extensions)162 private void serializeOpenGLExtensions(KXmlSerializer serializer, String extensions) 163 throws IOException { 164 serialize(serializer, OPENGL_EXTENSIONS_TAG, OPENGL_EXTENSION_TAG, 165 OPENGL_EXTENSION_DELIM, null, extensions, "name"); 166 } 167 serializeSystemLibrariesInfo(KXmlSerializer serializer, String libs)168 private void serializeSystemLibrariesInfo(KXmlSerializer serializer, String libs) 169 throws IOException { 170 serialize(serializer, SYSLIB_INFO_TAG, SYSLIB_TAG, SYSLIB_DELIM, null, libs, "name"); 171 } 172 173 /** 174 * Serializes a XML structure where there is an outer tag with tags inside it. 175 * 176 * <pre> 177 * Input: value1:value2;value3:value4 178 * 179 * Output: 180 * <OuterTag> 181 * <SubTag attr1="value1" attr2="value2" /> 182 * <SubTag attr1="value3" attr2="value4" /> 183 * </OuterTag> 184 * </pre> 185 * 186 * @param serializer to do it 187 * @param tag would be "OuterTag" 188 * @param subTag would be "SubTag" 189 * @param delim would be ";" 190 * @param attrDelim would be ":" in the example but can be null if only one attrName given 191 * @param data would be "value1:value2;value3:value4" 192 * @param attrNames would be an array with "attr1", "attr2" 193 * @throws IOException if there is a problem 194 */ serialize(KXmlSerializer serializer, String tag, String subTag, String delim, String attrDelim, String data, String... attrNames)195 private void serialize(KXmlSerializer serializer, String tag, String subTag, 196 String delim, String attrDelim, String data, String... attrNames) throws IOException { 197 serializer.startTag(ns, tag); 198 199 if (data == null) { 200 data = ""; 201 } 202 203 String[] values = data.split(delim); 204 for (String value : values) { 205 if (!value.isEmpty()) { 206 String[] attrValues = attrDelim != null ? value.split(attrDelim) : new String[] {value}; 207 if (attrValues.length == attrNames.length) { 208 serializer.startTag(ns, subTag); 209 for (int i = 0; i < attrNames.length; i++) { 210 serializer.attribute(ns, attrNames[i], attrValues[i]); 211 } 212 serializer.endTag(ns, subTag); 213 } 214 } 215 } 216 217 serializer.endTag(ns, tag); 218 } 219 220 /** 221 * Populates this class with package result data parsed from XML. 222 * 223 * @param parser the {@link XmlPullParser}. Expected to be pointing at start 224 * of a {@link #TAG} 225 */ 226 @Override parse(XmlPullParser parser)227 void parse(XmlPullParser parser) throws XmlPullParserException, IOException { 228 if (!parser.getName().equals(TAG)) { 229 throw new XmlPullParserException(String.format( 230 "invalid XML: Expected %s tag but received %s", TAG, parser.getName())); 231 } 232 int eventType = parser.getEventType(); 233 while (eventType != XmlPullParser.END_DOCUMENT) { 234 if (eventType == XmlPullParser.START_TAG) { 235 if (parser.getName().equals(SCREEN_TAG) || 236 parser.getName().equals(PHONE_TAG) || 237 parser.getName().equals(BUILD_TAG)) { 238 addMetricsFromAttributes(parser); 239 } else if (parser.getName().equals(FEATURE_INFO_TAG)) { 240 mMetrics.put(DeviceInfoConstants.FEATURES, parseFeatures(parser)); 241 } else if (parser.getName().equals(PROCESS_INFO_TAG)) { 242 mMetrics.put(DeviceInfoConstants.PROCESSES, parseProcess(parser)); 243 } else if (parser.getName().equals(SYSLIB_INFO_TAG)) { 244 mMetrics.put(DeviceInfoConstants.SYS_LIBRARIES, parseSystemLibraries(parser)); 245 } else if (parser.getName().equals(OPENGL_TEXTURE_FORMATS_INFO_TAG)) { 246 mMetrics.put(DeviceInfoConstants.OPEN_GL_COMPRESSED_TEXTURE_FORMATS, 247 parseOpenGLCompressedTextureFormats(parser)); 248 } 249 } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG)) { 250 return; 251 } 252 eventType = parser.next(); 253 } 254 } 255 parseFeatures(XmlPullParser parser)256 private String parseFeatures(XmlPullParser parser) throws XmlPullParserException, IOException { 257 return parseTag(parser, FEATURE_INFO_TAG, FEATURE_TAG, FEATURE_DELIM, FEATURE_ATTR_DELIM, 258 "name", "type", "available"); 259 } 260 parseProcess(XmlPullParser parser)261 private String parseProcess(XmlPullParser parser) throws XmlPullParserException, IOException { 262 return parseTag(parser, PROCESS_INFO_TAG, PROCESS_TAG, PROCESS_DELIM, 263 PROCESS_ATTR_DELIM, "name", "uid"); 264 } 265 parseOpenGLCompressedTextureFormats(XmlPullParser parser)266 private String parseOpenGLCompressedTextureFormats(XmlPullParser parser) 267 throws XmlPullParserException, IOException { 268 return parseTag(parser, OPENGL_TEXTURE_FORMATS_INFO_TAG, OPENGL_TEXTURE_FORMAT_TAG, 269 OPENGL_TEXTURE_FORMAT_DELIM, null, "name"); 270 } 271 parseSystemLibraries(XmlPullParser parser)272 private String parseSystemLibraries(XmlPullParser parser) 273 throws XmlPullParserException, IOException { 274 return parseTag(parser, SYSLIB_INFO_TAG, SYSLIB_TAG, SYSLIB_DELIM, null, "name"); 275 } 276 277 /** 278 * Converts XML into a flattened string. 279 * 280 * <pre> 281 * Input: 282 * <OuterTag> 283 * <SubTag attr1="value1" attr2="value2" /> 284 * <SubTag attr1="value3" attr2="value4" /> 285 * </OuterTag> 286 * 287 * Output: value1:value2;value3:value4 288 * </pre> 289 * 290 * @param parser that parses the xml 291 * @param tag like "OuterTag" 292 * @param subTag like "SubTag" 293 * @param delim like ";" 294 * @param attrDelim like ":" or null if tehre is only one attribute 295 * @param attrNames like "attr1", "attr2" 296 * @return flattened string like "value1:value2;value3:value4" 297 * @throws XmlPullParserException 298 * @throws IOException 299 */ parseTag(XmlPullParser parser, String tag, String subTag, String delim, String attrDelim, String... attrNames)300 private String parseTag(XmlPullParser parser, String tag, String subTag, String delim, 301 String attrDelim, String... attrNames) throws XmlPullParserException, IOException { 302 if (!parser.getName().equals(tag)) { 303 throw new XmlPullParserException(String.format( 304 "invalid XML: Expected %s tag but received %s", tag, 305 parser.getName())); 306 } 307 StringBuilder flattened = new StringBuilder(); 308 309 for (int eventType = parser.getEventType(); 310 eventType != XmlPullParser.END_DOCUMENT; 311 eventType = parser.next()) { 312 313 if (eventType == XmlPullParser.START_TAG && parser.getName().equals(subTag)) { 314 for (int i = 0; i < attrNames.length; i++) { 315 flattened.append(getAttribute(parser, attrNames[i])); 316 if (i + 1 < attrNames.length) { 317 flattened.append(attrDelim); 318 } 319 } 320 flattened.append(delim); 321 } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(tag)) { 322 break; 323 } 324 } 325 326 return flattened.toString(); 327 } 328 329 /** 330 * Adds all attributes from the current XML tag to metrics as name-value pairs 331 */ addMetricsFromAttributes(XmlPullParser parser)332 private void addMetricsFromAttributes(XmlPullParser parser) { 333 int attrCount = parser.getAttributeCount(); 334 for (int i = 0; i < attrCount; i++) { 335 mMetrics.put(parser.getAttributeName(i), parser.getAttributeValue(i)); 336 } 337 } 338 339 /** 340 * Populate the device info metrics with values collected from device. 341 * <p/> 342 * Check that the provided device info metrics are consistent with the currently stored metrics. 343 * If any inconsistencies occur, logs errors and stores error messages in the metrics map 344 */ populateMetrics(Map<String, String> metrics)345 public void populateMetrics(Map<String, String> metrics) { 346 if (mMetrics.isEmpty()) { 347 // no special processing needed, no existing metrics 348 mMetrics.putAll(metrics); 349 return; 350 } 351 Map<String, String> metricsCopy = new HashMap<String, String>( 352 metrics); 353 // add values for metrics that might be different across runs 354 combineMetrics(metricsCopy, DeviceInfoConstants.PHONE_NUMBER, DeviceInfoConstants.IMSI, 355 DeviceInfoConstants.IMSI, DeviceInfoConstants.SERIAL_NUMBER); 356 357 // ensure all the metrics we expect to be identical actually are 358 checkMetrics(metricsCopy, DeviceInfoConstants.BUILD_FINGERPRINT, 359 DeviceInfoConstants.BUILD_MODEL, DeviceInfoConstants.BUILD_BRAND, 360 DeviceInfoConstants.BUILD_MANUFACTURER, DeviceInfoConstants.BUILD_BOARD, 361 DeviceInfoConstants.BUILD_DEVICE, DeviceInfoConstants.PRODUCT_NAME, 362 DeviceInfoConstants.BUILD_ABI, DeviceInfoConstants.BUILD_ABI2, 363 DeviceInfoConstants.BUILD_ABIS, DeviceInfoConstants.BUILD_ABIS_32, 364 DeviceInfoConstants.BUILD_ABIS_64, DeviceInfoConstants.SCREEN_SIZE); 365 } 366 combineMetrics(Map<String, String> metrics, String... keysToCombine)367 private void combineMetrics(Map<String, String> metrics, String... keysToCombine) { 368 for (String combineKey : keysToCombine) { 369 String currentKeyValue = mMetrics.get(combineKey); 370 String valueToAdd = metrics.remove(combineKey); 371 if (valueToAdd != null) { 372 if (currentKeyValue == null) { 373 // strange - no existing value. Can occur during unit testing 374 mMetrics.put(combineKey, valueToAdd); 375 } else if (!currentKeyValue.equals(valueToAdd)) { 376 // new value! store a comma separated list 377 valueToAdd = String.format("%s,%s", currentKeyValue, valueToAdd); 378 mMetrics.put(combineKey, valueToAdd); 379 } else { 380 // ignore, current value is same as existing 381 } 382 383 } else { 384 CLog.d("Missing metric %s", combineKey); 385 } 386 } 387 } 388 checkMetrics(Map<String, String> metrics, String... keysToCheck)389 private void checkMetrics(Map<String, String> metrics, String... keysToCheck) { 390 Set<String> keyCheckSet = new HashSet<String>(); 391 Collections.addAll(keyCheckSet, keysToCheck); 392 for (Map.Entry<String, String> metricEntry : metrics.entrySet()) { 393 String currentValue = mMetrics.get(metricEntry.getKey()); 394 if (keyCheckSet.contains(metricEntry.getKey()) && currentValue != null 395 && !metricEntry.getValue().equals(currentValue)) { 396 CLog.e("Inconsistent info collected from devices. " 397 + "Current result has %s='%s', Received '%s'. Are you sharding or " + 398 "resuming a test run across different devices and/or builds?", 399 metricEntry.getKey(), currentValue, metricEntry.getValue()); 400 mMetrics.put(metricEntry.getKey(), 401 String.format("ERROR: Inconsistent results: %s, %s", 402 metricEntry.getValue(), currentValue)); 403 } else { 404 mMetrics.put(metricEntry.getKey(), metricEntry.getValue()); 405 } 406 } 407 } 408 409 /** 410 * Return the currently stored metrics. 411 * <p/> 412 * Exposed for unit testing. 413 */ getMetrics()414 Map<String, String> getMetrics() { 415 return mMetrics; 416 } 417 } 418