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