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 */ 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 */ 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 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 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 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 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 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 */ 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 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 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 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 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 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 */ 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 */ 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 */ 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 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 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 */ 412 Map<String, String> getMetrics() { 413 return mMetrics; 414 } 415 } 416