blob: d5f4530cffbd947fb993ab0c3720d509d727f07c [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 android.tests.getinfo.DeviceInfoConstants;
import com.android.tradefed.log.LogUtil.CLog;
import org.kxml2.io.KXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
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;
private 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 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 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()) {
// 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.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);
// 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);
} else {
// this might be expected, if device info collection was turned off
CLog.d("Could not find device info");
}
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;
}
/**
* Prints XML indicating what features are supported by the device. It parses a string from the
* featureData argument that is in the form of "feature1:true;feature2:false;featuer3;true;"
* with a trailing semi-colon.
*
* <pre>
* <FeatureInfo>
* <Feature name="android.name.of.feature" available="true" />
* ...
* </FeatureInfo>
* </pre>
*
* @param serializer used to create XML
* @param featureData raw unparsed feature data
*/
private void serializeFeatureInfo(KXmlSerializer serializer, String featureData)
throws IOException {
serializer.startTag(ns, FEATURE_INFO_TAG);
if (featureData == null) {
featureData = "";
}
String[] featurePairs = featureData.split(FEATURE_DELIM);
for (String featurePair : featurePairs) {
String[] nameTypeAvailability = featurePair.split(FEATURE_ATTR_DELIM);
if (nameTypeAvailability.length >= 3) {
serializer.startTag(ns, FEATURE_TAG);
serializer.attribute(ns, "name", nameTypeAvailability[0]);
serializer.attribute(ns, "type", nameTypeAvailability[1]);
serializer.attribute(ns, "available", nameTypeAvailability[2]);
serializer.endTag(ns, FEATURE_TAG);
}
}
serializer.endTag(ns, FEATURE_INFO_TAG);
}
/**
* Prints XML data indicating what particular processes of interest were running on the device.
* It parses a string from the rootProcesses argument that is in the form of
* "processName1;processName2;..." with a trailing semi-colon.
*
* <pre>
* <ProcessInfo>
* <Process name="long_cat_viewer" uid="0" />
* ...
* </ProcessInfo>
* </pre>
*/
private void serializeProcessInfo(KXmlSerializer serializer, String rootProcesses)
throws IOException {
serializer.startTag(ns, PROCESS_INFO_TAG);
if (rootProcesses == null) {
rootProcesses = "";
}
String[] processNames = rootProcesses.split(PROCESS_DELIM);
for (String processName : processNames) {
processName = processName.trim();
if (processName.length() > 0) {
serializer.startTag(ns, PROCESS_TAG);
serializer.attribute(ns, "name", processName);
serializer.attribute(ns, "uid", "0");
serializer.endTag(ns, PROCESS_TAG);
}
}
serializer.endTag(ns, PROCESS_INFO_TAG);
}
/**
* Prints XML data in two level hierarchy.
* It parses a string from the root argument that is in the form of
* "element1-delimiter-element2-delimiter..." with a trailing delimiter
*
* <pre>
* <infoTag>
* <elementTag name="element1" />
* ...
* </infoTag>
* </pre>
*/
private void serializeSimpleInfo(KXmlSerializer serializer, String root,
String infoTag, String elementTag, String delimiter)
throws IOException {
serializer.startTag(ns, infoTag);
if (root == null) {
root = "";
}
String[] elemNames = root.split(delimiter);
for (String elemName : elemNames) {
elemName = elemName.trim();
if (elemName.length() > 0) {
serializer.startTag(ns, elementTag);
serializer.attribute(ns, "name", elemName);
serializer.endTag(ns, elementTag);
}
}
serializer.endTag(ns, infoTag);
}
/**
* Prints XML data listing available OpenGL Compressed Texture Formats.
*
* <pre>
* <OpenGLCompressedTextureFormatsInfo>
* <TextureFormat name="abc" />
* ...
* </OpenGLCompressedTextureFormatsInfo>
* </pre>
*/
private void serializeOpenGLCompressedTextureFormatsInfo(KXmlSerializer serializer,
String root) throws IOException {
serializeSimpleInfo(serializer, root, OPENGL_TEXTURE_FORMATS_INFO_TAG,
OPENGL_TEXTURE_FORMAT_TAG,
OPENGL_TEXTURE_FORMAT_DELIM);
}
/**
* Prints XML data listing available system libraries.
* It parses a string from the rootLibraries argument that is in the form of
* "libName1;libName2;..." with a trailing semi-colon.
*
* <pre>
* <SystemLibrariesInfo>
* <Library name="a.b.c" />
* ...
* </SystemLibrariesInfo>
* </pre>
*/
private void serializeSystemLibrariesInfo(KXmlSerializer serializer, String rootLibraries)
throws IOException {
serializeSimpleInfo(serializer, rootLibraries, SYSLIB_INFO_TAG, SYSLIB_TAG, SYSLIB_DELIM);
}
/**
* 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)) {
// store features into metrics map, in the same format as when collected from
// device
mMetrics.put(DeviceInfoConstants.FEATURES, parseFeatures(parser));
} else if (parser.getName().equals(PROCESS_INFO_TAG)) {
// store processes into metrics map, in the same format as when collected from
// device
mMetrics.put(DeviceInfoConstants.PROCESSES, parseProcess(parser));
} else if (parser.getName().equals(SYSLIB_INFO_TAG)) {
// store system libs into metrics map, in the same format as when collected from
// device
mMetrics.put(DeviceInfoConstants.SYS_LIBRARIES, parseSystemLibraries(parser));
} else if (parser.getName().equals(OPENGL_TEXTURE_FORMATS_INFO_TAG)) {
// store OpenGL texture formats into metrics map, in the same format as when
// collected from device
mMetrics.put(DeviceInfoConstants.OPEN_GL_COMPRESSED_TEXTURE_FORMATS,
parseOpenGLCompressedTextureFormats(parser));
}
} else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG)) {
return;
}
eventType = parser.next();
}
}
/**
* Parse process XML, and return its contents as a delimited String
*/
private String parseProcess(XmlPullParser parser) throws XmlPullParserException, IOException {
if (!parser.getName().equals(PROCESS_INFO_TAG)) {
throw new XmlPullParserException(String.format(
"invalid XML: Expected %s tag but received %s", PROCESS_INFO_TAG,
parser.getName()));
}
StringBuilder processString = new StringBuilder();
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG && parser.getName().equals(PROCESS_TAG)) {
processString.append(getAttribute(parser, "name"));
processString.append(PROCESS_DELIM);
} else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(
PROCESS_INFO_TAG)) {
return processString.toString();
}
eventType = parser.next();
}
return processString.toString();
}
/**
* Parse feature XML, and return its contents as a delimited String
*/
private String parseFeatures(XmlPullParser parser) throws XmlPullParserException, IOException {
if (!parser.getName().equals(FEATURE_INFO_TAG)) {
throw new XmlPullParserException(String.format(
"invalid XML: Expected %s tag but received %s", FEATURE_INFO_TAG,
parser.getName()));
}
StringBuilder featureString = new StringBuilder();
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG && parser.getName().equals(FEATURE_TAG)) {
featureString.append(getAttribute(parser, "name"));
featureString.append(FEATURE_ATTR_DELIM);
featureString.append(getAttribute(parser, "type"));
featureString.append(FEATURE_ATTR_DELIM);
featureString.append(getAttribute(parser, "available"));
featureString.append(FEATURE_DELIM);
} else if (eventType == XmlPullParser.END_TAG
&& parser.getName().equals(FEATURE_INFO_TAG)) {
return featureString.toString();
}
eventType = parser.next();
}
return featureString.toString();
}
/**
* Parse two-level hierarchy XML, and return its contents as a delimited String
*/
private String parseSimpleInfo(XmlPullParser parser, String infoTag, String elementTag,
String delimiter) throws XmlPullParserException, IOException {
if (!parser.getName().equals(infoTag)) {
throw new XmlPullParserException(String.format(
"invalid XML: Expected %s tag but received %s", infoTag,
parser.getName()));
}
StringBuilder result = new StringBuilder();
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG && parser.getName().equals(elementTag)) {
result.append(getAttribute(parser, "name"));
result.append(delimiter);
} else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(
infoTag)) {
return result.toString();
}
eventType = parser.next();
}
return result.toString();
}
/**
* Parse JavaLibrariesInfo XML, and return its contents as a delimited String
*/
private String parseOpenGLCompressedTextureFormats(XmlPullParser parser)
throws XmlPullParserException, IOException {
return parseSimpleInfo(parser, OPENGL_TEXTURE_FORMATS_INFO_TAG, OPENGL_TEXTURE_FORMAT_TAG,
OPENGL_TEXTURE_FORMAT_DELIM);
}
/**
* Parse JavaLibrariesInfo XML, and return its contents as a delimited String
*/
private String parseSystemLibraries(XmlPullParser parser)
throws XmlPullParserException, IOException {
return parseSimpleInfo(parser, SYSLIB_INFO_TAG, SYSLIB_TAG, SYSLIB_DELIM);
}
/**
* 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
*
* @param runResult
*/
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.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;
}
}