• 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 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