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 /** Features such that, if any present, the test gets excluded from being shown. */ 87 final String[] excludedFeatures; 88 89 /** If any of of the features are present the test is meaningful to run. */ 90 final String[] applicableFeatures; 91 newTest(Context context, int titleResId, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures)92 public static TestListItem newTest(Context context, int titleResId, String testName, 93 Intent intent, String[] requiredFeatures, String[] excludedFeatures, 94 String[] applicableFeatures) { 95 return newTest(context.getString(titleResId), testName, intent, requiredFeatures, 96 excludedFeatures, applicableFeatures); 97 } 98 newTest(Context context, int titleResId, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures)99 public static TestListItem newTest(Context context, int titleResId, String testName, 100 Intent intent, String[] requiredFeatures, String[] excludedFeatures) { 101 return newTest(context.getString(titleResId), testName, intent, requiredFeatures, 102 excludedFeatures, null); 103 } 104 newTest(Context context, int titleResId, String testName, Intent intent, String[] requiredFeatures)105 public static TestListItem newTest(Context context, int titleResId, String testName, 106 Intent intent, String[] requiredFeatures) { 107 return newTest(context.getString(titleResId), testName, intent, requiredFeatures, null, 108 null); 109 } 110 newTest(String title, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures)111 public static TestListItem newTest(String title, String testName, Intent intent, 112 String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures) { 113 return new TestListItem(title, testName, intent, requiredFeatures, excludedFeatures, 114 applicableFeatures); 115 } 116 newTest(String title, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures)117 public static TestListItem newTest(String title, String testName, Intent intent, 118 String[] requiredFeatures, String[] excludedFeatures) { 119 return new TestListItem(title, testName, intent, requiredFeatures, excludedFeatures, 120 null); 121 } 122 newTest(String title, String testName, Intent intent, String[] requiredFeatures)123 public static TestListItem newTest(String title, String testName, Intent intent, 124 String[] requiredFeatures) { 125 return new TestListItem(title, testName, intent, requiredFeatures, null, null); 126 } 127 newCategory(Context context, int titleResId)128 public static TestListItem newCategory(Context context, int titleResId) { 129 return newCategory(context.getString(titleResId)); 130 } 131 newCategory(String title)132 public static TestListItem newCategory(String title) { 133 return new TestListItem(title, null, null, null, null, null); 134 } 135 TestListItem(String title, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures)136 private TestListItem(String title, String testName, Intent intent, 137 String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures) { 138 this.title = title; 139 this.testName = testName; 140 this.intent = intent; 141 this.requiredFeatures = requiredFeatures; 142 this.excludedFeatures = excludedFeatures; 143 this.applicableFeatures = applicableFeatures; 144 } 145 isTest()146 boolean isTest() { 147 return intent != null; 148 } 149 } 150 TestListAdapter(Context context)151 public TestListAdapter(Context context) { 152 this.mContext = context; 153 this.mLayoutInflater = 154 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 155 156 TestResultContentObserver observer = new TestResultContentObserver(); 157 ContentResolver resolver = context.getContentResolver(); 158 resolver.registerContentObserver(TestResultsProvider.RESULTS_CONTENT_URI, true, observer); 159 } 160 loadTestResults()161 public void loadTestResults() { 162 new RefreshTestResultsTask().execute(); 163 } 164 clearTestResults()165 public void clearTestResults() { 166 new ClearTestResultsTask().execute(); 167 } 168 setTestResult(TestResult testResult)169 public void setTestResult(TestResult testResult) { 170 new SetTestResultTask(testResult.getName(), testResult.getResult(), 171 testResult.getDetails()).execute(); 172 } 173 174 class RefreshTestResultsTask extends AsyncTask<Void, Void, RefreshResult> { 175 @Override doInBackground(Void... params)176 protected RefreshResult doInBackground(Void... params) { 177 List<TestListItem> rows = getRows(); 178 return getRefreshResults(rows); 179 } 180 181 @Override onPostExecute(RefreshResult result)182 protected void onPostExecute(RefreshResult result) { 183 super.onPostExecute(result); 184 mRows.clear(); 185 mRows.addAll(result.mItems); 186 mTestResults.clear(); 187 mTestResults.putAll(result.mResults); 188 mTestDetails.clear(); 189 mTestDetails.putAll(result.mDetails); 190 notifyDataSetChanged(); 191 } 192 } 193 194 static class RefreshResult { 195 List<TestListItem> mItems; 196 Map<String, Integer> mResults; 197 Map<String, String> mDetails; 198 RefreshResult(List<TestListItem> items, Map<String, Integer> results, Map<String, String> details)199 RefreshResult(List<TestListItem> items, Map<String, Integer> results, 200 Map<String, String> details) { 201 mItems = items; 202 mResults = results; 203 mDetails = details; 204 } 205 } 206 getRows()207 protected abstract List<TestListItem> getRows(); 208 209 static final String[] REFRESH_PROJECTION = { 210 TestResultsProvider._ID, 211 TestResultsProvider.COLUMN_TEST_NAME, 212 TestResultsProvider.COLUMN_TEST_RESULT, 213 TestResultsProvider.COLUMN_TEST_DETAILS, 214 }; 215 getRefreshResults(List<TestListItem> items)216 RefreshResult getRefreshResults(List<TestListItem> items) { 217 Map<String, Integer> results = new HashMap<String, Integer>(); 218 Map<String, String> details = new HashMap<String, String>(); 219 ContentResolver resolver = mContext.getContentResolver(); 220 Cursor cursor = null; 221 try { 222 cursor = resolver.query(TestResultsProvider.RESULTS_CONTENT_URI, REFRESH_PROJECTION, 223 null, null, null); 224 if (cursor.moveToFirst()) { 225 do { 226 String testName = cursor.getString(1); 227 int testResult = cursor.getInt(2); 228 String testDetails = cursor.getString(3); 229 results.put(testName, testResult); 230 details.put(testName, testDetails); 231 } while (cursor.moveToNext()); 232 } 233 } finally { 234 if (cursor != null) { 235 cursor.close(); 236 } 237 } 238 return new RefreshResult(items, results, details); 239 } 240 241 class ClearTestResultsTask extends AsyncTask<Void, Void, Void> { 242 243 @Override doInBackground(Void... params)244 protected Void doInBackground(Void... params) { 245 ContentResolver resolver = mContext.getContentResolver(); 246 resolver.delete(TestResultsProvider.RESULTS_CONTENT_URI, "1", null); 247 return null; 248 } 249 } 250 251 class SetTestResultTask extends AsyncTask<Void, Void, Void> { 252 253 private final String mTestName; 254 255 private final int mResult; 256 257 private final String mDetails; 258 SetTestResultTask(String testName, int result, String details)259 SetTestResultTask(String testName, int result, String details) { 260 mTestName = testName; 261 mResult = result; 262 mDetails = details; 263 } 264 265 @Override doInBackground(Void... params)266 protected Void doInBackground(Void... params) { 267 TestResultsProvider.setTestResult(mContext, mTestName, mResult, mDetails); 268 return null; 269 } 270 } 271 272 class TestResultContentObserver extends ContentObserver { 273 TestResultContentObserver()274 public TestResultContentObserver() { 275 super(new Handler()); 276 } 277 278 @Override onChange(boolean selfChange)279 public void onChange(boolean selfChange) { 280 super.onChange(selfChange); 281 loadTestResults(); 282 } 283 } 284 285 @Override areAllItemsEnabled()286 public boolean areAllItemsEnabled() { 287 // Section headers for test categories are not clickable. 288 return false; 289 } 290 291 @Override isEnabled(int position)292 public boolean isEnabled(int position) { 293 return getItem(position).isTest(); 294 } 295 296 @Override getItemViewType(int position)297 public int getItemViewType(int position) { 298 return getItem(position).isTest() ? TEST_VIEW_TYPE : CATEGORY_HEADER_VIEW_TYPE; 299 } 300 301 @Override getViewTypeCount()302 public int getViewTypeCount() { 303 return 2; 304 } 305 306 @Override getCount()307 public int getCount() { 308 return mRows.size(); 309 } 310 311 @Override getItem(int position)312 public TestListItem getItem(int position) { 313 return mRows.get(position); 314 } 315 316 @Override getItemId(int position)317 public long getItemId(int position) { 318 return position; 319 } 320 getTestResult(int position)321 public int getTestResult(int position) { 322 TestListItem item = getItem(position); 323 return mTestResults.containsKey(item.testName) 324 ? mTestResults.get(item.testName) 325 : TestResult.TEST_RESULT_NOT_EXECUTED; 326 } 327 getTestDetails(int position)328 public String getTestDetails(int position) { 329 TestListItem item = getItem(position); 330 return mTestDetails.containsKey(item.testName) 331 ? mTestDetails.get(item.testName) 332 : null; 333 } 334 allTestsPassed()335 public boolean allTestsPassed() { 336 for (TestListItem item : mRows) { 337 if (item.isTest() && (!mTestResults.containsKey(item.testName) 338 || (mTestResults.get(item.testName) != TestResult.TEST_RESULT_PASSED))) { 339 return false; 340 } 341 } 342 return true; 343 } 344 345 @Override getView(int position, View convertView, ViewGroup parent)346 public View getView(int position, View convertView, ViewGroup parent) { 347 TextView textView; 348 if (convertView == null) { 349 int layout = getLayout(position); 350 textView = (TextView) mLayoutInflater.inflate(layout, parent, false); 351 } else { 352 textView = (TextView) convertView; 353 } 354 355 TestListItem item = getItem(position); 356 textView.setText(item.title); 357 textView.setPadding(PADDING, 0, PADDING, 0); 358 textView.setCompoundDrawablePadding(PADDING); 359 360 if (item.isTest()) { 361 int testResult = getTestResult(position); 362 int backgroundResource = 0; 363 int iconResource = 0; 364 365 /** TODO: Remove fs_ prefix from feature icons since they are used here too. */ 366 switch (testResult) { 367 case TestResult.TEST_RESULT_PASSED: 368 backgroundResource = R.drawable.test_pass_gradient; 369 iconResource = R.drawable.fs_good; 370 break; 371 372 case TestResult.TEST_RESULT_FAILED: 373 backgroundResource = R.drawable.test_fail_gradient; 374 iconResource = R.drawable.fs_error; 375 break; 376 377 case TestResult.TEST_RESULT_NOT_EXECUTED: 378 break; 379 380 default: 381 throw new IllegalArgumentException("Unknown test result: " + testResult); 382 } 383 384 textView.setBackgroundResource(backgroundResource); 385 textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, iconResource, 0); 386 } 387 388 return textView; 389 } 390 getLayout(int position)391 private int getLayout(int position) { 392 int viewType = getItemViewType(position); 393 switch (viewType) { 394 case CATEGORY_HEADER_VIEW_TYPE: 395 return R.layout.test_category_row; 396 case TEST_VIEW_TYPE: 397 return android.R.layout.simple_list_item_1; 398 default: 399 throw new IllegalArgumentException("Illegal view type: " + viewType); 400 401 } 402 } 403 } 404