• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 != null && 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     int getTestResult(int position) {
721         TestListItem item = getItem(position);
722         return mTestResults.getOrDefault(item.testName, TestResult.TEST_RESULT_NOT_EXECUTED);
723     }
724 
getTestDetails(int position)725     String getTestDetails(int position) {
726         TestListItem item = getItem(position);
727         return mTestDetails.getOrDefault(item.testName, null);
728     }
729 
getReportLog(int position)730     ReportLog getReportLog(int position) {
731         TestListItem item = getItem(position);
732         return mReportLogs.getOrDefault(item.testName, null);
733     }
734 
735     /**
736      * Get test result histories.
737      *
738      * @param position The position of test.
739      * @return A {@link TestResultHistoryCollection} object containing test result histories of
740      *     tests.
741      */
getHistoryCollection(int position)742     TestResultHistoryCollection getHistoryCollection(int position) {
743         TestListItem item = getItem(position);
744         if (item == null) {
745             return null;
746         }
747         return mHistories.getOrDefault(item.testName, null);
748     }
749 
750     /**
751      * Get test names from test results.
752      *
753      * @return A set of test names.
754      */
getTestResultNames()755     Set<String> getTestResultNames() {
756         return mTestResults.keySet();
757     }
758 
759     /**
760      * Get test screenshots metadata
761      *
762      * @param testName The test name
763      * @return A {@link TestScreenshotsMetadata} object containing test screenshots metadata.
764      */
getScreenshotsMetadata(String testName)765     TestScreenshotsMetadata getScreenshotsMetadata(String testName) {
766         return mScreenshotsMetadata.getOrDefault(testName, null);
767     }
768 
769     /**
770      * Get test item by the given display mode and position.
771      *
772      * @param mode The display mode.
773      * @param position The position of test.
774      * @return A {@link TestListItem} object containing the test item.
775      */
getItem(String mode, int position)776     TestListItem getItem(String mode, int position) {
777         return mDisplayModesTests.get(mode).get(position);
778     }
779 
780     /**
781      * Get test item count by the given display mode.
782      *
783      * @param mode The display mode.
784      * @return A count of test items.
785      */
getCount(String mode)786     int getCount(String mode) {
787         return mDisplayModesTests.getOrDefault(mode, new ArrayList<>()).size();
788     }
789 
790     /**
791      * Get test result by the given test name.
792      *
793      * @param testName The test name
794      * @return The test item result.
795      */
getTestResult(String testName)796     int getTestResult(String testName) {
797         return mTestResults.getOrDefault(testName, TestResult.TEST_RESULT_NOT_EXECUTED);
798     }
799 
800     /**
801      * Get test details by the given test name.
802      *
803      * @param testName The test name
804      * @return A string containing the test details.
805      */
getTestDetails(String testName)806     String getTestDetails(String testName) {
807         return mTestDetails.getOrDefault(testName, null);
808     }
809 
810     /**
811      * Get test report log by the given test name.
812      *
813      * @param testName The test name
814      * @return A {@link ReportLog} object containing the test report log of the test item.
815      */
getReportLog(String testName)816     ReportLog getReportLog(String testName) {
817         return mReportLogs.getOrDefault(testName, null);
818     }
819 
820     /**
821      * Get test result histories by the given test name.
822      *
823      * @param testName The test name
824      * @return A {@link TestResultHistoryCollection} object containing the test result histories of
825      *     the test item.
826      */
getHistoryCollection(String testName)827     TestResultHistoryCollection getHistoryCollection(String testName) {
828         return mHistories.getOrDefault(testName, null);
829     }
830 
allTestsPassed()831     public boolean allTestsPassed() {
832         for (TestListItem item : mRows) {
833             if (item != null
834                     && item.isTest()
835                     && (!mTestResults.containsKey(item.testName)
836                             || (mTestResults.get(item.testName)
837                                     != TestResult.TEST_RESULT_PASSED))) {
838                 return false;
839             }
840         }
841         return true;
842     }
843 
844     @Override
getView(int position, View convertView, ViewGroup parent)845     public View getView(int position, View convertView, ViewGroup parent) {
846         TextView textView;
847         if (convertView == null) {
848             int layout = getLayout(position);
849             textView = (TextView) mLayoutInflater.inflate(layout, parent, false);
850         } else {
851             textView = (TextView) convertView;
852         }
853 
854         TestListItem item = getItem(position);
855 
856         if (item == null) {
857             return textView;
858         }
859 
860         textView.setText(item.title);
861         textView.setPadding(PADDING, 0, PADDING, 0);
862         textView.setCompoundDrawablePadding(PADDING);
863 
864         if (item.isTest()) {
865             int testResult = getTestResult(position);
866             int backgroundResource = 0;
867             int iconResource = 0;
868 
869             /** TODO: Remove fs_ prefix from feature icons since they are used here too. */
870             switch (testResult) {
871                 case TestResult.TEST_RESULT_PASSED:
872                     backgroundResource = R.drawable.test_pass_gradient;
873                     iconResource = R.drawable.fs_good;
874                     break;
875 
876                 case TestResult.TEST_RESULT_FAILED:
877                     backgroundResource = R.drawable.test_fail_gradient;
878                     iconResource = R.drawable.fs_error;
879                     break;
880 
881                 case TestResult.TEST_RESULT_NOT_EXECUTED:
882                     break;
883 
884                 default:
885                     throw new IllegalArgumentException("Unknown test result: " + testResult);
886             }
887 
888             textView.setBackgroundResource(backgroundResource);
889             textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, iconResource, 0);
890         }
891 
892         return textView;
893     }
894 
895     /**
896      * Uses {@link DeviceStateManager} to determine if the device is foldable or not. It relies on
897      * the OEM exposing supported states, and setting
898      * com.android.internal.R.array.config_foldedDeviceStates correctly with the folded states.
899      *
900      * @return true if the device is foldable, false otherwise
901      */
isFoldableDevice()902     public boolean isFoldableDevice() {
903         DeviceStateManager deviceStateManager = mContext.getSystemService(DeviceStateManager.class);
904         if (deviceStateManager == null) {
905             return false;
906         }
907         Set<Integer> supportedStates = deviceStateManager.getSupportedDeviceStates().stream().map(
908                 state -> state.getIdentifier()).collect(Collectors.toSet());
909         int identifier =
910                 mContext.getResources()
911                         .getIdentifier("config_foldedDeviceStates", "array", "android");
912         int[] foldedDeviceStates = mContext.getResources().getIntArray(identifier);
913         return Arrays.stream(foldedDeviceStates).anyMatch(supportedStates::contains);
914     }
915 
getLayout(int position)916     private int getLayout(int position) {
917         int viewType = getItemViewType(position);
918         switch (viewType) {
919             case CATEGORY_HEADER_VIEW_TYPE:
920                 return R.layout.test_category_row;
921             case TEST_VIEW_TYPE:
922                 return android.R.layout.simple_list_item_1;
923             default:
924                 throw new IllegalArgumentException("Illegal view type: " + viewType);
925         }
926     }
927 
deserialize(byte[] bytes)928     public static Object deserialize(byte[] bytes) {
929         if (bytes == null || bytes.length == 0) {
930             return null;
931         }
932         ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
933         ObjectInputStream objectInput = null;
934         try {
935             objectInput = new ObjectInputStream(byteStream);
936             return objectInput.readObject();
937         } catch (IOException e) {
938             return null;
939         } catch (ClassNotFoundException e) {
940             return null;
941         } finally {
942             try {
943                 if (objectInput != null) {
944                     objectInput.close();
945                 }
946                 byteStream.close();
947             } catch (IOException e) {
948                 // Ignore close exception.
949             }
950         }
951     }
952 
953     /**
954      * Sets test name suffix. In the folded mode, the suffix is [folded]; otherwise, it is empty
955      * string.
956      *
957      * @param mode A string of current display mode.
958      * @param name A string of test name.
959      * @return A string of test name with suffix, [folded], in the folded mode. A string of input
960      *     test name in the unfolded mode.
961      */
setTestNameSuffix(String mode, String name)962     public static String setTestNameSuffix(String mode, String name) {
963         if (name != null
964                 && mode.equalsIgnoreCase(DisplayMode.FOLDED.toString())
965                 && !name.endsWith(DisplayMode.FOLDED.asSuffix())) {
966             return name + DisplayMode.FOLDED.asSuffix();
967         }
968         return name;
969     }
970 
971     /**
972      * Removes test name suffix. In the unfolded mode, remove the suffix [folded].
973      *
974      * @param mode A string of current display mode.
975      * @param name A string of test name.
976      * @return A string of test name without suffix, [folded], in the unfolded mode. A string of
977      *     input test name in the folded mode.
978      */
removeTestNameSuffix(String mode, String name)979     public static String removeTestNameSuffix(String mode, String name) {
980         if (name != null
981                 && mode.equalsIgnoreCase(DisplayMode.UNFOLDED.toString())
982                 && name.endsWith(DisplayMode.FOLDED.asSuffix())) {
983             return name.substring(0, name.length() - DisplayMode.FOLDED.asSuffix().length());
984         }
985         return name;
986     }
987 }
988