blob: 96145e909970c8bb15ff4d2f56bdaac77ca3313b [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.tradefed.result;
import com.android.tradefed.log.LogUtil.CLog;
import org.kxml2.io.KXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.tests.getinfo.DeviceInfoConstants;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Data structure for the device info collected by CTS.
* <p/>
* Provides methods to serialize and deserialize from XML, as well as checks for consistency
* when multiple devices are used to generate the report.
*/
class DeviceInfoResult extends AbstractXmlPullParser {
static final String TAG = "DeviceInfo";
private static final String ns = CtsXmlResultReporter.ns;
static final String BUILD_TAG = "BuildInfo";
private static final String PHONE_TAG = "PhoneSubInfo";
private static final String SCREEN_TAG = "Screen";
private static final String FEATURE_INFO_TAG = "FeatureInfo";
private static final String FEATURE_TAG = "Feature";
private static final String FEATURE_ATTR_DELIM = ":";
private static final String FEATURE_DELIM = ";";
private static final String OPENGL_TEXTURE_FORMATS_INFO_TAG =
"OpenGLCompressedTextureFormatsInfo";
private static final String OPENGL_TEXTURE_FORMAT_TAG = "TextureFormat";
private static final String OPENGL_TEXTURE_FORMAT_DELIM = ";";
private static final String OPENGL_EXTENSIONS_TAG = "OpenGlExtensions";
private static final String OPENGL_EXTENSION_TAG = "OpenGlExtension";
private static final String OPENGL_EXTENSION_DELIM = ";";
private static final String SYSLIB_INFO_TAG = "SystemLibrariesInfo";
private static final String SYSLIB_TAG = "Library";
private static final String SYSLIB_DELIM = ";";
private static final String PROCESS_INFO_TAG = "ProcessInfo";
private static final String PROCESS_TAG = "Process";
private static final String PROCESS_DELIM = ";";
private static final String PROCESS_ATTR_DELIM = ":";
private Map<String, String> mMetrics = new HashMap<String, String>();
/**
* Serialize this object and all its contents to XML.
*
* @param serializer
* @throws IOException
*/
public void serialize(KXmlSerializer serializer) throws IOException {
serializer.startTag(ns, TAG);
if (mMetrics.isEmpty()) {
// this might be expected, if device info collection was turned off
CLog.d("Could not find device info");
serializer.endTag(ns, TAG);
return;
}
// Extract metrics that need extra handling, and then dump the remainder into BuildInfo
Map<String, String> metricsCopy = new HashMap<String, String>(mMetrics);
serializer.startTag(ns, SCREEN_TAG);
serializer.attribute(ns, DeviceInfoConstants.RESOLUTION,
getMetric(metricsCopy, DeviceInfoConstants.RESOLUTION));
serializer.attribute(ns, DeviceInfoConstants.SCREEN_DENSITY,
getMetric(metricsCopy, DeviceInfoConstants.SCREEN_DENSITY));
serializer.attribute(ns, DeviceInfoConstants.SCREEN_DENSITY_BUCKET,
getMetric(metricsCopy, DeviceInfoConstants.SCREEN_DENSITY_BUCKET));
serializer.attribute(ns, DeviceInfoConstants.SCREEN_SIZE,
getMetric(metricsCopy, DeviceInfoConstants.SCREEN_SIZE));
serializer.attribute(ns, DeviceInfoConstants.SMALLEST_SCREEN_WIDTH_DP,
getMetric(metricsCopy, DeviceInfoConstants.SMALLEST_SCREEN_WIDTH_DP));
serializer.endTag(ns, SCREEN_TAG);
serializer.startTag(ns, PHONE_TAG);
serializer.attribute(ns, DeviceInfoConstants.PHONE_NUMBER,
getMetric(metricsCopy, DeviceInfoConstants.PHONE_NUMBER));
serializer.endTag(ns, PHONE_TAG);
String featureData = getMetric(metricsCopy, DeviceInfoConstants.FEATURES);
String processData = getMetric(metricsCopy, DeviceInfoConstants.PROCESSES);
String sysLibData = getMetric(metricsCopy, DeviceInfoConstants.SYS_LIBRARIES);
String textureData = getMetric(metricsCopy,
DeviceInfoConstants.OPEN_GL_COMPRESSED_TEXTURE_FORMATS);
String openGlExtensionData = getMetric(metricsCopy,
DeviceInfoConstants.OPEN_GL_EXTENSIONS);
// dump the remaining metrics without translation
serializer.startTag(ns, BUILD_TAG);
for (Map.Entry<String, String> metricEntry : metricsCopy.entrySet()) {
serializer.attribute(ns, metricEntry.getKey(), metricEntry.getValue());
}
serializer.endTag(ns, BUILD_TAG);
serializeFeatureInfo(serializer, featureData);
serializeProcessInfo(serializer, processData);
serializeSystemLibrariesInfo(serializer, sysLibData);
serializeOpenGLCompressedTextureFormatsInfo(serializer, textureData);
serializeOpenGLExtensions(serializer, openGlExtensionData);
// End
serializer.endTag(ns, TAG);
}
/**
* Fetch and remove given metric from hashmap.
*
* @return the metric value or empty string if it was not present in map.
*/
private String getMetric(Map<String, String> metrics, String metricName ) {
String value = metrics.remove(metricName);
if (value == null) {
value = "";
}
return value;
}
private void serializeFeatureInfo(KXmlSerializer serializer, String featureData)
throws IOException {
serialize(serializer, FEATURE_INFO_TAG, FEATURE_TAG, FEATURE_DELIM, FEATURE_ATTR_DELIM,
featureData, "name", "type", "available");
}
private void serializeProcessInfo(KXmlSerializer serializer, String rootProcesses)
throws IOException {
serialize(serializer, PROCESS_INFO_TAG, PROCESS_TAG, PROCESS_DELIM, PROCESS_ATTR_DELIM,
rootProcesses, "name", "uid");
}
private void serializeOpenGLCompressedTextureFormatsInfo(KXmlSerializer serializer,
String formats) throws IOException {
serialize(serializer, OPENGL_TEXTURE_FORMATS_INFO_TAG, OPENGL_TEXTURE_FORMAT_TAG,
OPENGL_TEXTURE_FORMAT_DELIM, null, formats, "name");
}
private void serializeOpenGLExtensions(KXmlSerializer serializer, String extensions)
throws IOException {
serialize(serializer, OPENGL_EXTENSIONS_TAG, OPENGL_EXTENSION_TAG,
OPENGL_EXTENSION_DELIM, null, extensions, "name");
}
private void serializeSystemLibrariesInfo(KXmlSerializer serializer, String libs)
throws IOException {
serialize(serializer, SYSLIB_INFO_TAG, SYSLIB_TAG, SYSLIB_DELIM, null, libs, "name");
}
/**
* Serializes a XML structure where there is an outer tag with tags inside it.
*
* <pre>
* Input: value1:value2;value3:value4
*
* Output:
* <OuterTag>
* <SubTag attr1="value1" attr2="value2" />
* <SubTag attr1="value3" attr2="value4" />
* </OuterTag>
* </pre>
*
* @param serializer to do it
* @param tag would be "OuterTag"
* @param subTag would be "SubTag"
* @param delim would be ";"
* @param attrDelim would be ":" in the example but can be null if only one attrName given
* @param data would be "value1:value2;value3:value4"
* @param attrNames would be an array with "attr1", "attr2"
* @throws IOException if there is a problem
*/
private void serialize(KXmlSerializer serializer, String tag, String subTag,
String delim, String attrDelim, String data, String... attrNames) throws IOException {
serializer.startTag(ns, tag);
if (data == null) {
data = "";
}
String[] values = data.split(delim);
for (String value : values) {
if (!value.isEmpty()) {
String[] attrValues = attrDelim != null ? value.split(attrDelim) : new String[] {value};
if (attrValues.length == attrNames.length) {
serializer.startTag(ns, subTag);
for (int i = 0; i < attrNames.length; i++) {
serializer.attribute(ns, attrNames[i], attrValues[i]);
}
serializer.endTag(ns, subTag);
}
}
}
serializer.endTag(ns, tag);
}
/**
* Populates this class with package result data parsed from XML.
*
* @param parser the {@link XmlPullParser}. Expected to be pointing at start
* of a {@link #TAG}
*/
@Override
void parse(XmlPullParser parser) throws XmlPullParserException, IOException {
if (!parser.getName().equals(TAG)) {
throw new XmlPullParserException(String.format(
"invalid XML: Expected %s tag but received %s", TAG, parser.getName()));
}
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals(SCREEN_TAG) ||
parser.getName().equals(PHONE_TAG) ||
parser.getName().equals(BUILD_TAG)) {
addMetricsFromAttributes(parser);
} else if (parser.getName().equals(FEATURE_INFO_TAG)) {
mMetrics.put(DeviceInfoConstants.FEATURES, parseFeatures(parser));
} else if (parser.getName().equals(PROCESS_INFO_TAG)) {
mMetrics.put(DeviceInfoConstants.PROCESSES, parseProcess(parser));
} else if (parser.getName().equals(SYSLIB_INFO_TAG)) {
mMetrics.put(DeviceInfoConstants.SYS_LIBRARIES, parseSystemLibraries(parser));
} else if (parser.getName().equals(OPENGL_TEXTURE_FORMATS_INFO_TAG)) {
mMetrics.put(DeviceInfoConstants.OPEN_GL_COMPRESSED_TEXTURE_FORMATS,
parseOpenGLCompressedTextureFormats(parser));
}
} else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG)) {
return;
}
eventType = parser.next();
}
}
private String parseFeatures(XmlPullParser parser) throws XmlPullParserException, IOException {
return parseTag(parser, FEATURE_INFO_TAG, FEATURE_TAG, FEATURE_DELIM, FEATURE_ATTR_DELIM,
"name", "type", "available");
}
private String parseProcess(XmlPullParser parser) throws XmlPullParserException, IOException {
return parseTag(parser, PROCESS_INFO_TAG, PROCESS_TAG, PROCESS_DELIM,
PROCESS_ATTR_DELIM, "name", "uid");
}
private String parseOpenGLCompressedTextureFormats(XmlPullParser parser)
throws XmlPullParserException, IOException {
return parseTag(parser, OPENGL_TEXTURE_FORMATS_INFO_TAG, OPENGL_TEXTURE_FORMAT_TAG,
OPENGL_TEXTURE_FORMAT_DELIM, null, "name");
}
private String parseSystemLibraries(XmlPullParser parser)
throws XmlPullParserException, IOException {
return parseTag(parser, SYSLIB_INFO_TAG, SYSLIB_TAG, SYSLIB_DELIM, null, "name");
}
/**
* Converts XML into a flattened string.
*
* <pre>
* Input:
* <OuterTag>
* <SubTag attr1="value1" attr2="value2" />
* <SubTag attr1="value3" attr2="value4" />
* </OuterTag>
*
* Output: value1:value2;value3:value4
* </pre>
*
* @param parser that parses the xml
* @param tag like "OuterTag"
* @param subTag like "SubTag"
* @param delim like ";"
* @param attrDelim like ":" or null if tehre is only one attribute
* @param attrNames like "attr1", "attr2"
* @return flattened string like "value1:value2;value3:value4"
* @throws XmlPullParserException
* @throws IOException
*/
private String parseTag(XmlPullParser parser, String tag, String subTag, String delim,
String attrDelim, String... attrNames) throws XmlPullParserException, IOException {
if (!parser.getName().equals(tag)) {
throw new XmlPullParserException(String.format(
"invalid XML: Expected %s tag but received %s", tag,
parser.getName()));
}
StringBuilder flattened = new StringBuilder();
for (int eventType = parser.getEventType();
eventType != XmlPullParser.END_DOCUMENT;
eventType = parser.next()) {
if (eventType == XmlPullParser.START_TAG && parser.getName().equals(subTag)) {
for (int i = 0; i < attrNames.length; i++) {
flattened.append(getAttribute(parser, attrNames[i]));
if (i + 1 < attrNames.length) {
flattened.append(attrDelim);
}
}
flattened.append(delim);
} else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(tag)) {
break;
}
}
return flattened.toString();
}
/**
* Adds all attributes from the current XML tag to metrics as name-value pairs
*/
private void addMetricsFromAttributes(XmlPullParser parser) {
int attrCount = parser.getAttributeCount();
for (int i = 0; i < attrCount; i++) {
mMetrics.put(parser.getAttributeName(i), parser.getAttributeValue(i));
}
}
/**
* Populate the device info metrics with values collected from device.
* <p/>
* Check that the provided device info metrics are consistent with the currently stored metrics.
* If any inconsistencies occur, logs errors and stores error messages in the metrics map
*/
public void populateMetrics(Map<String, String> metrics) {
if (mMetrics.isEmpty()) {
// no special processing needed, no existing metrics
mMetrics.putAll(metrics);
return;
}
Map<String, String> metricsCopy = new HashMap<String, String>(
metrics);
// add values for metrics that might be different across runs
combineMetrics(metricsCopy, DeviceInfoConstants.PHONE_NUMBER, DeviceInfoConstants.IMSI,
DeviceInfoConstants.IMSI, DeviceInfoConstants.SERIAL_NUMBER);
// ensure all the metrics we expect to be identical actually are
checkMetrics(metricsCopy, DeviceInfoConstants.BUILD_FINGERPRINT,
DeviceInfoConstants.BUILD_MODEL, DeviceInfoConstants.BUILD_BRAND,
DeviceInfoConstants.BUILD_MANUFACTURER, DeviceInfoConstants.BUILD_BOARD,
DeviceInfoConstants.BUILD_DEVICE, DeviceInfoConstants.PRODUCT_NAME,
DeviceInfoConstants.BUILD_ABI, DeviceInfoConstants.BUILD_ABI2,
DeviceInfoConstants.BUILD_ABIS, DeviceInfoConstants.BUILD_ABIS_32,
DeviceInfoConstants.BUILD_ABIS_64, DeviceInfoConstants.SCREEN_SIZE);
}
private void combineMetrics(Map<String, String> metrics, String... keysToCombine) {
for (String combineKey : keysToCombine) {
String currentKeyValue = mMetrics.get(combineKey);
String valueToAdd = metrics.remove(combineKey);
if (valueToAdd != null) {
if (currentKeyValue == null) {
// strange - no existing value. Can occur during unit testing
mMetrics.put(combineKey, valueToAdd);
} else if (!currentKeyValue.equals(valueToAdd)) {
// new value! store a comma separated list
valueToAdd = String.format("%s,%s", currentKeyValue, valueToAdd);
mMetrics.put(combineKey, valueToAdd);
} else {
// ignore, current value is same as existing
}
} else {
CLog.d("Missing metric %s", combineKey);
}
}
}
private void checkMetrics(Map<String, String> metrics, String... keysToCheck) {
Set<String> keyCheckSet = new HashSet<String>();
Collections.addAll(keyCheckSet, keysToCheck);
for (Map.Entry<String, String> metricEntry : metrics.entrySet()) {
String currentValue = mMetrics.get(metricEntry.getKey());
if (keyCheckSet.contains(metricEntry.getKey()) && currentValue != null
&& !metricEntry.getValue().equals(currentValue)) {
CLog.e("Inconsistent info collected from devices. "
+ "Current result has %s='%s', Received '%s'. Are you sharding or " +
"resuming a test run across different devices and/or builds?",
metricEntry.getKey(), currentValue, metricEntry.getValue());
mMetrics.put(metricEntry.getKey(),
String.format("ERROR: Inconsistent results: %s, %s",
metricEntry.getValue(), currentValue));
} else {
mMetrics.put(metricEntry.getKey(), metricEntry.getValue());
}
}
}
/**
* Return the currently stored metrics.
* <p/>
* Exposed for unit testing.
*/
Map<String, String> getMetrics() {
return mMetrics;
}
}