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