DeviceInfoResult.java revision 4162419797975a7d2bee14d014fb7c74a4151100
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 */
16package com.android.cts.tradefed.result;
17
18import com.android.tradefed.log.LogUtil.CLog;
19
20import org.kxml2.io.KXmlSerializer;
21import org.xmlpull.v1.XmlPullParser;
22import org.xmlpull.v1.XmlPullParserException;
23
24import android.tests.getinfo.DeviceInfoConstants;
25
26import java.io.IOException;
27import java.util.Collections;
28import java.util.HashMap;
29import java.util.HashSet;
30import java.util.Map;
31import 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 */
39class 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.SCREEN_SIZE);
364    }
365
366    private void combineMetrics(Map<String, String> metrics, String... keysToCombine) {
367        for (String combineKey : keysToCombine) {
368            String currentKeyValue = mMetrics.get(combineKey);
369            String valueToAdd = metrics.remove(combineKey);
370            if (valueToAdd != null) {
371                if (currentKeyValue == null) {
372                    // strange - no existing value. Can occur during unit testing
373                    mMetrics.put(combineKey, valueToAdd);
374                } else if (!currentKeyValue.equals(valueToAdd)) {
375                    // new value! store a comma separated list
376                    valueToAdd = String.format("%s,%s", currentKeyValue, valueToAdd);
377                    mMetrics.put(combineKey, valueToAdd);
378                } else {
379                    // ignore, current value is same as existing
380                }
381
382            } else {
383                CLog.d("Missing metric %s", combineKey);
384            }
385        }
386    }
387
388    private void checkMetrics(Map<String, String> metrics, String... keysToCheck) {
389        Set<String> keyCheckSet = new HashSet<String>();
390        Collections.addAll(keyCheckSet, keysToCheck);
391        for (Map.Entry<String, String> metricEntry : metrics.entrySet()) {
392            String currentValue = mMetrics.get(metricEntry.getKey());
393            if (keyCheckSet.contains(metricEntry.getKey()) && currentValue != null
394                    && !metricEntry.getValue().equals(currentValue)) {
395                CLog.e("Inconsistent info collected from devices. "
396                        + "Current result has %s='%s', Received '%s'. Are you sharding or " +
397                        "resuming a test run across different devices and/or builds?",
398                        metricEntry.getKey(), currentValue, metricEntry.getValue());
399                mMetrics.put(metricEntry.getKey(),
400                        String.format("ERROR: Inconsistent results: %s, %s",
401                                metricEntry.getValue(), currentValue));
402            } else {
403                mMetrics.put(metricEntry.getKey(), metricEntry.getValue());
404            }
405        }
406    }
407
408    /**
409     * Return the currently stored metrics.
410     * <p/>
411     * Exposed for unit testing.
412     */
413    Map<String, String> getMetrics() {
414        return mMetrics;
415    }
416}
417