Home | History | Annotate | Download | only in result
      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.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      */
    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 
    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 
    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 
    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 
    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 
    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      */
    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
    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 
    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 
    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 
    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 
    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      */
    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      */
    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      */
    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 
    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 
    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      */
    414     Map<String, String> getMetrics() {
    415         return mMetrics;
    416     }
    417 }
    418