1 /* 2 * Copyright (C) 2010 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 */ 16 17 package com.android.cts.verifier; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.database.ContentObserver; 23 import android.database.Cursor; 24 import android.os.AsyncTask; 25 import android.os.Handler; 26 import android.view.LayoutInflater; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import android.widget.BaseAdapter; 30 import android.widget.ListView; 31 import android.widget.TextView; 32 33 import java.util.ArrayList; 34 import java.util.HashMap; 35 import java.util.List; 36 import java.util.Map; 37 38 /** 39 * {@link BaseAdapter} that handles loading, refreshing, and setting test 40 * results. What tests are shown can be customized by overriding 41 * {@link #getRows()}. See {@link ArrayTestListAdapter} and 42 * {@link ManifestTestListAdapter} for examples. 43 */ 44 public abstract class TestListAdapter extends BaseAdapter { 45 46 /** Activities implementing {@link Intent#ACTION_MAIN} and this will appear in the list. */ 47 public static final String CATEGORY_MANUAL_TEST = "android.cts.intent.category.MANUAL_TEST"; 48 49 /** View type for a category of tests like "Sensors" or "Features" */ 50 private static final int CATEGORY_HEADER_VIEW_TYPE = 0; 51 52 /** View type for an actual test like the Accelerometer test. */ 53 private static final int TEST_VIEW_TYPE = 1; 54 55 /** Padding around the text views and icons. */ 56 private static final int PADDING = 10; 57 58 private final Context mContext; 59 60 /** Immutable data of tests like the test's title and launch intent. */ 61 private final List<TestListItem> mRows = new ArrayList<TestListItem>(); 62 63 /** Mutable test results that will change as each test activity finishes. */ 64 private final Map<String, Integer> mTestResults = new HashMap<String, Integer>(); 65 66 /** Map from test name to test details. */ 67 private final Map<String, String> mTestDetails = new HashMap<String, String>(); 68 69 private final LayoutInflater mLayoutInflater; 70 71 /** {@link ListView} row that is either a test category header or a test. */ 72 public static class TestListItem { 73 74 /** Title shown in the {@link ListView}. */ 75 final String title; 76 77 /** Test name with class and test ID to uniquely identify the test. Null for categories. */ 78 final String testName; 79 80 /** Intent used to launch the activity from the list. Null for categories. */ 81 final Intent intent; 82 83 /** Features necessary to run this test. */ 84 final String[] requiredFeatures; 85 86 public static TestListItem newTest(Context context, int titleResId, String testName, 87 Intent intent, String[] requiredFeatures) { 88 return newTest(context.getString(titleResId), testName, intent, requiredFeatures); 89 } 90 91 public static TestListItem newTest(String title, String testName, Intent intent, 92 String[] requiredFeatures) { 93 return new TestListItem(title, testName, intent, requiredFeatures); 94 } 95 96 public static TestListItem newCategory(Context context, int titleResId) { 97 return newCategory(context.getString(titleResId)); 98 } 99 100 public static TestListItem newCategory(String title) { 101 return new TestListItem(title, null, null, null); 102 } 103 104 private TestListItem(String title, String testName, Intent intent, 105 String[] requiredFeatures) { 106 this.title = title; 107 this.testName = testName; 108 this.intent = intent; 109 this.requiredFeatures = requiredFeatures; 110 } 111 112 boolean isTest() { 113 return intent != null; 114 } 115 } 116 117 public TestListAdapter(Context context) { 118 this.mContext = context; 119 this.mLayoutInflater = 120 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 121 122 TestResultContentObserver observer = new TestResultContentObserver(); 123 ContentResolver resolver = context.getContentResolver(); 124 resolver.registerContentObserver(TestResultsProvider.RESULTS_CONTENT_URI, true, observer); 125 } 126 127 public void loadTestResults() { 128 new RefreshTestResultsTask().execute(); 129 } 130 131 public void clearTestResults() { 132 new ClearTestResultsTask().execute(); 133 } 134 135 public void setTestResult(TestResult testResult) { 136 new SetTestResultTask(testResult.getName(), testResult.getResult(), 137 testResult.getDetails()).execute(); 138 } 139 140 class RefreshTestResultsTask extends AsyncTask<Void, Void, RefreshResult> { 141 @Override 142 protected RefreshResult doInBackground(Void... params) { 143 List<TestListItem> rows = getRows(); 144 return getRefreshResults(rows); 145 } 146 147 @Override 148 protected void onPostExecute(RefreshResult result) { 149 super.onPostExecute(result); 150 mRows.clear(); 151 mRows.addAll(result.mItems); 152 mTestResults.clear(); 153 mTestResults.putAll(result.mResults); 154 mTestDetails.clear(); 155 mTestDetails.putAll(result.mDetails); 156 notifyDataSetChanged(); 157 } 158 } 159 160 static class RefreshResult { 161 List<TestListItem> mItems; 162 Map<String, Integer> mResults; 163 Map<String, String> mDetails; 164 165 RefreshResult(List<TestListItem> items, Map<String, Integer> results, 166 Map<String, String> details) { 167 mItems = items; 168 mResults = results; 169 mDetails = details; 170 } 171 } 172 173 protected abstract List<TestListItem> getRows(); 174 175 static final String[] REFRESH_PROJECTION = { 176 TestResultsProvider._ID, 177 TestResultsProvider.COLUMN_TEST_NAME, 178 TestResultsProvider.COLUMN_TEST_RESULT, 179 TestResultsProvider.COLUMN_TEST_DETAILS, 180 }; 181 182 RefreshResult getRefreshResults(List<TestListItem> items) { 183 Map<String, Integer> results = new HashMap<String, Integer>(); 184 Map<String, String> details = new HashMap<String, String>(); 185 ContentResolver resolver = mContext.getContentResolver(); 186 Cursor cursor = null; 187 try { 188 cursor = resolver.query(TestResultsProvider.RESULTS_CONTENT_URI, REFRESH_PROJECTION, 189 null, null, null); 190 if (cursor.moveToFirst()) { 191 do { 192 String testName = cursor.getString(1); 193 int testResult = cursor.getInt(2); 194 String testDetails = cursor.getString(3); 195 results.put(testName, testResult); 196 details.put(testName, testDetails); 197 } while (cursor.moveToNext()); 198 } 199 } finally { 200 if (cursor != null) { 201 cursor.close(); 202 } 203 } 204 return new RefreshResult(items, results, details); 205 } 206 207 class ClearTestResultsTask extends AsyncTask<Void, Void, Void> { 208 209 @Override 210 protected Void doInBackground(Void... params) { 211 ContentResolver resolver = mContext.getContentResolver(); 212 resolver.delete(TestResultsProvider.RESULTS_CONTENT_URI, "1", null); 213 return null; 214 } 215 } 216 217 class SetTestResultTask extends AsyncTask<Void, Void, Void> { 218 219 private final String mTestName; 220 221 private final int mResult; 222 223 private final String mDetails; 224 225 SetTestResultTask(String testName, int result, String details) { 226 mTestName = testName; 227 mResult = result; 228 mDetails = details; 229 } 230 231 @Override 232 protected Void doInBackground(Void... params) { 233 TestResultsProvider.setTestResult(mContext, mTestName, mResult, mDetails); 234 return null; 235 } 236 } 237 238 class TestResultContentObserver extends ContentObserver { 239 240 public TestResultContentObserver() { 241 super(new Handler()); 242 } 243 244 @Override 245 public void onChange(boolean selfChange) { 246 super.onChange(selfChange); 247 loadTestResults(); 248 } 249 } 250 251 @Override 252 public boolean areAllItemsEnabled() { 253 // Section headers for test categories are not clickable. 254 return false; 255 } 256 257 @Override 258 public boolean isEnabled(int position) { 259 return getItem(position).isTest(); 260 } 261 262 @Override 263 public int getItemViewType(int position) { 264 return getItem(position).isTest() ? TEST_VIEW_TYPE : CATEGORY_HEADER_VIEW_TYPE; 265 } 266 267 @Override 268 public int getViewTypeCount() { 269 return 2; 270 } 271 272 @Override 273 public int getCount() { 274 return mRows.size(); 275 } 276 277 @Override 278 public TestListItem getItem(int position) { 279 return mRows.get(position); 280 } 281 282 @Override 283 public long getItemId(int position) { 284 return position; 285 } 286 287 public int getTestResult(int position) { 288 TestListItem item = getItem(position); 289 return mTestResults.containsKey(item.testName) 290 ? mTestResults.get(item.testName) 291 : TestResult.TEST_RESULT_NOT_EXECUTED; 292 } 293 294 public String getTestDetails(int position) { 295 TestListItem item = getItem(position); 296 return mTestDetails.containsKey(item.testName) 297 ? mTestDetails.get(item.testName) 298 : null; 299 } 300 301 public boolean allTestsPassed() { 302 for (TestListItem item : mRows) { 303 if (item.isTest() && (!mTestResults.containsKey(item.testName) 304 || (mTestResults.get(item.testName) != TestResult.TEST_RESULT_PASSED))) { 305 return false; 306 } 307 } 308 return true; 309 } 310 311 @Override 312 public View getView(int position, View convertView, ViewGroup parent) { 313 TextView textView; 314 if (convertView == null) { 315 int layout = getLayout(position); 316 textView = (TextView) mLayoutInflater.inflate(layout, parent, false); 317 } else { 318 textView = (TextView) convertView; 319 } 320 321 TestListItem item = getItem(position); 322 textView.setText(item.title); 323 textView.setPadding(PADDING, 0, PADDING, 0); 324 textView.setCompoundDrawablePadding(PADDING); 325 326 if (item.isTest()) { 327 int testResult = getTestResult(position); 328 int backgroundResource = 0; 329 int iconResource = 0; 330 331 /** TODO: Remove fs_ prefix from feature icons since they are used here too. */ 332 switch (testResult) { 333 case TestResult.TEST_RESULT_PASSED: 334 backgroundResource = R.drawable.test_pass_gradient; 335 iconResource = R.drawable.fs_good; 336 break; 337 338 case TestResult.TEST_RESULT_FAILED: 339 backgroundResource = R.drawable.test_fail_gradient; 340 iconResource = R.drawable.fs_error; 341 break; 342 343 case TestResult.TEST_RESULT_NOT_EXECUTED: 344 break; 345 346 default: 347 throw new IllegalArgumentException("Unknown test result: " + testResult); 348 } 349 350 textView.setBackgroundResource(backgroundResource); 351 textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, iconResource, 0); 352 } 353 354 return textView; 355 } 356 357 private int getLayout(int position) { 358 int viewType = getItemViewType(position); 359 switch (viewType) { 360 case CATEGORY_HEADER_VIEW_TYPE: 361 return R.layout.test_category_row; 362 case TEST_VIEW_TYPE: 363 return android.R.layout.simple_list_item_1; 364 default: 365 throw new IllegalArgumentException("Illegal view type: " + viewType); 366 367 } 368 } 369 }