• 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 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