CTS Verifier XML Reports

Bug 4878013

1. Add a version name and code to the CTS Verifier. Show the
   version name on the welcome screen.

2. Share a XML report instead of a plain text report. See
   TestResultsReport for the format.

Change-Id: I10d21b7ea4d468472fc15866d7259e456d6741a6
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index fddbcae..8c900e2 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -17,8 +17,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.android.cts.verifier"
-      android:versionCode="1"
-      android:versionName="1.0">
+      android:versionCode="2"
+      android:versionName="2.3_r4">
       
     <uses-sdk android:minSdkVersion="5"></uses-sdk>
 
diff --git a/apps/CtsVerifier/res/layout/main.xml b/apps/CtsVerifier/res/layout/main.xml
index d6cb6cd..cdfb8f4 100644
--- a/apps/CtsVerifier/res/layout/main.xml
+++ b/apps/CtsVerifier/res/layout/main.xml
@@ -26,12 +26,21 @@
         android:text="@string/continue_button_text"
         />
     <TextView
-        android:id="@+id/welcome_text"
+        android:id="@+id/version_text"
         android:gravity="center"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:layout_above="@+id/continue_button"
-        android:padding="10dip"
+        android:paddingBottom="10dip"
+        style="@style/VersionFont"
+        />
+    <TextView
+        android:id="@+id/welcome_text"
+        android:gravity="center"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_above="@+id/version_text"
+        android:paddingTop="10dip"
         android:text="@string/welcome_text"
         style="@style/WelcomeFont"
         />
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 08bd98b..f2257a2 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -16,6 +16,7 @@
 <resources>
     <string name="app_name">CTS Verifier</string>
     <string name="welcome_text">Welcome to the CTS Verifier!</string>
+    <string name="version_text">%1$s</string>
     <string name="continue_button_text">Continue</string>
 
     <string name="pass_button_text">Pass</string>
@@ -23,11 +24,7 @@
     <string name="fail_button_text">Fail</string>
 
     <!-- Strings for TestResultsReport -->
-    <string name="subject_header">[CTS Verifier %1$s]</string>
-    <string name="body_header">CTS Verifier %1$s Test Results</string>
-    <string name="pass_result">PASS</string>
-    <string name="fail_result">FAIL</string>
-    <string name="not_executed_result">NOT_EXECUTED</string>
+    <string name="subject_header">CTS Verifier %1$s - %2$s</string>
 
     <!-- Strings for TestListActivity -->
     <string name="test_list_title">Manual Test List</string>
@@ -43,6 +40,7 @@
     <string name="test_results_copied">Test results copied to clipboard.</string>
     <string name="share">Share</string>
     <string name="share_test_results">Share Test Results</string>
+    <string name="test_results_error">Couldn\'t create test results report.</string>
 
     <!-- Strings for BluetoothActivity -->
     <string name="bluetooth_test">Bluetooth Test</string>
diff --git a/apps/CtsVerifier/res/values/styles.xml b/apps/CtsVerifier/res/values/styles.xml
index c5b05cb..c7fe859 100644
--- a/apps/CtsVerifier/res/values/styles.xml
+++ b/apps/CtsVerifier/res/values/styles.xml
@@ -3,4 +3,7 @@
     <style name="WelcomeFont" parent="@android:style/TextAppearance.Large">
         <item name="android:textColor">#9fbf3b</item>
     </style>
+    <style name="VersionFont" parent="@android:style/TextAppearance.Large">
+        <item name="android:textColor">#ffffff</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/CtsVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/CtsVerifierActivity.java
index e35674c..ee3184b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/CtsVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/CtsVerifierActivity.java
@@ -20,8 +20,9 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
-import android.view.Window;
 import android.view.View.OnClickListener;
+import android.view.Window;
+import android.widget.TextView;
 
 /** {@link Activity} that displays an introduction to the verifier. */
 public class CtsVerifierActivity extends Activity {
@@ -40,6 +41,9 @@
             }
         };
 
+        TextView versionText = (TextView) findViewById(R.id.version_text);
+        versionText.setText(getString(R.string.version_text, Version.getVersionName(this)));
+
         findViewById(R.id.detective_logo).setOnClickListener(clickListener);
         findViewById(R.id.continue_button).setOnClickListener(clickListener);
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
index 244caab..fe41583 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.text.ClipboardManager;
+import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -29,10 +30,15 @@
 import android.widget.ListView;
 import android.widget.Toast;
 
