am cf0c3aa8: Merge "CtsVerifier Test Result Infrastructure" into froyo
Merge commit 'cf0c3aa8313c4202d5af2c879824a57f906cd96f' into gingerbread
* commit 'cf0c3aa8313c4202d5af2c879824a57f906cd96f':
CtsVerifier Test Result Infrastructure
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 625dfe5..190c519 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -32,6 +32,9 @@
</activity>
<activity android:name=".TestListActivity" android:label="@string/test_list_title" />
+
+ <provider android:name=".TestResultsProvider"
+ android:authorities="com.android.cts.verifier.testresultsprovider" />
<activity android:name=".suid.SuidFilesActivity"
android:label="@string/suid_files"
diff --git a/apps/CtsVerifier/res/drawable/test_fail_gradient.xml b/apps/CtsVerifier/res/drawable/test_fail_gradient.xml
new file mode 100644
index 0000000..defc0dd
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable/test_fail_gradient.xml
@@ -0,0 +1,6 @@
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <gradient android:startColor="#33F3614B"
+ android:centerColor="#66F3614B"
+ android:endColor="#FFF3614B"
+ android:angle="315"/>
+</shape>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/drawable/test_pass_gradient.xml b/apps/CtsVerifier/res/drawable/test_pass_gradient.xml
new file mode 100644
index 0000000..49a938c
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable/test_pass_gradient.xml
@@ -0,0 +1,6 @@
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <gradient android:startColor="#3399D200"
+ android:centerColor="#6699D200"
+ android:endColor="#FF99D200"
+ android:angle="315"/>
+</shape>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/main.xml b/apps/CtsVerifier/res/layout/main.xml
index 52da8b8..479cc1b 100644
--- a/apps/CtsVerifier/res/layout/main.xml
+++ b/apps/CtsVerifier/res/layout/main.xml
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
@@ -28,7 +28,8 @@
android:id="@+id/continue_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
android:onClick="continueButtonClickHandler"
android:text="@string/continue_button_text"
/>
-</LinearLayout>
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/menu/test_list_menu.xml b/apps/CtsVerifier/res/menu/test_list_menu.xml
new file mode 100644
index 0000000..3f97a16
--- /dev/null
+++ b/apps/CtsVerifier/res/menu/test_list_menu.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/clear"
+ android:icon="@android:drawable/ic_menu_delete"
+ android:title="@string/clear" />
+</menu>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 6d4d52b..9904336 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -24,6 +24,8 @@
<string name="test_category_security">Security</string>
<string name="test_category_features">Features</string>
<string name="test_category_other">Other</string>
+ <string name="clear">Clear</string>
+ <string name="test_results_cleared">Test results cleared.</string>
<!-- Strings for FeatureSummaryActivity -->
<string name="feature_summary">Hardware/Software Feature Summary</string>
@@ -38,7 +40,7 @@
<!-- Strings for SuidFilesActivity -->
<string name="suid_files">SUID Files</string>
- <string name="starting_scan">Starting scan...</string>
+ <string name="scanning_directory">Scanning directory...</string>
<string name="file_status">User: %1$s\nGroup: %2$s\nPermissions: %3$s\nPath: %4$s</string>
<string name="no_file_status">Could not stat file...</string>
<string name="congratulations">Congratulations!</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
index f0bba52..b7801a6 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
@@ -16,251 +16,132 @@
package com.android.cts.verifier;
-import android.app.ListActivity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
+import com.android.cts.verifier.TestListAdapter.TestListItem;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import android.app.ListActivity;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.Toast;
/** {@link ListActivity} that displays a list of manual tests. */
public class TestListActivity extends ListActivity {
- /** Activities implementing {@link Intent#ACTION_MAIN} and this will appear in the list. */
- static final String CATEGORY_MANUAL_TEST = "android.cts.intent.category.MANUAL_TEST";
-
- static final String TEST_CATEGORY_META_DATA = "test_category";
+ private static final int LAUNCH_TEST_REQUEST_CODE = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setListAdapter(new TestListAdapter(this));
- }
+ TestListAdapter adapter = new TestListAdapter(this);
+ setListAdapter(adapter);
+
+ TestResultContentObserver observer = new TestResultContentObserver(adapter);
+ ContentResolver resolver = getContentResolver();
+ resolver.registerContentObserver(TestResultsProvider.CONTENT_URI, true, observer);
+ }
/** Launch the activity when its {@link ListView} item is clicked. */
@Override
protected void onListItemClick(ListView listView, View view, int position, long id) {
super.onListItemClick(listView, view, position, id);
Intent intent = getIntent(position);
- startActivity(intent);
+ startActivityForResult(intent, LAUNCH_TEST_REQUEST_CODE);
}
- @SuppressWarnings("unchecked")
private Intent getIntent(int position) {
- ListAdapter adapter = getListAdapter();
- Map<String, ?> data = (Map<String, ?>) adapter.getItem(position);
- return (Intent) data.get(TestListAdapter.INTENT);
+ TestListAdapter adapter = getListAdapter();
+ TestListItem item = adapter.getItem(position);
+ return item.intent;
+ }
+
+ @Override
+ public TestListAdapter getListAdapter() {
+ return (TestListAdapter) super.getListAdapter();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ switch (requestCode) {
+ case LAUNCH_TEST_REQUEST_CODE:
+ handleLaunchTestResult(resultCode, data);
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown request code: " + requestCode);
+ }
+ }
+
+ private void handleLaunchTestResult(int resultCode, Intent data) {
+ if (resultCode == RESULT_OK) {
+ TestResult testResult = TestResult.fromActivityResult(resultCode, data);
+ ContentValues values = new ContentValues(2);
+ values.put(TestResultsProvider.COLUMN_TEST_RESULT, testResult.getResult());
+ values.put(TestResultsProvider.COLUMN_TEST_NAME, testResult.getName());
+
+ ContentResolver resolver = getContentResolver();
+ int numUpdated = resolver.update(TestResultsProvider.CONTENT_URI, values,
+ TestResultsProvider.COLUMN_TEST_NAME + " = ?",
+ new String[] {testResult.getName()});
+
+ if (numUpdated == 0) {
+ resolver.insert(TestResultsProvider.CONTENT_URI, values);
+ }
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.test_list_menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.clear:
+ handleClearItemSelected();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private void handleClearItemSelected() {
+ ContentResolver resolver = getContentResolver();
+ resolver.delete(TestResultsProvider.CONTENT_URI, "1", null);
+ Toast.makeText(this, R.string.test_results_cleared, Toast.LENGTH_SHORT).show();
}
/**
- * Each {@link ListView} item will have a map associated it with containing the title to
- * display and the intent used to launch it. If there is no intent, then it is a test category
- * header.
+ * {@link ContentResolver} that refreshes the {@link TestListAdapter} and thus
+ * the {@link ListView} when the test results change.
*/
- static class TestListAdapter extends BaseAdapter {
+ private static class TestResultContentObserver extends ContentObserver {
- static final String TITLE = "title";
+ private final TestListAdapter mAdapter;
- static final String INTENT = "intent";
-
- /** View type for a category of tests like "Sensors" or "Features" */
- static final int TEST_CATEGORY_HEADER_VIEW_TYPE = 0;
-
- /** View type for an actual test like the Accelerometer test. */
- static final int TEST_VIEW_TYPE = 1;
-
- private final List<Map<String, ?>> mData;
-
- private final LayoutInflater mLayoutInflater;
-
- TestListAdapter(Context context) {
- this.mData = getData(context);
- this.mLayoutInflater =
- (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- static List<Map<String, ?>> getData(Context context) {
- /*
- * 1. Get all the tests keyed by their category.
- * 2. Flatten the tests and categories into one giant list for the list view.
- */
-
- Map<String, List<Map<String, ?>>> testsByCategory = getTestsByCategory(context);
-
- List<String> testCategories = new ArrayList<String>(testsByCategory.keySet());
- Collections.sort(testCategories);
-
- List<Map<String, ?>> data = new ArrayList<Map<String, ?>>();
- for (String testCategory : testCategories) {
- addItem(data, testCategory, null);
-
- List<Map<String, ?>> tests = testsByCategory.get(testCategory);
- Collections.sort(tests, new Comparator<Map<String, ?>>() {
- public int compare(Map<String, ?> item, Map<String, ?> otherItem) {
- String title = (String) item.get(TITLE);
- String otherTitle = (String) otherItem.get(TITLE);
- return title.compareTo(otherTitle);
- }
- });
- data.addAll(tests);
- }
-
- return data;
- }
-
- static Map<String, List<Map<String, ?>>> getTestsByCategory(Context context) {
- Map<String, List<Map<String, ?>>> testsByCategory =
- new HashMap<String, List<Map<String, ?>>>();
-
- Intent mainIntent = new Intent(Intent.ACTION_MAIN);
- mainIntent.addCategory(CATEGORY_MANUAL_TEST);
-
- PackageManager packageManager = context.getPackageManager();
- List<ResolveInfo> list = packageManager.queryIntentActivities(mainIntent,
- PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
-
- for (int i = 0; i < list.size(); i++) {
- ResolveInfo info = list.get(i);
- String testCategory = getTestCategory(context, info.activityInfo.metaData);
- String title = getTitle(context, info.activityInfo);
- Intent intent = getActivityIntent(info.activityInfo);
- addItemToCategory(testsByCategory, testCategory, title, intent);
- }
-
- return testsByCategory;
- }
-
- static String getTestCategory(Context context, Bundle metaData) {
- String testCategory = null;
- if (metaData != null) {
- testCategory = metaData.getString(TEST_CATEGORY_META_DATA);
- }
- if (testCategory != null) {
- return testCategory;
- } else {
- return context.getString(R.string.test_category_other);
- }
- }
-
- static String getTitle(Context context, ActivityInfo activityInfo) {
- if (activityInfo.labelRes != 0) {
- return context.getString(activityInfo.labelRes);
- } else {
- return activityInfo.name;
- }
- }
-
- static Intent getActivityIntent(ActivityInfo activityInfo) {
- Intent intent = new Intent();
- intent.setClassName(activityInfo.packageName, activityInfo.name);
- return intent;
- }
-
- static void addItemToCategory(Map<String, List<Map<String, ?>>> data, String testCategory,
- String title, Intent intent) {
- List<Map<String, ?>> tests;
- if (data.containsKey(testCategory)) {
- tests = data.get(testCategory);
- } else {
- tests = new ArrayList<Map<String, ?>>();
- }
- data.put(testCategory, tests);
- addItem(tests, title, intent);
- }
-
- /**
- * @param tests to add this new item to
- * @param title to show in the list view
- * @param intent for a test to launch or null for a test category header
- */
- @SuppressWarnings("unchecked")
- static void addItem(List<Map<String, ?>> tests, String title, Intent intent) {
- HashMap item = new HashMap(2);
- item.put(TITLE, title);
- item.put(INTENT, intent);
- tests.add(item);
+ public TestResultContentObserver(TestListAdapter adapter) {
+ super(new Handler());
+ this.mAdapter = adapter;
}
@Override
- public boolean areAllItemsEnabled() {
- // Section headers for test categories are not clickable.
- return false;
- }
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
- @Override
- public boolean isEnabled(int position) {
- return isTestActivity(position);
- }
-
- @Override
- public int getItemViewType(int position) {
- return isTestActivity(position) ? TEST_VIEW_TYPE : TEST_CATEGORY_HEADER_VIEW_TYPE;
- }
-
- private boolean isTestActivity(int position) {
- Map<String, ?> item = getItem(position);
- return item.get(INTENT) != null;
- }
-
- @Override
- public int getViewTypeCount() {
- return 2;
- }
-
- public int getCount() {
- return mData.size();
- }
-
- public Map<String, ?> getItem(int position) {
- return mData.get(position);
- }
-
- public long getItemId(int position) {
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- TextView textView;
- if (convertView == null) {
- int layout = getLayout(position);
- textView = (TextView) mLayoutInflater.inflate(layout, parent, false);
- } else {
- textView = (TextView) convertView;
- }
-
- Map<String, ?> data = getItem(position);
- String title = (String) data.get(TITLE);
- textView.setText(title);
- return textView;
- }
-
- private int getLayout(int position) {
- int viewType = getItemViewType(position);
- switch (viewType) {
- case TEST_CATEGORY_HEADER_VIEW_TYPE:
- return R.layout.test_category_row;
- case TEST_VIEW_TYPE:
- return android.R.layout.simple_list_item_1;
- default:
- throw new IllegalArgumentException("Illegal view type: " + viewType);
-
- }
+ // TODO: Could be improved by just refreshing the particular test result.
+ mAdapter.refreshTestResults();
}
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
new file mode 100644
index 0000000..ddf4fe8
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2010 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.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * {@link BaseAdapter} that populates the {@link TestListActivity}'s {@link ListView}.
+ * Making a new test activity to appear in the list requires the following steps:
+ *
+ * <ol>
+ * <li>REQUIRED: Add an activity to the AndroidManifest.xml with an intent filter with a
+ * main action and the MANUAL_TEST category.
+ * <pre>
+ * <intent-filter>
+ * <action android:name="android.intent.action.MAIN" />
+ * <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ * </intent-filter>
+ * </pre>
+ * </li>
+ * <li>OPTIONAL: Add a meta data attribute to indicate what category of tests the activity
+ * should belong to. If you don't add this attribute, your test will show up in the
+ * "Other" tests category.
+ * <pre>
+ * <meta-data android:name="test_category" android:value="@string/test_category_security" />
+ * </pre>
+ * </li>
+ * </ol>
+ */
+class TestListAdapter extends BaseAdapter {
+
+ /** Activities implementing {@link Intent#ACTION_MAIN} and this will appear in the list. */
+ public static final String CATEGORY_MANUAL_TEST = "android.cts.intent.category.MANUAL_TEST";
+
+ private static final String TEST_CATEGORY_META_DATA = "test_category";
+
+ /** View type for a category of tests like "Sensors" or "Features" */
+ private static final int CATEGORY_HEADER_VIEW_TYPE = 0;
+
+ /** View type for an actual test like the Accelerometer test. */
+ private static final int TEST_VIEW_TYPE = 1;
+
+ /** Padding around the text views and icons. */
+ private static final int PADDING = 10;
+
+ private final Context mContext;
+
+ /** Immutable data of tests like the test's title and launch intent. */
+ private final List<TestListItem> mRows;
+
+ /** Mutable test results that will change as each test activity finishes. */
+ private final Map<String, Integer> mTestResults = new HashMap<String, Integer>();
+
+ private final LayoutInflater mLayoutInflater;
+
+ /** {@link ListView} row that is either a test category header or a test. */
+ static class TestListItem {
+
+ /** Title shown in the {@link ListView}. */
+ final String title;
+
+ /** Class name with package to uniquely identify the test. Null for categories. */
+ final String className;
+
+ /** Intent used to launch the activity from the list. Null for categories. */
+ final Intent intent;
+
+ static TestListItem newTest(String title, String className, Intent intent) {
+ return new TestListItem(title, className, intent);
+ }
+
+ static TestListItem newCategory(String title) {
+ return new TestListItem(title, null, null);
+ }
+
+ private TestListItem(String title, String className, Intent intent) {
+ this.title = title;
+ this.className = className;
+ this.intent = intent;
+ }
+
+ boolean isTest() {
+ return intent != null;
+ }
+ }
+
+ TestListAdapter(Context context) {
+ this.mContext = context;
+ this.mRows = getRows(context);
+ this.mLayoutInflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ updateTestResults(mContext, mTestResults);
+ }
+
+ static List<TestListItem> getRows(Context context) {
+ /*
+ * 1. Get all the tests keyed by their category.
+ * 2. Flatten the tests and categories into one giant list for the list view.
+ */
+
+ Map<String, List<TestListItem>> testsByCategory = getTestsByCategory(context);
+
+ List<String> testCategories = new ArrayList<String>(testsByCategory.keySet());
+ Collections.sort(testCategories);
+
+ List<TestListItem> allRows = new ArrayList<TestListItem>();
+ for (String testCategory : testCategories) {
+ allRows.add(TestListItem.newCategory(testCategory));
+
+ List<TestListItem> tests = testsByCategory.get(testCategory);
+ Collections.sort(tests, new Comparator<TestListItem>() {
+ public int compare(TestListItem item, TestListItem otherItem) {
+ return item.title.compareTo(otherItem.title);
+ }
+ });
+ allRows.addAll(tests);
+ }
+ return allRows;
+ }
+
+ static Map<String, List<TestListItem>> getTestsByCategory(Context context) {
+ Map<String, List<TestListItem>> testsByCategory =
+ new HashMap<String, List<TestListItem>>();
+
+ Intent mainIntent = new Intent(Intent.ACTION_MAIN);
+ mainIntent.addCategory(CATEGORY_MANUAL_TEST);
+
+ PackageManager packageManager = context.getPackageManager();
+ List<ResolveInfo> list = packageManager.queryIntentActivities(mainIntent,
+ PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+
+ for (int i = 0; i < list.size(); i++) {
+ ResolveInfo info = list.get(i);
+ String testCategory = getTestCategory(context, info.activityInfo.metaData);
+ String title = getTitle(context, info.activityInfo);
+ String className = info.activityInfo.name;
+ Intent intent = getActivityIntent(info.activityInfo);
+
+ addTestToCategory(testsByCategory, testCategory, title, className, intent);
+ }
+
+ return testsByCategory;
+ }
+
+ static String getTestCategory(Context context, Bundle metaData) {
+ String testCategory = null;
+ if (metaData != null) {
+ testCategory = metaData.getString(TEST_CATEGORY_META_DATA);
+ }
+ if (testCategory != null) {
+ return testCategory;
+ } else {
+ return context.getString(R.string.test_category_other);
+ }
+ }
+
+ static String getTitle(Context context, ActivityInfo activityInfo) {
+ if (activityInfo.labelRes != 0) {
+ return context.getString(activityInfo.labelRes);
+ } else {
+ return activityInfo.name;
+ }
+ }
+
+ static Intent getActivityIntent(ActivityInfo activityInfo) {
+ Intent intent = new Intent();
+ intent.setClassName(activityInfo.packageName, activityInfo.name);
+ return intent;
+ }
+
+ static void addTestToCategory(Map<String, List<TestListItem>> testsByCategory,
+ String testCategory, String title, String className, Intent intent) {
+ List<TestListItem> tests;
+ if (testsByCategory.containsKey(testCategory)) {
+ tests = testsByCategory.get(testCategory);
+ } else {
+ tests = new ArrayList<TestListItem>();
+ }
+ testsByCategory.put(testCategory, tests);
+ tests.add(TestListItem.newTest(title, className, intent));
+ }
+
+ static void updateTestResults(Context context, Map<String, Integer> testResults) {
+ testResults.clear();
+ ContentResolver resolver = context.getContentResolver();
+ Cursor cursor = null;
+ try {
+ cursor = resolver.query(TestResultsProvider.RESULTS_ALL_CONTENT_URI,
+ TestResultsProvider.ALL_COLUMNS, null, null, null);
+ if (cursor.moveToFirst()) {
+ do {
+ String className = cursor.getString(1);
+ int testResult = cursor.getInt(2);
+ testResults.put(className, testResult);
+ } while (cursor.moveToNext());
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ public void refreshTestResults() {
+ updateTestResults(mContext, mTestResults);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ // Section headers for test categories are not clickable.
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return getItem(position).isTest();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return getItem(position).isTest() ? TEST_VIEW_TYPE : CATEGORY_HEADER_VIEW_TYPE;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ public int getCount() {
+ return mRows.size();
+ }
+
+ public TestListItem getItem(int position) {
+ return mRows.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView textView;
+ if (convertView == null) {
+ int layout = getLayout(position);
+ textView = (TextView) mLayoutInflater.inflate(layout, parent, false);
+ } else {
+ textView = (TextView) convertView;
+ }
+
+ TestListItem item = getItem(position);
+ textView.setText(item.title);
+ textView.setPadding(PADDING, 0, PADDING, 0);
+ textView.setCompoundDrawablePadding(PADDING);
+
+ if (item.isTest()) {
+ int testResult = mTestResults.containsKey(item.className)
+ ? mTestResults.get(item.className)
+ : TestResult.TEST_RESULT_NOT_EXECUTED;
+
+
+ int backgroundResource = 0;
+ int iconResource = 0;
+
+ /** TODO: Remove fs_ prefix from feature icons since they are used here too. */
+ switch (testResult) {
+ case TestResult.TEST_RESULT_PASSED:
+ backgroundResource = R.drawable.test_pass_gradient;
+ iconResource = R.drawable.fs_good;
+ break;
+
+ case TestResult.TEST_RESULT_FAILED:
+ backgroundResource = R.drawable.test_fail_gradient;
+ iconResource = R.drawable.fs_error;
+ break;
+
+ case TestResult.TEST_RESULT_NOT_EXECUTED:
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown test result: " + testResult);
+ }
+
+ textView.setBackgroundResource(backgroundResource);
+ textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, iconResource, 0);
+ }
+
+ return textView;
+ }
+
+ private int getLayout(int position) {
+ int viewType = getItemViewType(position);
+ switch (viewType) {
+ case CATEGORY_HEADER_VIEW_TYPE:
+ return R.layout.test_category_row;
+ case TEST_VIEW_TYPE:
+ return android.R.layout.simple_list_item_1;
+ default:
+ throw new IllegalArgumentException("Illegal view type: " + viewType);
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
new file mode 100644
index 0000000..6269f97
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 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.app.Activity;
+import android.content.Intent;
+
+/**
+ * Object representing the result of a test activity like whether it succeeded or failed.
+ * Use {@link #setPassedResult(Activity)} or {@link #setFailedResult(Activity)} from a test
+ * activity like you would {@link Activity#setResult(int)} so that {@link TestListActivity} will
+ * persist the test result and update its adapter and thus the list view.
+ */
+public class TestResult {
+
+ public static final int TEST_RESULT_NOT_EXECUTED = 0;
+ public static final int TEST_RESULT_PASSED = 1;
+ public static final int TEST_RESULT_FAILED = 2;
+
+ private static final String TEST_NAME = "name";
+ private static final String TEST_RESULT = "result";
+
+ private final String mName;
+
+ private final int mResult;
+
+ /** Sets the test activity's result to pass. */
+ public static void setPassedResult(Activity activity) {
+ activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_PASSED));
+ }
+
+ /** Sets the test activity's result to failed. */
+ public static void setFailedResult(Activity activity) {
+ activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_FAILED));
+ }
+
+ private static Intent createResult(Activity activity, int testResult) {
+ Intent data = new Intent(activity, activity.getClass());
+ data.putExtra(TEST_NAME, activity.getClass().getName());
+ data.putExtra(TEST_RESULT, testResult);
+ return data;
+ }
+
+ /**
+ * Convert the test activity's result into a {@link TestResult}. Only meant to be used by
+ * {@link TestListActivity}.
+ */
+ public static TestResult fromActivityResult(int resultCode, Intent data) {
+ String name = data.getStringExtra(TEST_NAME);
+ int result = data.getIntExtra(TEST_RESULT, TEST_RESULT_NOT_EXECUTED);
+ return new TestResult(name, result);
+ }
+
+ private TestResult(String name, int result) {
+ this.mName = name;
+ this.mResult = result;
+ }
+
+ /** Return the name of the test like "com.android.cts.verifier.foo.FooTest" */
+ public String getName() {
+ return mName;
+ }
+
+ /** Return integer test result. See test result constants. */
+ public int getResult() {
+ return mResult;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
new file mode 100644
index 0000000..8d07b13
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2010 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.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+
+/** {@link ContentProvider} that provides read and write access to the test results. */
+public class TestResultsProvider extends ContentProvider {
+
+ private static final String RESULTS_PATH = "results";
+
+ public static final String AUTHORITY = "com.android.cts.verifier.testresultsprovider";
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
+ public static final Uri RESULTS_ALL_CONTENT_URI = Uri.withAppendedPath(CONTENT_URI, RESULTS_PATH);
+
+ public static final String _ID = "_id";
+
+ /** String name of the test like "com.android.cts.verifier.foo.FooTestActivity" */
+ public static final String COLUMN_TEST_NAME = "testname";
+
+ /** Integer test result corresponding to constants in {@link TestResult}. */
+ public static final String COLUMN_TEST_RESULT = "testresult";
+
+ public static final String[] ALL_COLUMNS = {
+ _ID,
+ COLUMN_TEST_NAME,
+ COLUMN_TEST_RESULT
+ };
+
+ private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+ private static final int RESULTS_ALL = 1;
+ private static final int RESULTS_ID = 2;
+ private static final int RESULTS_TEST_NAME = 3;
+ static {
+ URI_MATCHER.addURI(AUTHORITY, RESULTS_PATH, RESULTS_ALL);
+ URI_MATCHER.addURI(AUTHORITY, RESULTS_PATH + "/#", RESULTS_ID);
+ URI_MATCHER.addURI(AUTHORITY, RESULTS_PATH + "/*", RESULTS_TEST_NAME);
+ }
+
+ private static final String TABLE_NAME = "results";
+
+ private SQLiteOpenHelper mOpenHelper;
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = new TestResultsOpenHelper(getContext());
+ return false;
+ }
+
+ private static class TestResultsOpenHelper extends SQLiteOpenHelper {
+
+ private static final String DATABASE_NAME = "results.db";
+
+ private static final int DATABASE_VERSION = 4;
+
+ TestResultsOpenHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_NAME + " ("
+ + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + COLUMN_TEST_NAME + " TEXT, "
+ + COLUMN_TEST_RESULT + " INTEGER);");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+ onCreate(db);
+ }
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ SQLiteQueryBuilder query = new SQLiteQueryBuilder();
+ query.setTables(TABLE_NAME);
+
+ int match = URI_MATCHER.match(uri);
+ switch (match) {
+ case RESULTS_ALL:
+ break;
+
+ case RESULTS_ID:
+ query.appendWhere(_ID);
+ query.appendWhere(uri.getPathSegments().get(0));
+ break;
+
+ case RESULTS_TEST_NAME:
+ query.appendWhere(COLUMN_TEST_NAME);
+ query.appendWhere(uri.getPathSegments().get(0));
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ return query.query(db, projection, selection, selectionArgs, null, null, sortOrder);
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long id = db.insert(TABLE_NAME, null, values);
+ getContext().getContentResolver().notifyChange(uri, null);
+ return Uri.withAppendedPath(CONTENT_URI, "" + id);
+
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int numUpdated = db.update(TABLE_NAME, values, selection, selectionArgs);
+ if (numUpdated > 0) {
+ getContext().getContentResolver().notifyChange(uri, null);
+ }
+ return numUpdated;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int numDeleted = db.delete(TABLE_NAME, selection, selectionArgs);
+ if (numDeleted > 0) {
+ getContext().getContentResolver().notifyChange(uri, null);
+ }
+ return numDeleted;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidFilesActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidFilesActivity.java
index c89b8b4..4188b58 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidFilesActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidFilesActivity.java
@@ -17,6 +17,7 @@
package com.android.cts.verifier.suid;
import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestResult;
import com.android.cts.verifier.os.FileUtils;
import com.android.cts.verifier.os.FileUtils.FileStatus;
@@ -61,12 +62,13 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setResult(RESULT_CANCELED);
mAdapter = new SuidFilesAdapter();
setListAdapter(mAdapter);
mProgressDialog = new ProgressDialog(this);
- mProgressDialog.setMessage(getString(R.string.starting_scan));
+ mProgressDialog.setTitle(getString(R.string.scanning_directory));
mProgressDialog.setOnCancelListener(new OnCancelListener() {
public void onCancel(DialogInterface dialog) {
// If the scanning dialog is cancelled, then stop the task and finish the activity
@@ -109,6 +111,7 @@
@Override
protected void onDestroy() {
+ Log.e("Suid", "onDestroy");
super.onDestroy();
if (mFindSuidFilesTask != null) {
mFindSuidFilesTask.cancel(true);
@@ -223,10 +226,13 @@
.setOnCancelListener(new OnCancelListener() {
public void onCancel(DialogInterface dialog) {
// No reason to hang around if there were no offending files.
+ TestResult.setPassedResult(SuidFilesActivity.this);
finish();
}
})
.show();
+ } else {
+ TestResult.setFailedResult(SuidFilesActivity.this);
}
}
}
diff --git a/apps/CtsVerifier/tests/src/com/android/cts/verifier/TestListActivityTest.java b/apps/CtsVerifier/tests/src/com/android/cts/verifier/TestListActivityTest.java
index 5175f33..5d0f918 100644
--- a/apps/CtsVerifier/tests/src/com/android/cts/verifier/TestListActivityTest.java
+++ b/apps/CtsVerifier/tests/src/com/android/cts/verifier/TestListActivityTest.java
@@ -19,8 +19,10 @@
import android.app.Activity;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
+import android.content.ContentResolver;
import android.content.Intent;
import android.content.IntentFilter;
+import android.database.Cursor;
import android.test.ActivityInstrumentationTestCase2;
import android.view.KeyEvent;
@@ -50,9 +52,29 @@
}
/** Test that clicking on an item launches a test. */
- public void testListItem() throws Throwable {
+ public void testLaunchAndFinishTestActivity() throws Throwable {
+ clearAllTestResults();
+ Activity testActivity = launchTestActivity();
+ finishTestActivity(testActivity);
+ }
+
+ private void clearAllTestResults() throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ ContentResolver resolver = mActivity.getContentResolver();
+ resolver.delete(TestResultsProvider.CONTENT_URI, "1", null);
+
+ Cursor cursor = resolver.query(TestResultsProvider.RESULTS_ALL_CONTENT_URI,
+ TestResultsProvider.ALL_COLUMNS, null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+ });
+ }
+
+ private Activity launchTestActivity() {
IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
- filter.addCategory(TestListActivity.CATEGORY_MANUAL_TEST);
+ filter.addCategory(TestListAdapter.CATEGORY_MANUAL_TEST);
ActivityMonitor monitor = new ActivityMonitor(filter, null, false);
mInstrumentation.addMonitor(monitor);
@@ -60,8 +82,20 @@
sendKeys(KeyEvent.KEYCODE_ENTER);
Activity activity = mInstrumentation.waitForMonitorWithTimeout(monitor,
- TimeUnit.SECONDS.toMillis(10));
+ TimeUnit.SECONDS.toMillis(1));
assertNotNull(activity);
+ return activity;
+ }
+
+ private void finishTestActivity(Activity activity) throws Throwable {
+ TestResult.setPassedResult(activity);
activity.finish();
+ mInstrumentation.waitForIdleSync();
+
+ ContentResolver resolver = mActivity.getContentResolver();
+ Cursor cursor = resolver.query(TestResultsProvider.RESULTS_ALL_CONTENT_URI,
+ TestResultsProvider.ALL_COLUMNS, null, null, null);
+ assertEquals(1, cursor.getCount());
+ cursor.close();
}
}
diff --git a/apps/CtsVerifier/tests/src/com/android/cts/verifier/TestResultsProviderTest.java b/apps/CtsVerifier/tests/src/com/android/cts/verifier/TestResultsProviderTest.java
new file mode 100644
index 0000000..0b90061
--- /dev/null
+++ b/apps/CtsVerifier/tests/src/com/android/cts/verifier/TestResultsProviderTest.java
@@ -0,0 +1,81 @@
+package com.android.cts.verifier;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.test.ProviderTestCase2;
+
+public class TestResultsProviderTest extends ProviderTestCase2<TestResultsProvider> {
+
+ private static final String FOO_TEST_NAME = "com.android.cts.verifier.foo.FooActivity";
+
+ private static final String BAR_TEST_NAME = "com.android.cts.verifier.foo.BarActivity";
+
+ private TestResultsProvider mProvider;
+
+ public TestResultsProviderTest() {
+ super(TestResultsProvider.class, TestResultsProvider.AUTHORITY);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mProvider = getProvider();
+ }
+
+ public void testInsertUpdateDeleteByTestName() {
+ Cursor cursor = mProvider.query(TestResultsProvider.RESULTS_ALL_CONTENT_URI,
+ TestResultsProvider.ALL_COLUMNS, null, null, null);
+ assertEquals(0, cursor.getCount());
+
+ ContentValues values = new ContentValues(2);
+ values.put(TestResultsProvider.COLUMN_TEST_NAME, FOO_TEST_NAME);
+ values.put(TestResultsProvider.COLUMN_TEST_RESULT, TestResult.TEST_RESULT_FAILED);
+ assertNotNull(mProvider.insert(TestResultsProvider.CONTENT_URI, values));
+
+ cursor = mProvider.query(TestResultsProvider.RESULTS_ALL_CONTENT_URI,
+ TestResultsProvider.ALL_COLUMNS, null, null, null);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToFirst());
+ assertEquals(FOO_TEST_NAME, cursor.getString(1));
+ assertEquals(TestResult.TEST_RESULT_FAILED, cursor.getInt(2));
+ cursor.close();
+
+ values = new ContentValues();
+ values.put(TestResultsProvider.COLUMN_TEST_NAME, BAR_TEST_NAME);
+ values.put(TestResultsProvider.COLUMN_TEST_RESULT, TestResult.TEST_RESULT_PASSED);
+ int numUpdated = mProvider.update(TestResultsProvider.CONTENT_URI, values,
+ TestResultsProvider.COLUMN_TEST_NAME + " = ?", new String[] {BAR_TEST_NAME});
+ assertEquals(0, numUpdated);
+
+ cursor = mProvider.query(Uri.withAppendedPath(TestResultsProvider.CONTENT_URI, "results"),
+ TestResultsProvider.ALL_COLUMNS, null, null, null);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToFirst());
+ assertEquals(FOO_TEST_NAME, cursor.getString(1));
+ assertEquals(TestResult.TEST_RESULT_FAILED, cursor.getInt(2));
+ cursor.close();
+
+ values = new ContentValues(1);
+ values.put(TestResultsProvider.COLUMN_TEST_RESULT, TestResult.TEST_RESULT_PASSED);
+ numUpdated = mProvider.update(TestResultsProvider.CONTENT_URI, values,
+ TestResultsProvider.COLUMN_TEST_NAME + " = ?", new String[] {FOO_TEST_NAME});
+ assertEquals(1, numUpdated);
+
+ cursor = mProvider.query(Uri.withAppendedPath(TestResultsProvider.CONTENT_URI, "results"),
+ TestResultsProvider.ALL_COLUMNS, null, null, null);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToFirst());
+ assertEquals(FOO_TEST_NAME, cursor.getString(1));
+ assertEquals(TestResult.TEST_RESULT_PASSED, cursor.getInt(2));
+ cursor.close();
+
+ int numDeleted = mProvider.delete(TestResultsProvider.CONTENT_URI, "1", null);
+ assertEquals(1, numDeleted);
+
+ cursor = mProvider.query(TestResultsProvider.RESULTS_ALL_CONTENT_URI,
+ TestResultsProvider.ALL_COLUMNS, null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+}