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 com.android.compatibility.common.util.ReportLog; 20 import com.android.compatibility.common.util.TestResultHistory; 21 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.database.ContentObserver; 26 import android.database.Cursor; 27 import android.os.AsyncTask; 28 import android.os.Handler; 29 import android.view.LayoutInflater; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.widget.BaseAdapter; 33 import android.widget.ListView; 34 import android.widget.TextView; 35 36 import java.io.ByteArrayInputStream; 37 import java.io.IOException; 38 import java.io.ObjectInputStream; 39 import java.util.ArrayList; 40 import java.util.HashMap; 41 import java.util.List; 42 import java.util.Map; 43 44 /** 45 * {@link BaseAdapter} that handles loading, refreshing, and setting test 46 * results. What tests are shown can be customized by overriding 47 * {@link #getRows()}. See {@link ArrayTestListAdapter} and 48 * {@link ManifestTestListAdapter} for examples. 49 */ 50 public abstract class TestListAdapter extends BaseAdapter { 51 52 /** Activities implementing {@link Intent#ACTION_MAIN} and this will appear in the list. */ 53 public static final String CATEGORY_MANUAL_TEST = "android.cts.intent.category.MANUAL_TEST"; 54 55 /** View type for a category of tests like "Sensors" or "Features" */ 56 private static final int CATEGORY_HEADER_VIEW_TYPE = 0; 57 58 /** View type for an actual test like the Accelerometer test. */ 59 private static final int TEST_VIEW_TYPE = 1; 60 61 /** Padding around the text views and icons. */ 62 private static final int PADDING = 10; 63 64 private final Context mContext; 65 66 /** Immutable data of tests like the test's title and launch intent. */ 67 private final List<TestListItem> mRows = new ArrayList<TestListItem>(); 68 69 /** Mutable test results that will change as each test activity finishes. */ 70 private final Map<String, Integer> mTestResults = new HashMap<String, Integer>(); 71 72 /** Map from test name to test details. */ 73 private final Map<String, String> mTestDetails = new HashMap<String, String>(); 74 75 /** Map from test name to {@link ReportLog}. */ 76 private final Map<String, ReportLog> mReportLogs = new HashMap<String, ReportLog>(); 77 78 /** Map from test name to {@link TestResultHistory}. */ 79 private final Map<String, TestResultHistoryCollection> mHistories = new HashMap<>(); 80 81 private final LayoutInflater mLayoutInflater; 82 83 /** {@link ListView} row that is either a test category header or a test. */ 84 public static class TestListItem { 85 86 /** Title shown in the {@link ListView}. */ 87 final String title; 88 89 /** Test name with class and test ID to uniquely identify the test. Null for categories. */ 90 final String testName; 91 92 /** Intent used to launch the activity from the list. Null for categories. */ 93 final Intent intent; 94 95 /** Features necessary to run this test. */ 96 final String[] requiredFeatures; 97 98 /** Configs necessary to run this test. */ 99 final String[] requiredConfigs; 100 101 /** Features such that, if any present, the test gets excluded from being shown. */ 102 final String[] excludedFeatures; 103 104 /** If any of of the features are present the test is meaningful to run. */ 105 final String[] applicableFeatures; 106 newTest(Context context, int titleResId, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures)107 public static TestListItem newTest(Context context, int titleResId, String testName, 108 Intent intent, String[] requiredFeatures, String[] excludedFeatures, 109 String[] applicableFeatures) { 110 return newTest(context.getString(titleResId), testName, intent, requiredFeatures, 111 excludedFeatures, applicableFeatures); 112 } 113 newTest(Context context, int titleResId, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures)114 public static TestListItem newTest(Context context, int titleResId, String testName, 115 Intent intent, String[] requiredFeatures, String[] excludedFeatures) { 116 return newTest(context.getString(titleResId), testName, intent, requiredFeatures, 117 excludedFeatures, null); 118 } 119 newTest(Context context, int titleResId, String testName, Intent intent, String[] requiredFeatures)120 public static TestListItem newTest(Context context, int titleResId, String testName, 121 Intent intent, String[] requiredFeatures) { 122 return newTest(context.getString(titleResId), testName, intent, requiredFeatures, null, 123 null); 124 } 125 newTest(String title, String testName, Intent intent, String[] requiredFeatures, String[] requiredConfigs, String[] excludedFeatures, String[] applicableFeatures)126 public static TestListItem newTest(String title, String testName, Intent intent, 127 String[] requiredFeatures, String[] requiredConfigs, String[] excludedFeatures, 128 String[] applicableFeatures) { 129 return new TestListItem(title, testName, intent, requiredFeatures, requiredConfigs, 130 excludedFeatures, applicableFeatures); 131 } 132 newTest(String title, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures)133 public static TestListItem newTest(String title, String testName, Intent intent, 134 String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures) { 135 return new TestListItem(title, testName, intent, requiredFeatures, null, excludedFeatures, 136 applicableFeatures); 137 } 138 newTest(String title, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures)139 public static TestListItem newTest(String title, String testName, Intent intent, 140 String[] requiredFeatures, String[] excludedFeatures) { 141 return new TestListItem(title, testName, intent, requiredFeatures, null, excludedFeatures, 142 null); 143 } 144 newTest(String title, String testName, Intent intent, String[] requiredFeatures)145 public static TestListItem newTest(String title, String testName, Intent intent, 146 String[] requiredFeatures) { 147 return new TestListItem(title, testName, intent, requiredFeatures, null, null, null); 148 } 149 newCategory(Context context, int titleResId)150 public static TestListItem newCategory(Context context, int titleResId) { 151 return newCategory(context.getString(titleResId)); 152 } 153 newCategory(String title)154 public static TestListItem newCategory(String title) { 155 return new TestListItem(title, null, null, null, null, null, null); 156 } 157 TestListItem(String title, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures)158 protected TestListItem(String title, String testName, Intent intent, 159 String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures) { 160 this(title, testName, intent, requiredFeatures, null, excludedFeatures, applicableFeatures); 161 } 162 TestListItem(String title, String testName, Intent intent, String[] requiredFeatures, String[] requiredConfigs, String[] excludedFeatures, String[] applicableFeatures)163 protected TestListItem(String title, String testName, Intent intent, 164 String[] requiredFeatures, String[] requiredConfigs, String[] excludedFeatures, 165 String[] applicableFeatures) { 166 this.title = title; 167 this.testName = testName; 168 this.intent = intent; 169 this.requiredFeatures = requiredFeatures; 170 this.requiredConfigs = requiredConfigs; 171 this.excludedFeatures = excludedFeatures; 172 this.applicableFeatures = applicableFeatures; 173 } 174 isTest()175 boolean isTest() { 176 return intent != null; 177 } 178 } 179 TestListAdapter(Context context)180 public TestListAdapter(Context context) { 181 this.mContext = context; 182 this.mLayoutInflater = 183 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 184 185 TestResultContentObserver observer = new TestResultContentObserver(); 186 ContentResolver resolver = context.getContentResolver(); 187 resolver.registerContentObserver(TestResultsProvider.getResultContentUri(context), true, observer); 188 } 189 loadTestResults()190 public void loadTestResults() { 191 new RefreshTestResultsTask().execute(); 192 } 193 clearTestResults()194 public void clearTestResults() { 195 new ClearTestResultsTask().execute(); 196 } 197 setTestResult(TestResult testResult)198 public void setTestResult(TestResult testResult) { 199 String name = testResult.getName(); 200 201 // Append existing history 202 TestResultHistoryCollection histories = testResult.getHistoryCollection(); 203 histories.merge(null, mHistories.get(name)); 204 205 new SetTestResultTask(name, testResult.getResult(), 206 testResult.getDetails(), testResult.getReportLog(), histories).execute(); 207 } 208 209 class RefreshTestResultsTask extends AsyncTask<Void, Void, RefreshResult> { 210 @Override doInBackground(Void... params)211 protected RefreshResult doInBackground(Void... params) { 212 List<TestListItem> rows = getRows(); 213 return getRefreshResults(rows); 214 } 215 216 @Override onPostExecute(RefreshResult result)217 protected void onPostExecute(RefreshResult result) { 218 super.onPostExecute(result); 219 mRows.clear(); 220 mRows.addAll(result.mItems); 221 mTestResults.clear(); 222 mTestResults.putAll(result.mResults); 223 mTestDetails.clear(); 224 mTestDetails.putAll(result.mDetails); 225 mReportLogs.clear(); 226 mReportLogs.putAll(result.mReportLogs); 227 mHistories.clear(); 228 mHistories.putAll(result.mHistories); 229 notifyDataSetChanged(); 230 } 231 } 232 233 static class RefreshResult { 234 List<TestListItem> mItems; 235 Map<String, Integer> mResults; 236 Map<String, String> mDetails; 237 Map<String, ReportLog> mReportLogs; 238 Map<String, TestResultHistoryCollection> mHistories; 239 RefreshResult( List<TestListItem> items, Map<String, Integer> results, Map<String, String> details, Map<String, ReportLog> reportLogs, Map<String, TestResultHistoryCollection> histories)240 RefreshResult( 241 List<TestListItem> items, 242 Map<String, Integer> results, 243 Map<String, String> details, 244 Map<String, ReportLog> reportLogs, 245 Map<String, TestResultHistoryCollection> histories) { 246 mItems = items; 247 mResults = results; 248 mDetails = details; 249 mReportLogs = reportLogs; 250 mHistories = histories; 251 } 252 } 253 getRows()254 protected abstract List<TestListItem> getRows(); 255 256 static final String[] REFRESH_PROJECTION = { 257 TestResultsProvider._ID, 258 TestResultsProvider.COLUMN_TEST_NAME, 259 TestResultsProvider.COLUMN_TEST_RESULT, 260 TestResultsProvider.COLUMN_TEST_DETAILS, 261 TestResultsProvider.COLUMN_TEST_METRICS, 262 TestResultsProvider.COLUMN_TEST_RESULT_HISTORY, 263 }; 264 getRefreshResults(List<TestListItem> items)265 RefreshResult getRefreshResults(List<TestListItem> items) { 266 Map<String, Integer> results = new HashMap<String, Integer>(); 267 Map<String, String> details = new HashMap<String, String>(); 268 Map<String, ReportLog> reportLogs = new HashMap<String, ReportLog>(); 269 Map<String, TestResultHistoryCollection> histories = new HashMap<>(); 270 ContentResolver resolver = mContext.getContentResolver(); 271 Cursor cursor = null; 272 try { 273 cursor = resolver.query(TestResultsProvider.getResultContentUri(mContext), REFRESH_PROJECTION, 274 null, null, null); 275 if (cursor.moveToFirst()) { 276 do { 277 String testName = cursor.getString(1); 278 int testResult = cursor.getInt(2); 279 String testDetails = cursor.getString(3); 280 ReportLog reportLog = (ReportLog) deserialize(cursor.getBlob(4)); 281 TestResultHistoryCollection historyCollection = 282 (TestResultHistoryCollection) deserialize(cursor.getBlob(5)); 283 results.put(testName, testResult); 284 details.put(testName, testDetails); 285 reportLogs.put(testName, reportLog); 286 histories.put(testName, historyCollection); 287 } while (cursor.moveToNext()); 288 } 289 } finally { 290 if (cursor != null) { 291 cursor.close(); 292 } 293 } 294 return new RefreshResult(items, results, details, reportLogs, histories); 295 } 296 297 class ClearTestResultsTask extends AsyncTask<Void, Void, Void> { 298 299 @Override doInBackground(Void... params)300 protected Void doInBackground(Void... params) { 301 ContentResolver resolver = mContext.getContentResolver(); 302 resolver.delete(TestResultsProvider.getResultContentUri(mContext), "1", null); 303 return null; 304 } 305 } 306 307 class SetTestResultTask extends AsyncTask<Void, Void, Void> { 308 309 private final String mTestName; 310 private final int mResult; 311 private final String mDetails; 312 private final ReportLog mReportLog; 313 private final TestResultHistoryCollection mHistoryCollection; 314 SetTestResultTask( String testName, int result, String details, ReportLog reportLog, TestResultHistoryCollection historyCollection)315 SetTestResultTask( 316 String testName, 317 int result, 318 String details, 319 ReportLog reportLog, 320 TestResultHistoryCollection historyCollection) { 321 mTestName = testName; 322 mResult = result; 323 mDetails = details; 324 mReportLog = reportLog; 325 mHistoryCollection = historyCollection; 326 } 327 328 @Override doInBackground(Void... params)329 protected Void doInBackground(Void... params) { 330 TestResultsProvider.setTestResult( 331 mContext, mTestName, mResult, mDetails, mReportLog, mHistoryCollection); 332 return null; 333 } 334 } 335 336 class TestResultContentObserver extends ContentObserver { 337 TestResultContentObserver()338 public TestResultContentObserver() { 339 super(new Handler()); 340 } 341 342 @Override onChange(boolean selfChange)343 public void onChange(boolean selfChange) { 344 super.onChange(selfChange); 345 loadTestResults(); 346 } 347 } 348 349 @Override areAllItemsEnabled()350 public boolean areAllItemsEnabled() { 351 // Section headers for test categories are not clickable. 352 return false; 353 } 354 355 @Override isEnabled(int position)356 public boolean isEnabled(int position) { 357 return getItem(position).isTest(); 358 } 359 360 @Override getItemViewType(int position)361 public int getItemViewType(int position) { 362 return getItem(position).isTest() ? TEST_VIEW_TYPE : CATEGORY_HEADER_VIEW_TYPE; 363 } 364 365 @Override getViewTypeCount()366 public int getViewTypeCount() { 367 return 2; 368 } 369 370 @Override getCount()371 public int getCount() { 372 return mRows.size(); 373 } 374 375 @Override getItem(int position)376 public TestListItem getItem(int position) { 377 return mRows.get(position); 378 } 379 380 @Override getItemId(int position)381 public long getItemId(int position) { 382 return position; 383 } 384 getTestResult(int position)385 public int getTestResult(int position) { 386 TestListItem item = getItem(position); 387 return mTestResults.containsKey(item.testName) 388 ? mTestResults.get(item.testName) 389 : TestResult.TEST_RESULT_NOT_EXECUTED; 390 } 391 getTestDetails(int position)392 public String getTestDetails(int position) { 393 TestListItem item = getItem(position); 394 return mTestDetails.containsKey(item.testName) 395 ? mTestDetails.get(item.testName) 396 : null; 397 } 398 getReportLog(int position)399 public ReportLog getReportLog(int position) { 400 TestListItem item = getItem(position); 401 return mReportLogs.containsKey(item.testName) 402 ? mReportLogs.get(item.testName) 403 : null; 404 } 405 406 /** 407 * Get test result histories. 408 * 409 * @param position The position of test. 410 * @return A {@link TestResultHistoryCollection} object containing test result histories of tests. 411 */ getHistoryCollection(int position)412 public TestResultHistoryCollection getHistoryCollection(int position) { 413 TestListItem item = getItem(position); 414 return mHistories.containsKey(item.testName) 415 ? mHistories.get(item.testName) 416 : null; 417 } 418 allTestsPassed()419 public boolean allTestsPassed() { 420 for (TestListItem item : mRows) { 421 if (item.isTest() && (!mTestResults.containsKey(item.testName) 422 || (mTestResults.get(item.testName) != TestResult.TEST_RESULT_PASSED))) { 423 return false; 424 } 425 } 426 return true; 427 } 428 429 @Override getView(int position, View convertView, ViewGroup parent)430 public View getView(int position, View convertView, ViewGroup parent) { 431 TextView textView; 432 if (convertView == null) { 433 int layout = getLayout(position); 434 textView = (TextView) mLayoutInflater.inflate(layout, parent, false); 435 } else { 436 textView = (TextView) convertView; 437 } 438 439 TestListItem item = getItem(position); 440 textView.setText(item.title); 441 textView.setPadding(PADDING, 0, PADDING, 0); 442 textView.setCompoundDrawablePadding(PADDING); 443 444 if (item.isTest()) { 445 int testResult = getTestResult(position); 446 int backgroundResource = 0; 447 int iconResource = 0; 448 449 /** TODO: Remove fs_ prefix from feature icons since they are used here too. */ 450 switch (testResult) { 451 case TestResult.TEST_RESULT_PASSED: 452 backgroundResource = R.drawable.test_pass_gradient; 453 iconResource = R.drawable.fs_good; 454 break; 455 456 case TestResult.TEST_RESULT_FAILED: 457 backgroundResource = R.drawable.test_fail_gradient; 458 iconResource = R.drawable.fs_error; 459 break; 460 461 case TestResult.TEST_RESULT_NOT_EXECUTED: 462 break; 463 464 default: 465 throw new IllegalArgumentException("Unknown test result: " + testResult); 466 } 467 468 textView.setBackgroundResource(backgroundResource); 469 textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, iconResource, 0); 470 } 471 472 return textView; 473 } 474 getLayout(int position)475 private int getLayout(int position) { 476 int viewType = getItemViewType(position); 477 switch (viewType) { 478 case CATEGORY_HEADER_VIEW_TYPE: 479 return R.layout.test_category_row; 480 case TEST_VIEW_TYPE: 481 return android.R.layout.simple_list_item_1; 482 default: 483 throw new IllegalArgumentException("Illegal view type: " + viewType); 484 485 } 486 } 487 deserialize(byte[] bytes)488 public static Object deserialize(byte[] bytes) { 489 if (bytes == null || bytes.length == 0) { 490 return null; 491 } 492 ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes); 493 ObjectInputStream objectInput = null; 494 try { 495 objectInput = new ObjectInputStream(byteStream); 496 return objectInput.readObject(); 497 } catch (IOException e) { 498 return null; 499 } catch (ClassNotFoundException e) { 500 return null; 501 } finally { 502 try { 503 if (objectInput != null) { 504 objectInput.close(); 505 } 506 byteStream.close(); 507 } catch (IOException e) { 508 // Ignore close exception. 509 } 510 } 511 } 512 } 513