• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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