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