+import java.io.IOException;
+
 /** {@link ListActivity} that displays a  list of manual tests. */
 public class TestListActivity extends ListActivity {
 
+    private static final String TAG = TestListActivity.class.getSimpleName();
+
     private static final int LAUNCH_TEST_REQUEST_CODE = 1;
+
     private TestListAdapter mAdapter;
 
     @Override
@@ -109,19 +115,29 @@
     }
 
     private void handleCopyItemSelected() {
-        TestResultsReport report = new TestResultsReport(this, mAdapter);
-        ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
-        clipboardManager.setText(report.getBody());
-        Toast.makeText(this, R.string.test_results_copied, Toast.LENGTH_SHORT).show();
+        try {
+            TestResultsReport report = new TestResultsReport(this, mAdapter);
+            ClipboardManager clipboardManager = (ClipboardManager)
+                    getSystemService(CLIPBOARD_SERVICE);
+            clipboardManager.setText(report.getBody());
+            Toast.makeText(this, R.string.test_results_copied, Toast.LENGTH_SHORT).show();
+        } catch (IOException e) {
+            Toast.makeText(this, R.string.test_results_error, Toast.LENGTH_SHORT).show();
+            Log.e(TAG, "Coudn't copy test results report", e);
+        }
     }
 
     private void handleShareItemSelected() {
-        Intent target = new Intent(Intent.ACTION_SEND);
-        target.setType("text/plain");
-
-        TestResultsReport report = new TestResultsReport(this, mAdapter);
-        target.putExtra(Intent.EXTRA_SUBJECT, report.getSubject());
-        target.putExtra(Intent.EXTRA_TEXT, report.getBody());
-        startActivity(Intent.createChooser(target, getString(R.string.share_test_results)));
+        try {
+            Intent target = new Intent(Intent.ACTION_SEND);
+            TestResultsReport report = new TestResultsReport(this, mAdapter);
+            target.setType(report.getType());
+            target.putExtra(Intent.EXTRA_SUBJECT, report.getSubject());
+            target.putExtra(Intent.EXTRA_TEXT, report.getBody());
+            startActivity(Intent.createChooser(target, getString(R.string.share_test_results)));
+        } catch (IOException e) {
+            Toast.makeText(this, R.string.test_results_error, Toast.LENGTH_SHORT).show();
+            Log.e(TAG, "Coudn't share test results report", e);
+        }
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
index 18a08fe..c7af68a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
@@ -18,90 +18,131 @@
 
 import com.android.cts.verifier.TestListAdapter.TestListItem;
 
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Build;
+import org.xmlpull.v1.XmlSerializer;
 
-/** Plain text report of the current test results. */
+import android.content.Context;
+import android.os.Build;
+import android.util.Xml;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * XML text report of the current test results.
+ * <p>
+ * Sample:
+ * <pre>
+ * <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ * <test-results-report report-version="1" creation-time="Tue Jun 28 11:04:10 PDT 2011">
+ *   <verifier-info version-name="2.3_r4" version-code="2" />
+ *   <device-info>
+ *     <build-info fingerprint="google/soju/crespo:2.3.4/GRJ22/121341:user/release-keys" />
+ *   </device-info>
+ *   <test-results>
+ *     <test title="Audio Quality Verifier" class-name="com.android.cts.verifier.audioquality.AudioQualityVerifierActivity" result="not-executed" />
+ *     <test title="Hardware/Software Feature Summary" class-name="com.android.cts.verifier.features.FeatureSummaryActivity" result="fail" />
+ *     <test title="Bluetooth Test" class-name="com.android.cts.verifier.bluetooth.BluetoothTestActivity" result="fail" />
+ *     <test title="SUID File Scanner" class-name="com.android.cts.verifier.suid.SuidFilesActivity" result="not-executed" />
+ *     <test title="Accelerometer Test" class-name="com.android.cts.verifier.sensors.AccelerometerTestActivity" result="pass" />
+ *   </test-results>
+ * </test-results-report>
+ * </pre>
+ */
 class TestResultsReport {
 
+    /** Version of the test report. Increment whenever adding new tags and attributes. */
+    private static final int REPORT_VERSION = 1;
+
+    /** Format of the report's creation time. Maintain the same format at CTS. */
+    private static DateFormat DATE_FORMAT =
+            new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH);
+
+    private static final String TEST_RESULTS_REPORT_TAG = "test-results-report";
+    private static final String VERIFIER_INFO_TAG = "verifier-info";
+    private static final String DEVICE_INFO_TAG = "device-info";
+    private static final String BUILD_INFO_TAG = "build-info";
+    private static final String TEST_RESULTS_TAG = "test-results";
+    private static final String TEST_TAG = "test";
+
     private final Context mContext;
 
     private final TestListAdapter mAdapter;
 
-    private final String mVersionName;
-
     TestResultsReport(Context context, TestListAdapter adapter) {
         this.mContext = context;
         this.mAdapter = adapter;
-        this.mVersionName = getVersionName(context);
     }
 
-    private static String getVersionName(Context context) {
-        try {
-            PackageManager packageManager = context.getPackageManager();
-            PackageInfo info = packageManager.getPackageInfo(context.getPackageName(), 0);
-            return info.versionName;
-        } catch (NameNotFoundException e) {
-            throw new RuntimeException("Could not get find package information for "
-                    + context.getPackageName());
-        }
+    String getType() {
+        return "application/xml";
     }
 
     String getSubject() {
-        return new StringBuilder()
-                .append(mContext.getString(R.string.subject_header, mVersionName))
-                .append(' ')
-                .append(Build.FINGERPRINT)
-                .toString();
+        return mContext.getString(R.string.subject_header,
+                Version.getVersionName(mContext),
+                Build.FINGERPRINT);
     }
 
-    String getBody() {
-        StringBuilder builder = new StringBuilder()
-                .append(mContext.getString(R.string.body_header, mVersionName))
-                .append("\n\n")
-                .append(Build.FINGERPRINT)
-                .append("\n\n");
+    String getBody() throws IllegalArgumentException, IllegalStateException, IOException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
 
+        XmlSerializer xml = Xml.newSerializer();
+        xml.setOutput(outputStream, "utf-8");
+        xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        xml.startDocument("utf-8", true);
+
+        xml.startTag(null, TEST_RESULTS_REPORT_TAG);
+        xml.attribute(null, "report-version", Integer.toString(REPORT_VERSION));
+        xml.attribute(null, "creation-time", DATE_FORMAT.format(new Date()));
+
+        xml.startTag(null, VERIFIER_INFO_TAG);
+        xml.attribute(null, "version-name", Version.getVersionName(mContext));
+        xml.attribute(null, "version-code", Integer.toString(Version.getVersionCode(mContext)));
+        xml.endTag(null, VERIFIER_INFO_TAG);
+
+        xml.startTag(null, DEVICE_INFO_TAG);
+        xml.startTag(null, BUILD_INFO_TAG);
+        xml.attribute(null, "fingerprint", Build.FINGERPRINT);
+        xml.endTag(null, BUILD_INFO_TAG);
+        xml.endTag(null, DEVICE_INFO_TAG);
+
+        xml.startTag(null, TEST_RESULTS_TAG);
         int count = mAdapter.getCount();
         for (int i = 0; i < count; i++) {
             TestListItem item = mAdapter.getItem(i);
-            if (!item.isTest()) {
-                builder.append(item.title).append('\n');
-            } else {
-                builder.append(item.title)
-                        .append(".....")
-                        .append(getTestResultString(mAdapter.getTestResult(i)))
-                        .append('\n');
-            }
-
-            if (i + 1 < count && !mAdapter.getItem(i + 1).isTest()) {
-                builder.append('\n');
+            if (item.isTest()) {
+                xml.startTag(null, TEST_TAG);
+                xml.attribute(null, "title", item.title);
+                xml.attribute(null, "class-name", item.className);
+                xml.attribute(null, "result", getTestResultString(mAdapter.getTestResult(i)));
+                xml.endTag(null, TEST_TAG);
             }
         }
-        return builder.toString();
+        xml.endTag(null, TEST_RESULTS_TAG);
+
+        xml.endTag(null, TEST_RESULTS_REPORT_TAG);
+        xml.endDocument();
+
+        return outputStream.toString("utf-8");
     }
 
     private String getTestResultString(int testResult) {
-        int resId = 0;
         switch (testResult) {
             case TestResult.TEST_RESULT_PASSED:
-                resId = R.string.pass_result;
-                break;
+                return "pass";
 
             case TestResult.TEST_RESULT_FAILED:
-                resId = R.string.fail_result;
-                break;
+                return "fail";
 
             case TestResult.TEST_RESULT_NOT_EXECUTED:
-                resId = R.string.not_executed_result;
-                break;
+                return "not-executed";
 
             default:
                 throw new IllegalArgumentException("Unknown test result: " + testResult);
         }
-        return mContext.getString(resId);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/Version.java b/apps/CtsVerifier/src/com/android/cts/verifier/Version.java
new file mode 100644
index 0000000..e7b6121
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/Version.java
@@ -0,0 +1,43 @@
+/*
+ * 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.verifier;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+
+class Version {
+
+    static String getVersionName(Context context) {
+        return getPackageInfo(context).versionName;
+    }
+
+    static int getVersionCode(Context context) {
+        return getPackageInfo(context).versionCode;
+    }
+
+    static PackageInfo getPackageInfo(Context context) {
+        try {
+            PackageManager packageManager = context.getPackageManager();
+            return packageManager.getPackageInfo(context.getPackageName(), 0);
+        } catch (NameNotFoundException e) {
+            throw new RuntimeException("Could not get find package information for "
+                    + context.getPackageName());
+        }
+    }
+}