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