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 static com.android.cts.verifier.ReportExporter.LOGS_DIRECTORY; 20 import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode; 21 import static com.android.cts.verifier.TestListActivity.sInitialLaunch; 22 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.database.ContentObserver; 27 import android.database.Cursor; 28 import android.hardware.devicestate.DeviceStateManager; 29 import android.os.AsyncTask; 30 import android.os.Environment; 31 import android.os.Handler; 32 import android.view.LayoutInflater; 33 import android.view.View; 34 import android.view.ViewGroup; 35 import android.widget.BaseAdapter; 36 import android.widget.ListView; 37 import android.widget.TextView; 38 39 import com.android.compatibility.common.util.ReportLog; 40 import com.android.compatibility.common.util.TestScreenshotsMetadata; 41 import com.android.cts.verifier.TestListActivity.DisplayMode; 42 43 import java.io.ByteArrayInputStream; 44 import java.io.File; 45 import java.io.IOException; 46 import java.io.ObjectInputStream; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.HashMap; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Set; 53 import java.util.concurrent.atomic.AtomicBoolean; 54 import java.util.stream.Collectors; 55 56 /** 57 * {@link BaseAdapter} that handles loading, refreshing, and setting test results. What tests are 58 * shown can be customized by overriding {@link #getRows()}. See {@link ArrayTestListAdapter} and 59 * {@link ManifestTestListAdapter} for examples. 60 */ 61 public abstract class TestListAdapter extends BaseAdapter { 62 63 /** Activities implementing {@link Intent#ACTION_MAIN} and this will appear in the list. */ 64 public static final String CATEGORY_MANUAL_TEST = "android.cts.intent.category.MANUAL_TEST"; 65 66 /** View type for a category of tests like "Sensors" or "Features" */ 67 private static final int CATEGORY_HEADER_VIEW_TYPE = 0; 68 69 /** View type for an actual test like the Accelerometer test. */ 70 private static final int TEST_VIEW_TYPE = 1; 71 72 /** Padding around the text views and icons. */ 73 private static final int PADDING = 10; 74 75 private final Context mContext; 76 77 /** Immutable data of tests like the test's title and launch intent. */ 78 private final List<TestListItem> mRows = new ArrayList<TestListItem>(); 79 80 /** Mutable test results that will change as each test activity finishes. */ 81 private final Map<String, Integer> mTestResults = new HashMap<String, Integer>(); 82 83 /** Map from test name to test details. */ 84 private final Map<String, String> mTestDetails = new HashMap<String, String>(); 85 86 /** Map from test name to {@link ReportLog}. */ 87 private final Map<String, ReportLog> mReportLogs = new HashMap<String, ReportLog>(); 88 89 /** Map from test name to {@link TestResultHistoryCollection}. */ 90 private final Map<String, TestResultHistoryCollection> mHistories = new HashMap<>(); 91 92 /** Map from test name to {@link TestScreenshotsMetadata}. */ 93 private final Map<String, TestScreenshotsMetadata> mScreenshotsMetadata = new HashMap<>(); 94 95 /** Flag to identify whether the mHistories has been loaded. */ 96 private final AtomicBoolean mHasLoadedResultHistory = new AtomicBoolean(false); 97 98 private final LayoutInflater mLayoutInflater; 99 100 /** 101 * Map from display mode to the list of {@link TestListItem}. Records the TestListItem from main 102 * view only, including unfolded mode and folded mode respectively. 103 */ 104 protected Map<String, List<TestListItem>> mDisplayModesTests = new HashMap<>(); 105 106 /** {@link ListView} row that is either a test category header or a test. */ 107 public static class TestListItem { 108 109 /** Title shown in the {@link ListView}. */ 110 public final String title; 111 112 /** Test name with class and test ID to uniquely identify the test. Null for categories. */ 113 public String testName; 114 115 /** Intent used to launch the activity from the list. Null for categories. */ 116 public final Intent intent; 117 118 /** Features necessary to run this test. */ 119 public final String[] requiredFeatures; 120 121 /** Configs necessary to run this test. */ 122 public final String[] requiredConfigs; 123 124 /** Intent actions necessary to run this test. */ 125 public final String[] requiredActions; 126 127 /** Features such that, if any present, the test gets excluded from being shown. */ 128 public final String[] excludedFeatures; 129 130 /** User "types" that, if any present, the test gets excluded from being shown. */ 131 public final String[] excludedUserTypes; 132 133 /** If any of of the features are present the test is meaningful to run. */ 134 public final String[] applicableFeatures; 135 136 /** Configs display mode to run this test. */ 137 public final String displayMode; 138 139 /** Configs test pass mode to record the test result. */ 140 public final boolean passInEitherMode; 141 142 // TODO: refactor to use a Builder approach instead 143 144 /** 145 * Creates a new test item with given required, excluded and applicable features, the 146 * context and the resource ID of the title. 147 */ newTest( Context context, int titleResId, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures)148 public static TestListItem newTest( 149 Context context, 150 int titleResId, 151 String testName, 152 Intent intent, 153 String[] requiredFeatures, 154 String[] excludedFeatures, 155 String[] applicableFeatures) { 156 return newTest( 157 context.getString(titleResId), 158 testName, 159 intent, 160 requiredFeatures, 161 excludedFeatures, 162 applicableFeatures); 163 } 164 165 /** 166 * Creates a new test item with given required and excluded features, the context and the 167 * resource ID of the title. 168 */ newTest( Context context, int titleResId, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures)169 public static TestListItem newTest( 170 Context context, 171 int titleResId, 172 String testName, 173 Intent intent, 174 String[] requiredFeatures, 175 String[] excludedFeatures) { 176 return newTest( 177 context.getString(titleResId), 178 testName, 179 intent, 180 requiredFeatures, 181 excludedFeatures, 182 /* applicableFeatures= */ null); 183 } 184 185 /** 186 * Creates a new test item with given required features, the context and the resource ID of 187 * the title. 188 */ newTest( Context context, int titleResId, String testName, Intent intent, String[] requiredFeatures)189 public static TestListItem newTest( 190 Context context, 191 int titleResId, 192 String testName, 193 Intent intent, 194 String[] requiredFeatures) { 195 return newTest( 196 context.getString(titleResId), 197 testName, 198 intent, 199 requiredFeatures, 200 /* excludedFeatures= */ null, 201 /* applicableFeatures= */ null); 202 } 203 204 /** 205 * Creates a new test item with given display mode, the required, excluded, applicable 206 * features and required configureations and actions. 207 */ newTest( String title, String testName, Intent intent, String[] requiredFeatures, String[] requiredConfigs, String[] requiredActions, String[] excludedFeatures, String[] applicableFeatures, String[] excludedUserTypes, String displayMode, boolean passInEitherMode)208 public static TestListItem newTest( 209 String title, 210 String testName, 211 Intent intent, 212 String[] requiredFeatures, 213 String[] requiredConfigs, 214 String[] requiredActions, 215 String[] excludedFeatures, 216 String[] applicableFeatures, 217 String[] excludedUserTypes, 218 String displayMode, 219 boolean passInEitherMode) { 220 return new TestListItem( 221 title, 222 testName, 223 intent, 224 requiredFeatures, 225 requiredConfigs, 226 requiredActions, 227 excludedFeatures, 228 applicableFeatures, 229 excludedUserTypes, 230 displayMode, 231 passInEitherMode); 232 } 233 234 /** 235 * Creates a new test item with given display mode, the required, excluded, applicable 236 * features, required configurations and actions and test pass mode. 237 */ newTest( String title, String testName, Intent intent, String[] requiredFeatures, String[] requiredConfigs, String[] requiredActions, String[] excludedFeatures, String[] applicableFeatures, String[] excludedUserTypes, String displayMode)238 public static TestListItem newTest( 239 String title, 240 String testName, 241 Intent intent, 242 String[] requiredFeatures, 243 String[] requiredConfigs, 244 String[] requiredActions, 245 String[] excludedFeatures, 246 String[] applicableFeatures, 247 String[] excludedUserTypes, 248 String displayMode) { 249 return new TestListItem( 250 title, 251 testName, 252 intent, 253 requiredFeatures, 254 requiredConfigs, 255 requiredActions, 256 excludedFeatures, 257 applicableFeatures, 258 excludedUserTypes, 259 displayMode, 260 /* passInEitherMode= */ false); 261 } 262 263 /** 264 * Creates a new test item with given required, excluded, applicable features and required 265 * configureations. 266 */ newTest( String title, String testName, Intent intent, String[] requiredFeatures, String[] requiredConfigs, String[] excludedFeatures, String[] applicableFeatures)267 public static TestListItem newTest( 268 String title, 269 String testName, 270 Intent intent, 271 String[] requiredFeatures, 272 String[] requiredConfigs, 273 String[] excludedFeatures, 274 String[] applicableFeatures) { 275 return new TestListItem( 276 title, 277 testName, 278 intent, 279 requiredFeatures, 280 requiredConfigs, 281 /* requiredActions= */ null, 282 excludedFeatures, 283 applicableFeatures, 284 /* excludedUserTypes= */ null, 285 /* displayMode= */ null, 286 /* passInEitherMode= */ false); 287 } 288 289 /** Creates a new test item with given required, excluded and applicable features. */ newTest( String title, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures)290 public static TestListItem newTest( 291 String title, 292 String testName, 293 Intent intent, 294 String[] requiredFeatures, 295 String[] excludedFeatures, 296 String[] applicableFeatures) { 297 return new TestListItem( 298 title, 299 testName, 300 intent, 301 requiredFeatures, 302 /* requiredConfigs= */ null, 303 /* requiredActions= */ null, 304 excludedFeatures, 305 applicableFeatures, 306 /* excludedUserTypes= */ null, 307 /* displayMode= */ null, 308 /* passInEitherMode= */ false); 309 } 310 311 /** Creates a new test item with given required and excluded features. */ newTest( String title, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures)312 public static TestListItem newTest( 313 String title, 314 String testName, 315 Intent intent, 316 String[] requiredFeatures, 317 String[] excludedFeatures) { 318 return new TestListItem( 319 title, 320 testName, 321 intent, 322 requiredFeatures, 323 /* requiredConfigs= */ null, 324 /* requiredActions= */ null, 325 excludedFeatures, 326 /* applicableFeatures= */ null, 327 /* excludedUserTypes= */ null, 328 /* displayMode= */ null, 329 /* passInEitherMode= */ false); 330 } 331 332 /** Creates a new test item with given required features. */ newTest( String title, String testName, Intent intent, String[] requiredFeatures)333 public static TestListItem newTest( 334 String title, String testName, Intent intent, String[] requiredFeatures) { 335 return new TestListItem( 336 title, 337 testName, 338 intent, 339 requiredFeatures, 340 /* requiredConfigs= */ null, 341 /* requiredActions= */ null, 342 /* excludedFeatures= */ null, 343 /* applicableFeatures= */ null, 344 /* excludedUserTypes= */ null, 345 /* displayMode= */ null, 346 /* passInEitherMode= */ false); 347 } 348 newCategory(Context context, int titleResId)349 public static TestListItem newCategory(Context context, int titleResId) { 350 return newCategory(context.getString(titleResId)); 351 } 352 newCategory(String title)353 public static TestListItem newCategory(String title) { 354 return new TestListItem( 355 title, 356 /* testName= */ null, 357 /* intent= */ null, 358 /* requiredFeatures= */ null, 359 /* requiredConfigs= */ null, 360 /* requiredActions= */ null, 361 /* excludedFeatures= */ null, 362 /* applicableFeatures= */ null, 363 /* excludedUserTypes= */ null, 364 /* displayMode= */ null, 365 /* passInEitherMode= */ false); 366 } 367 TestListItem( String title, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures)368 protected TestListItem( 369 String title, 370 String testName, 371 Intent intent, 372 String[] requiredFeatures, 373 String[] excludedFeatures, 374 String[] applicableFeatures) { 375 this( 376 title, 377 testName, 378 intent, 379 requiredFeatures, 380 /* requiredConfigs= */ null, 381 /* requiredActions= */ null, 382 excludedFeatures, 383 applicableFeatures, 384 /* excludedUserTypes= */ null, 385 /* displayMode= */ null, 386 /* passInEitherMode= */ false); 387 } 388 TestListItem( String title, String testName, Intent intent, String[] requiredFeatures, String[] requiredConfigs, String[] requiredActions, String[] excludedFeatures, String[] applicableFeatures, String[] excludedUserTypes, String displayMode, boolean passInEitherMode)389 protected TestListItem( 390 String title, 391 String testName, 392 Intent intent, 393 String[] requiredFeatures, 394 String[] requiredConfigs, 395 String[] requiredActions, 396 String[] excludedFeatures, 397 String[] applicableFeatures, 398 String[] excludedUserTypes, 399 String displayMode, 400 boolean passInEitherMode) { 401 this.title = title; 402 if (!sInitialLaunch) { 403 testName = setTestNameSuffix(sCurrentDisplayMode, testName); 404 } 405 this.testName = testName; 406 this.intent = intent; 407 this.requiredActions = requiredActions; 408 this.requiredFeatures = requiredFeatures; 409 this.requiredConfigs = requiredConfigs; 410 this.excludedFeatures = excludedFeatures; 411 this.applicableFeatures = applicableFeatures; 412 this.excludedUserTypes = excludedUserTypes; 413 this.displayMode = displayMode; 414 this.passInEitherMode = passInEitherMode; 415 } 416 isTest()417 boolean isTest() { 418 return intent != null; 419 } 420 } 421 TestListAdapter(Context context)422 public TestListAdapter(Context context) { 423 this.mContext = context; 424 this.mLayoutInflater = 425 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 426 427 TestResultContentObserver observer = new TestResultContentObserver(); 428 ContentResolver resolver = context.getContentResolver(); 429 resolver.registerContentObserver( 430 TestResultsProvider.getResultContentUri(context), true, observer); 431 } 432 loadTestResults()433 public void loadTestResults() { 434 new RefreshTestResultsTask(false).execute(); 435 } 436 clearTestResults()437 public void clearTestResults() { 438 new ClearTestResultsTask().execute(); 439 } 440 setTestResult(TestResult testResult)441 public void setTestResult(TestResult testResult) { 442 String name = testResult.getName(); 443 444 // Append existing history 445 TestResultHistoryCollection histories = testResult.getHistoryCollection(); 446 histories.merge(null, mHistories.get(name)); 447 448 new SetTestResultTask( 449 name, 450 testResult.getResult(), 451 testResult.getDetails(), 452 testResult.getReportLog(), 453 histories, 454 mScreenshotsMetadata.get(name)) 455 .execute(); 456 } 457 458 class RefreshTestResultsTask extends AsyncTask<Void, Void, RefreshResult> { 459 460 private boolean mIsFromMainView; 461 RefreshTestResultsTask(boolean isFromMainView)462 RefreshTestResultsTask(boolean isFromMainView) { 463 mIsFromMainView = isFromMainView; 464 } 465 466 @Override doInBackground(Void... params)467 protected RefreshResult doInBackground(Void... params) { 468 List<TestListItem> rows = getRows(); 469 // When initial launch, needs to fetch tests in the unfolded/folded mode 470 // to be stored in mDisplayModesTests as the basis for the future switch. 471 if (sInitialLaunch) { 472 sInitialLaunch = false; 473 } 474 475 if (mIsFromMainView) { 476 rows = mDisplayModesTests.get(sCurrentDisplayMode); 477 } 478 479 return getRefreshResults(rows); 480 } 481 482 @Override onPostExecute(RefreshResult result)483 protected void onPostExecute(RefreshResult result) { 484 super.onPostExecute(result); 485 mRows.clear(); 486 mRows.addAll(result.mItems); 487 mTestResults.clear(); 488 mTestResults.putAll(result.mResults); 489 mTestDetails.clear(); 490 mTestDetails.putAll(result.mDetails); 491 mReportLogs.clear(); 492 mReportLogs.putAll(result.mReportLogs); 493 mHistories.clear(); 494 mHistories.putAll(result.mHistories); 495 mScreenshotsMetadata.clear(); 496 mScreenshotsMetadata.putAll(result.mScreenshotsMetadata); 497 mHasLoadedResultHistory.set(true); 498 notifyDataSetChanged(); 499 } 500 } 501 502 static class RefreshResult { 503 List<TestListItem> mItems; 504 Map<String, Integer> mResults; 505 Map<String, String> mDetails; 506 Map<String, ReportLog> mReportLogs; 507 Map<String, TestResultHistoryCollection> mHistories; 508 Map<String, TestScreenshotsMetadata> mScreenshotsMetadata; 509 RefreshResult( List<TestListItem> items, Map<String, Integer> results, Map<String, String> details, Map<String, ReportLog> reportLogs, Map<String, TestResultHistoryCollection> histories, Map<String, TestScreenshotsMetadata> screenshotsMetadata)510 RefreshResult( 511 List<TestListItem> items, 512 Map<String, Integer> results, 513 Map<String, String> details, 514 Map<String, ReportLog> reportLogs, 515 Map<String, TestResultHistoryCollection> histories, 516 Map<String, TestScreenshotsMetadata> screenshotsMetadata) { 517 mItems = items; 518 mResults = results; 519 mDetails = details; 520 mReportLogs = reportLogs; 521 mHistories = histories; 522 mScreenshotsMetadata = screenshotsMetadata; 523 } 524 } 525 getRows()526 protected abstract List<TestListItem> getRows(); 527 528 static final String[] REFRESH_PROJECTION = { 529 TestResultsProvider._ID, 530 TestResultsProvider.COLUMN_TEST_NAME, 531 TestResultsProvider.COLUMN_TEST_RESULT, 532 TestResultsProvider.COLUMN_TEST_DETAILS, 533 TestResultsProvider.COLUMN_TEST_METRICS, 534 TestResultsProvider.COLUMN_TEST_RESULT_HISTORY, 535 TestResultsProvider.COLUMN_TEST_SCREENSHOTS_METADATA, 536 }; 537 getRefreshResults(List<TestListItem> items)538 RefreshResult getRefreshResults(List<TestListItem> items) { 539 Map<String, Integer> results = new HashMap<String, Integer>(); 540 Map<String, String> details = new HashMap<String, String>(); 541 Map<String, ReportLog> reportLogs = new HashMap<String, ReportLog>(); 542 Map<String, TestResultHistoryCollection> histories = new HashMap<>(); 543 Map<String, TestScreenshotsMetadata> screenshotsMetadata = new HashMap<>(); 544 ContentResolver resolver = mContext.getContentResolver(); 545 Cursor cursor = null; 546 try { 547 cursor = 548 resolver.query( 549 TestResultsProvider.getResultContentUri(mContext), 550 REFRESH_PROJECTION, 551 null, 552 null, 553 null); 554 if (cursor.moveToFirst()) { 555 do { 556 String testName = cursor.getString(1); 557 int testResult = cursor.getInt(2); 558 String testDetails = cursor.getString(3); 559 ReportLog reportLog = (ReportLog) deserialize(cursor.getBlob(4)); 560 TestResultHistoryCollection historyCollection = 561 (TestResultHistoryCollection) deserialize(cursor.getBlob(5)); 562 TestScreenshotsMetadata screenshots = 563 (TestScreenshotsMetadata) deserialize(cursor.getBlob(6)); 564 results.put(testName, testResult); 565 details.put(testName, testDetails); 566 reportLogs.put(testName, reportLog); 567 histories.put(testName, historyCollection); 568 screenshotsMetadata.put(testName, screenshots); 569 } while (cursor.moveToNext()); 570 } 571 } finally { 572 if (cursor != null) { 573 cursor.close(); 574 } 575 } 576 return new RefreshResult( 577 items, results, details, reportLogs, histories, screenshotsMetadata); 578 } 579 580 class ClearTestResultsTask extends AsyncTask<Void, Void, Void> { 581 deleteDirectory(File file)582 private void deleteDirectory(File file) { 583 for (File subfile : file.listFiles()) { 584 if (subfile.isDirectory()) { 585 deleteDirectory(subfile); 586 } 587 subfile.delete(); 588 } 589 } 590 591 @Override doInBackground(Void... params)592 protected Void doInBackground(Void... params) { 593 ContentResolver resolver = mContext.getContentResolver(); 594 resolver.delete(TestResultsProvider.getResultContentUri(mContext), "1", null); 595 596 // Apart from deleting metadata from content resolver database, need to delete 597 // files generated in LOGS_DIRECTORY. For example screenshots. 598 File resFolder = 599 new File( 600 Environment.getExternalStorageDirectory().getAbsolutePath() 601 + File.separator 602 + LOGS_DIRECTORY); 603 deleteDirectory(resFolder); 604 605 return null; 606 } 607 } 608 609 class SetTestResultTask extends AsyncTask<Void, Void, Void> { 610 611 private final String mTestName; 612 private final int mResult; 613 private final String mDetails; 614 private final ReportLog mReportLog; 615 private final TestResultHistoryCollection mHistoryCollection; 616 private final TestScreenshotsMetadata mScreenshotsMetadata; 617 SetTestResultTask( String testName, int result, String details, ReportLog reportLog, TestResultHistoryCollection historyCollection, TestScreenshotsMetadata screenshotsMetadata)618 SetTestResultTask( 619 String testName, 620 int result, 621 String details, 622 ReportLog reportLog, 623 TestResultHistoryCollection historyCollection, 624 TestScreenshotsMetadata screenshotsMetadata) { 625 mTestName = testName; 626 mResult = result; 627 mDetails = details; 628 mReportLog = reportLog; 629 mHistoryCollection = historyCollection; 630 mScreenshotsMetadata = screenshotsMetadata; 631 } 632 633 @Override doInBackground(Void... params)634 protected Void doInBackground(Void... params) { 635 if (mHasLoadedResultHistory.get()) { 636 mHistoryCollection.merge(null, mHistories.get(mTestName)); 637 } else { 638 // Loads history from ContentProvider directly if it has not been loaded yet. 639 ContentResolver resolver = mContext.getContentResolver(); 640 641 try (Cursor cursor = 642 resolver.query( 643 TestResultsProvider.getTestNameUri(mContext, mTestName), 644 new String[] {TestResultsProvider.COLUMN_TEST_RESULT_HISTORY}, 645 null, 646 null, 647 null)) { 648 if (cursor.moveToFirst()) { 649 do { 650 TestResultHistoryCollection historyCollection = 651 (TestResultHistoryCollection) deserialize(cursor.getBlob(0)); 652 mHistoryCollection.merge(null, historyCollection); 653 } while (cursor.moveToNext()); 654 } 655 } 656 } 657 TestResultsProvider.setTestResult( 658 mContext, 659 mTestName, 660 mResult, 661 mDetails, 662 mReportLog, 663 mHistoryCollection, 664 mScreenshotsMetadata); 665 return null; 666 } 667 } 668 669 class TestResultContentObserver extends ContentObserver { 670 TestResultContentObserver()671 public TestResultContentObserver() { 672 super(new Handler()); 673 } 674 675 @Override onChange(boolean selfChange)676 public void onChange(boolean selfChange) { 677 super.onChange(selfChange); 678 loadTestResults(); 679 } 680 } 681 682 @Override areAllItemsEnabled()683 public boolean areAllItemsEnabled() { 684 // Section headers for test categories are not clickable. 685 return false; 686 } 687 688 @Override isEnabled(int position)689 public boolean isEnabled(int position) { 690 if (getItem(position) == null) { 691 return false; 692 } 693 return getItem(position).isTest(); 694 } 695 696 @Override getItemViewType(int position)697 public int getItemViewType(int position) { 698 if (getItem(position) == null) { 699 return CATEGORY_HEADER_VIEW_TYPE; 700 } 701 return getItem(position).isTest() ? TEST_VIEW_TYPE : CATEGORY_HEADER_VIEW_TYPE; 702 } 703 704 @Override getViewTypeCount()705 public int getViewTypeCount() { 706 return 2; 707 } 708 709 @Override getCount()710 public int getCount() { 711 return mRows.size(); 712 } 713 714 @Override getItem(int position)715 public TestListItem getItem(int position) { 716 return mRows.get(position); 717 } 718 719 @Override getItemId(int position)720 public long getItemId(int position) { 721 return position; 722 } 723 724 /** Gets {@link TestListItem} with the given test name. */ getItemByName(String testName)725 public TestListItem getItemByName(String testName) { 726 for (TestListItem item: mRows) { 727 if (item != null && item.testName != null && item.testName.equals(testName)) { 728 return item; 729 } 730 } 731 return null; 732 } 733 getTestResult(int position)734 public int getTestResult(int position) { 735 TestListItem item = getItem(position); 736 return mTestResults.containsKey(item.testName) 737 ? mTestResults.get(item.testName) 738 : TestResult.TEST_RESULT_NOT_EXECUTED; 739 } 740 getTestDetails(int position)741 public String getTestDetails(int position) { 742 TestListItem item = getItem(position); 743 return mTestDetails.containsKey(item.testName) ? mTestDetails.get(item.testName) : null; 744 } 745 getReportLog(int position)746 public ReportLog getReportLog(int position) { 747 TestListItem item = getItem(position); 748 return mReportLogs.containsKey(item.testName) ? mReportLogs.get(item.testName) : null; 749 } 750 751 /** 752 * Get test result histories. 753 * 754 * @param position The position of test. 755 * @return A {@link TestResultHistoryCollection} object containing test result histories of 756 * tests. 757 */ getHistoryCollection(int position)758 public TestResultHistoryCollection getHistoryCollection(int position) { 759 TestListItem item = getItem(position); 760 if (item == null) { 761 return null; 762 } 763 return mHistories.containsKey(item.testName) ? mHistories.get(item.testName) : null; 764 } 765 766 /** 767 * Get test screenshots metadata 768 * 769 * @param position The position of test 770 * @return A {@link TestScreenshotsMetadata} object containing test screenshots metadata. 771 */ getScreenshotsMetadata(String mode, int position)772 public TestScreenshotsMetadata getScreenshotsMetadata(String mode, int position) { 773 TestListItem item = getItem(mode, position); 774 return mScreenshotsMetadata.containsKey(item.testName) 775 ? mScreenshotsMetadata.get(item.testName) 776 : null; 777 } 778 779 /** 780 * Get test item by the given display mode and position. 781 * 782 * @param mode The display mode. 783 * @param position The position of test. 784 * @return A {@link TestListItem} object containing the test item. 785 */ getItem(String mode, int position)786 public TestListItem getItem(String mode, int position) { 787 return mDisplayModesTests.get(mode).get(position); 788 } 789 790 /** 791 * Get test item count by the given display mode. 792 * 793 * @param mode The display mode. 794 * @return A count of test items. 795 */ getCount(String mode)796 public int getCount(String mode) { 797 return mDisplayModesTests.getOrDefault(mode, new ArrayList<>()).size(); 798 } 799 800 /** 801 * Get test result by the given display mode and position. 802 * 803 * @param mode The display mode. 804 * @param position The position of test. 805 * @return The test item result. 806 */ getTestResult(String mode, int position)807 public int getTestResult(String mode, int position) { 808 TestListItem item = mDisplayModesTests.get(mode).get(position); 809 return mTestResults.containsKey(item.testName) 810 ? mTestResults.get(item.testName) 811 : TestResult.TEST_RESULT_NOT_EXECUTED; 812 } 813 814 /** 815 * Get test details by the given display mode and position. 816 * 817 * @param mode The display mode. 818 * @param position The position of test. 819 * @return A string containing the test details. 820 */ getTestDetails(String mode, int position)821 public String getTestDetails(String mode, int position) { 822 TestListItem item = mDisplayModesTests.get(mode).get(position); 823 return mTestDetails.containsKey(item.testName) ? mTestDetails.get(item.testName) : null; 824 } 825 826 /** 827 * Get test report log by the given display mode and position. 828 * 829 * @param mode The display mode. 830 * @param position The position of test. 831 * @return A {@link ReportLog} object containing the test report log of the test item. 832 */ getReportLog(String mode, int position)833 public ReportLog getReportLog(String mode, int position) { 834 TestListItem item = mDisplayModesTests.get(mode).get(position); 835 return mReportLogs.containsKey(item.testName) ? mReportLogs.get(item.testName) : null; 836 } 837 838 /** 839 * Get test result histories by the given display mode and position. 840 * 841 * @param mode The display mode. 842 * @param position The position of test. 843 * @return A {@link TestResultHistoryCollection} object containing the test result histories of 844 * the test item. 845 */ getHistoryCollection(String mode, int position)846 public TestResultHistoryCollection getHistoryCollection(String mode, int position) { 847 TestListItem item = mDisplayModesTests.get(mode).get(position); 848 return mHistories.containsKey(item.testName) ? mHistories.get(item.testName) : null; 849 } 850 allTestsPassed()851 public boolean allTestsPassed() { 852 for (TestListItem item : mRows) { 853 if (item != null && item.isTest() 854 && (!mTestResults.containsKey(item.testName) 855 || (mTestResults.get(item.testName) 856 != TestResult.TEST_RESULT_PASSED))) { 857 return false; 858 } 859 } 860 return true; 861 } 862 863 @Override getView(int position, View convertView, ViewGroup parent)864 public View getView(int position, View convertView, ViewGroup parent) { 865 TextView textView; 866 if (convertView == null) { 867 int layout = getLayout(position); 868 textView = (TextView) mLayoutInflater.inflate(layout, parent, false); 869 } else { 870 textView = (TextView) convertView; 871 } 872 873 TestListItem item = getItem(position); 874 875 if (item == null) { 876 return textView; 877 } 878 879 textView.setText(item.title); 880 textView.setPadding(PADDING, 0, PADDING, 0); 881 textView.setCompoundDrawablePadding(PADDING); 882 883 if (item.isTest()) { 884 int testResult = getTestResult(position); 885 int backgroundResource = 0; 886 int iconResource = 0; 887 888 /** TODO: Remove fs_ prefix from feature icons since they are used here too. */ 889 switch (testResult) { 890 case TestResult.TEST_RESULT_PASSED: 891 backgroundResource = R.drawable.test_pass_gradient; 892 iconResource = R.drawable.fs_good; 893 break; 894 895 case TestResult.TEST_RESULT_FAILED: 896 backgroundResource = R.drawable.test_fail_gradient; 897 iconResource = R.drawable.fs_error; 898 break; 899 900 case TestResult.TEST_RESULT_NOT_EXECUTED: 901 break; 902 903 default: 904 throw new IllegalArgumentException("Unknown test result: " + testResult); 905 } 906 907 textView.setBackgroundResource(backgroundResource); 908 textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, iconResource, 0); 909 } 910 911 return textView; 912 } 913 914 /** 915 * Uses {@link DeviceStateManager} to determine if the device is foldable or not. It relies on 916 * the OEM exposing supported states, and setting 917 * com.android.internal.R.array.config_foldedDeviceStates correctly with the folded states. 918 * 919 * @return true if the device is foldable, false otherwise 920 */ isFoldableDevice()921 public boolean isFoldableDevice() { 922 DeviceStateManager deviceStateManager = mContext.getSystemService(DeviceStateManager.class); 923 if (deviceStateManager == null) { 924 return false; 925 } 926 Set<Integer> supportedStates = Arrays.stream( 927 deviceStateManager.getSupportedStates()).boxed().collect(Collectors.toSet()); 928 int identifier = mContext.getResources().getIdentifier( 929 "config_foldedDeviceStates", "array", "android"); 930 int[] foldedDeviceStates = mContext.getResources().getIntArray(identifier); 931 return Arrays.stream(foldedDeviceStates).anyMatch(supportedStates::contains); 932 } 933 getLayout(int position)934 private int getLayout(int position) { 935 int viewType = getItemViewType(position); 936 switch (viewType) { 937 case CATEGORY_HEADER_VIEW_TYPE: 938 return R.layout.test_category_row; 939 case TEST_VIEW_TYPE: 940 return android.R.layout.simple_list_item_1; 941 default: 942 throw new IllegalArgumentException("Illegal view type: " + viewType); 943 } 944 } 945 deserialize(byte[] bytes)946 public static Object deserialize(byte[] bytes) { 947 if (bytes == null || bytes.length == 0) { 948 return null; 949 } 950 ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes); 951 ObjectInputStream objectInput = null; 952 try { 953 objectInput = new ObjectInputStream(byteStream); 954 return objectInput.readObject(); 955 } catch (IOException e) { 956 return null; 957 } catch (ClassNotFoundException e) { 958 return null; 959 } finally { 960 try { 961 if (objectInput != null) { 962 objectInput.close(); 963 } 964 byteStream.close(); 965 } catch (IOException e) { 966 // Ignore close exception. 967 } 968 } 969 } 970 971 /** 972 * Sets test name suffix. In the folded mode, the suffix is [folded]; otherwise, it is empty 973 * string. 974 * 975 * @param mode A string of current display mode. 976 * @param name A string of test name. 977 * @return A string of test name with suffix, [folded], in the folded mode. A string of input 978 * test name in the unfolded mode. 979 */ setTestNameSuffix(String mode, String name)980 public static String setTestNameSuffix(String mode, String name) { 981 if (name != null 982 && mode.equalsIgnoreCase(DisplayMode.FOLDED.toString()) 983 && !name.endsWith(DisplayMode.FOLDED.asSuffix())) { 984 return name + DisplayMode.FOLDED.asSuffix(); 985 } 986 return name; 987 } 988 989 /** 990 * Removes test name suffix. In the unfolded mode, remove the suffix [folded]. 991 * 992 * @param mode A string of current display mode. 993 * @param name A string of test name. 994 * @return A string of test name without suffix, [folded], in the unfolded mode. A string of 995 * input test name in the folded mode. 996 */ removeTestNameSuffix(String mode, String name)997 public static String removeTestNameSuffix(String mode, String name) { 998 if (name != null 999 && mode.equalsIgnoreCase(DisplayMode.UNFOLDED.toString()) 1000 && name.endsWith(DisplayMode.FOLDED.asSuffix())) { 1001 return name.substring(0, name.length() - DisplayMode.FOLDED.asSuffix().length()); 1002 } 1003 return name; 1004 } 1005 } 1006