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