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 com.android.compatibility.common.util.ReportLog;
20 import com.android.compatibility.common.util.TestResultHistory;
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.os.AsyncTask;
28 import android.os.Handler;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.BaseAdapter;
33 import android.widget.ListView;
34 import android.widget.TextView;
35 
36 import java.io.ByteArrayInputStream;
37 import java.io.IOException;
38 import java.io.ObjectInputStream;
39 import java.util.ArrayList;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 
44 /**
45  * {@link BaseAdapter} that handles loading, refreshing, and setting test
46  * results. What tests are shown can be customized by overriding
47  * {@link #getRows()}. See {@link ArrayTestListAdapter} and
48  * {@link ManifestTestListAdapter} for examples.
49  */
50 public abstract class TestListAdapter extends BaseAdapter {
51 
52     /** Activities implementing {@link Intent#ACTION_MAIN} and this will appear in the list. */
53     public static final String CATEGORY_MANUAL_TEST = "android.cts.intent.category.MANUAL_TEST";
54 
55     /** View type for a category of tests like "Sensors" or "Features" */
56     private static final int CATEGORY_HEADER_VIEW_TYPE = 0;
57 
58     /** View type for an actual test like the Accelerometer test. */
59     private static final int TEST_VIEW_TYPE = 1;
60 
61     /** Padding around the text views and icons. */
62     private static final int PADDING = 10;
63 
64     private final Context mContext;
65 
66     /** Immutable data of tests like the test's title and launch intent. */
67     private final List<TestListItem> mRows = new ArrayList<TestListItem>();
68 
69     /** Mutable test results that will change as each test activity finishes. */
70     private final Map<String, Integer> mTestResults = new HashMap<String, Integer>();
71 
72     /** Map from test name to test details. */
73     private final Map<String, String> mTestDetails = new HashMap<String, String>();
74 
75     /** Map from test name to {@link ReportLog}. */
76     private final Map<String, ReportLog> mReportLogs = new HashMap<String, ReportLog>();
77 
78     /** Map from test name to {@link TestResultHistory}. */
79     private final Map<String, TestResultHistoryCollection> mHistories = new HashMap<>();
80 
81     private final LayoutInflater mLayoutInflater;
82 
83     /** {@link ListView} row that is either a test category header or a test. */
84     public static class TestListItem {
85 
86         /** Title shown in the {@link ListView}. */
87         final String title;
88 
89         /** Test name with class and test ID to uniquely identify the test. Null for categories. */
90         final String testName;
91 
92         /** Intent used to launch the activity from the list. Null for categories. */
93         final Intent intent;
94 
95         /** Features necessary to run this test. */
96         final String[] requiredFeatures;
97 
98         /** Configs necessary to run this test. */
99         final String[] requiredConfigs;
100 
101         /** Features such that, if any present, the test gets excluded from being shown. */
102         final String[] excludedFeatures;
103 
104         /** If any of of the features are present the test is meaningful to run. */
105         final String[] applicableFeatures;
106 
newTest(Context context, int titleResId, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures)107         public static TestListItem newTest(Context context, int titleResId, String testName,
108                 Intent intent, String[] requiredFeatures, String[] excludedFeatures,
109                 String[] applicableFeatures) {
110             return newTest(context.getString(titleResId), testName, intent, requiredFeatures,
111                     excludedFeatures, applicableFeatures);
112         }
113 
newTest(Context context, int titleResId, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures)114         public static TestListItem newTest(Context context, int titleResId, String testName,
115                 Intent intent, String[] requiredFeatures, String[] excludedFeatures) {
116             return newTest(context.getString(titleResId), testName, intent, requiredFeatures,
117                     excludedFeatures, null);
118         }
119 
newTest(Context context, int titleResId, String testName, Intent intent, String[] requiredFeatures)120         public static TestListItem newTest(Context context, int titleResId, String testName,
121                 Intent intent, String[] requiredFeatures) {
122             return newTest(context.getString(titleResId), testName, intent, requiredFeatures, null,
123                     null);
124         }
125 
newTest(String title, String testName, Intent intent, String[] requiredFeatures, String[] requiredConfigs, String[] excludedFeatures, String[] applicableFeatures)126         public static TestListItem newTest(String title, String testName, Intent intent,
127                 String[] requiredFeatures, String[] requiredConfigs, String[] excludedFeatures,
128                 String[] applicableFeatures) {
129             return new TestListItem(title, testName, intent, requiredFeatures, requiredConfigs,
130                     excludedFeatures, applicableFeatures);
131         }
132 
newTest(String title, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures)133         public static TestListItem newTest(String title, String testName, Intent intent,
134                 String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures) {
135             return new TestListItem(title, testName, intent, requiredFeatures, null, excludedFeatures,
136                     applicableFeatures);
137         }
138 
newTest(String title, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures)139         public static TestListItem newTest(String title, String testName, Intent intent,
140                 String[] requiredFeatures, String[] excludedFeatures) {
141             return new TestListItem(title, testName, intent, requiredFeatures, null, excludedFeatures,
142                     null);
143         }
144 
newTest(String title, String testName, Intent intent, String[] requiredFeatures)145         public static TestListItem newTest(String title, String testName, Intent intent,
146                 String[] requiredFeatures) {
147             return new TestListItem(title, testName, intent, requiredFeatures, null, null, null);
148         }
149 
newCategory(Context context, int titleResId)150         public static TestListItem newCategory(Context context, int titleResId) {
151             return newCategory(context.getString(titleResId));
152         }
153 
newCategory(String title)154         public static TestListItem newCategory(String title) {
155             return new TestListItem(title, null, null, null, null, null, null);
156         }
157 
TestListItem(String title, String testName, Intent intent, String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures)158         protected TestListItem(String title, String testName, Intent intent,
159                 String[] requiredFeatures, String[] excludedFeatures, String[] applicableFeatures) {
160             this(title, testName, intent, requiredFeatures, null, excludedFeatures, applicableFeatures);
161         }
162 
TestListItem(String title, String testName, Intent intent, String[] requiredFeatures, String[] requiredConfigs, String[] excludedFeatures, String[] applicableFeatures)163         protected TestListItem(String title, String testName, Intent intent,
164                 String[] requiredFeatures, String[] requiredConfigs, String[] excludedFeatures,
165                 String[] applicableFeatures) {
166             this.title = title;
167             this.testName = testName;
168             this.intent = intent;
169             this.requiredFeatures = requiredFeatures;
170             this.requiredConfigs = requiredConfigs;
171             this.excludedFeatures = excludedFeatures;
172             this.applicableFeatures = applicableFeatures;
173         }
174 
isTest()175         boolean isTest() {
176             return intent != null;
177         }
178     }
179 
TestListAdapter(Context context)180     public TestListAdapter(Context context) {
181         this.mContext = context;
182         this.mLayoutInflater =
183                 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
184 
185         TestResultContentObserver observer = new TestResultContentObserver();
186         ContentResolver resolver = context.getContentResolver();
187         resolver.registerContentObserver(TestResultsProvider.getResultContentUri(context), true, observer);
188     }
189 
loadTestResults()190     public void loadTestResults() {
191         new RefreshTestResultsTask().execute();
192     }
193 
clearTestResults()194     public void clearTestResults() {
195         new ClearTestResultsTask().execute();
196     }
197 
setTestResult(TestResult testResult)198     public void setTestResult(TestResult testResult) {
199         String name = testResult.getName();
200 
201         // Append existing history
202         TestResultHistoryCollection histories = testResult.getHistoryCollection();
203         histories.merge(null, mHistories.get(name));
204 
205         new SetTestResultTask(name, testResult.getResult(),
206                 testResult.getDetails(), testResult.getReportLog(), histories).execute();
207     }
208 
209     class RefreshTestResultsTask extends AsyncTask<Void, Void, RefreshResult> {
210         @Override
doInBackground(Void... params)211         protected RefreshResult doInBackground(Void... params) {
212             List<TestListItem> rows = getRows();
213             return getRefreshResults(rows);
214         }
215 
216         @Override
onPostExecute(RefreshResult result)217         protected void onPostExecute(RefreshResult result) {
218             super.onPostExecute(result);
219             mRows.clear();
220             mRows.addAll(result.mItems);
221             mTestResults.clear();
222             mTestResults.putAll(result.mResults);
223             mTestDetails.clear();
224             mTestDetails.putAll(result.mDetails);
225             mReportLogs.clear();
226             mReportLogs.putAll(result.mReportLogs);
227             mHistories.clear();
228             mHistories.putAll(result.mHistories);
229             notifyDataSetChanged();
230         }
231     }
232 
233     static class RefreshResult {
234         List<TestListItem> mItems;
235         Map<String, Integer> mResults;
236         Map<String, String> mDetails;
237         Map<String, ReportLog> mReportLogs;
238         Map<String, TestResultHistoryCollection> mHistories;
239 
RefreshResult( List<TestListItem> items, Map<String, Integer> results, Map<String, String> details, Map<String, ReportLog> reportLogs, Map<String, TestResultHistoryCollection> histories)240         RefreshResult(
241                 List<TestListItem> items,
242                 Map<String, Integer> results,
243                 Map<String, String> details,
244                 Map<String, ReportLog> reportLogs,
245                 Map<String, TestResultHistoryCollection> histories) {
246             mItems = items;
247             mResults = results;
248             mDetails = details;
249             mReportLogs = reportLogs;
250             mHistories = histories;
251         }
252     }
253 
getRows()254     protected abstract List<TestListItem> getRows();
255 
256     static final String[] REFRESH_PROJECTION = {
257         TestResultsProvider._ID,
258         TestResultsProvider.COLUMN_TEST_NAME,
259         TestResultsProvider.COLUMN_TEST_RESULT,
260         TestResultsProvider.COLUMN_TEST_DETAILS,
261         TestResultsProvider.COLUMN_TEST_METRICS,
262         TestResultsProvider.COLUMN_TEST_RESULT_HISTORY,
263     };
264 
getRefreshResults(List<TestListItem> items)265     RefreshResult getRefreshResults(List<TestListItem> items) {
266         Map<String, Integer> results = new HashMap<String, Integer>();
267         Map<String, String> details = new HashMap<String, String>();
268         Map<String, ReportLog> reportLogs = new HashMap<String, ReportLog>();
269         Map<String, TestResultHistoryCollection> histories = new HashMap<>();
270         ContentResolver resolver = mContext.getContentResolver();
271         Cursor cursor = null;
272         try {
273             cursor = resolver.query(TestResultsProvider.getResultContentUri(mContext), REFRESH_PROJECTION,
274                     null, null, null);
275             if (cursor.moveToFirst()) {
276                 do {
277                     String testName = cursor.getString(1);
278                     int testResult = cursor.getInt(2);
279                     String testDetails = cursor.getString(3);
280                     ReportLog reportLog = (ReportLog) deserialize(cursor.getBlob(4));
281                     TestResultHistoryCollection historyCollection =
282                         (TestResultHistoryCollection) deserialize(cursor.getBlob(5));
283                     results.put(testName, testResult);
284                     details.put(testName, testDetails);
285                     reportLogs.put(testName, reportLog);
286                     histories.put(testName, historyCollection);
287                 } while (cursor.moveToNext());
288             }
289         } finally {
290             if (cursor != null) {
291                 cursor.close();
292             }
293         }
294         return new RefreshResult(items, results, details, reportLogs, histories);
295     }
296 
297     class ClearTestResultsTask extends AsyncTask<Void, Void, Void> {
298 
299         @Override
doInBackground(Void... params)300         protected Void doInBackground(Void... params) {
301             ContentResolver resolver = mContext.getContentResolver();
302             resolver.delete(TestResultsProvider.getResultContentUri(mContext), "1", null);
303             return null;
304         }
305     }
306 
307     class SetTestResultTask extends AsyncTask<Void, Void, Void> {
308 
309         private final String mTestName;
310         private final int mResult;
311         private final String mDetails;
312         private final ReportLog mReportLog;
313         private final TestResultHistoryCollection mHistoryCollection;
314 
SetTestResultTask( String testName, int result, String details, ReportLog reportLog, TestResultHistoryCollection historyCollection)315         SetTestResultTask(
316                 String testName,
317                 int result,
318                 String details,
319                 ReportLog reportLog,
320                 TestResultHistoryCollection historyCollection) {
321             mTestName = testName;
322             mResult = result;
323             mDetails = details;
324             mReportLog = reportLog;
325             mHistoryCollection = historyCollection;
326         }
327 
328         @Override
doInBackground(Void... params)329         protected Void doInBackground(Void... params) {
330             TestResultsProvider.setTestResult(
331                 mContext, mTestName, mResult, mDetails, mReportLog, mHistoryCollection);
332             return null;
333         }
334     }
335 
336     class TestResultContentObserver extends ContentObserver {
337 
TestResultContentObserver()338         public TestResultContentObserver() {
339             super(new Handler());
340         }
341 
342         @Override
onChange(boolean selfChange)343         public void onChange(boolean selfChange) {
344             super.onChange(selfChange);
345             loadTestResults();
346         }
347     }
348 
349     @Override
areAllItemsEnabled()350     public boolean areAllItemsEnabled() {
351         // Section headers for test categories are not clickable.
352         return false;
353     }
354 
355     @Override
isEnabled(int position)356     public boolean isEnabled(int position) {
357         return getItem(position).isTest();
358     }
359 
360     @Override
getItemViewType(int position)361     public int getItemViewType(int position) {
362         return getItem(position).isTest() ? TEST_VIEW_TYPE : CATEGORY_HEADER_VIEW_TYPE;
363     }
364 
365     @Override
getViewTypeCount()366     public int getViewTypeCount() {
367         return 2;
368     }
369 
370     @Override
getCount()371     public int getCount() {
372         return mRows.size();
373     }
374 
375     @Override
getItem(int position)376     public TestListItem getItem(int position) {
377         return mRows.get(position);
378     }
379 
380     @Override
getItemId(int position)381     public long getItemId(int position) {
382         return position;
383     }
384 
getTestResult(int position)385     public int getTestResult(int position) {
386         TestListItem item = getItem(position);
387         return mTestResults.containsKey(item.testName)
388                 ? mTestResults.get(item.testName)
389                 : TestResult.TEST_RESULT_NOT_EXECUTED;
390     }
391 
getTestDetails(int position)392     public String getTestDetails(int position) {
393         TestListItem item = getItem(position);
394         return mTestDetails.containsKey(item.testName)
395                 ? mTestDetails.get(item.testName)
396                 : null;
397     }
398 
getReportLog(int position)399     public ReportLog getReportLog(int position) {
400         TestListItem item = getItem(position);
401         return mReportLogs.containsKey(item.testName)
402                 ? mReportLogs.get(item.testName)
403                 : null;
404     }
405 
406     /**
407      * Get test result histories.
408      *
409      * @param position The position of test.
410      * @return A {@link TestResultHistoryCollection} object containing test result histories of tests.
411      */
getHistoryCollection(int position)412     public TestResultHistoryCollection getHistoryCollection(int position) {
413         TestListItem item = getItem(position);
414         return mHistories.containsKey(item.testName)
415             ? mHistories.get(item.testName)
416             : null;
417     }
418 
allTestsPassed()419     public boolean allTestsPassed() {
420         for (TestListItem item : mRows) {
421             if (item.isTest() && (!mTestResults.containsKey(item.testName)
422                     || (mTestResults.get(item.testName) != TestResult.TEST_RESULT_PASSED))) {
423                 return false;
424             }
425         }
426         return true;
427     }
428 
429     @Override
getView(int position, View convertView, ViewGroup parent)430     public View getView(int position, View convertView, ViewGroup parent) {
431         TextView textView;
432         if (convertView == null) {
433             int layout = getLayout(position);
434             textView = (TextView) mLayoutInflater.inflate(layout, parent, false);
435         } else {
436             textView = (TextView) convertView;
437         }
438 
439         TestListItem item = getItem(position);
440         textView.setText(item.title);
441         textView.setPadding(PADDING, 0, PADDING, 0);
442         textView.setCompoundDrawablePadding(PADDING);
443 
444         if (item.isTest()) {
445             int testResult = getTestResult(position);
446             int backgroundResource = 0;
447             int iconResource = 0;
448 
449             /** TODO: Remove fs_ prefix from feature icons since they are used here too. */
450             switch (testResult) {
451                 case TestResult.TEST_RESULT_PASSED:
452                     backgroundResource = R.drawable.test_pass_gradient;
453                     iconResource = R.drawable.fs_good;
454                     break;
455 
456                 case TestResult.TEST_RESULT_FAILED:
457                     backgroundResource = R.drawable.test_fail_gradient;
458                     iconResource = R.drawable.fs_error;
459                     break;
460 
461                 case TestResult.TEST_RESULT_NOT_EXECUTED:
462                     break;
463 
464                 default:
465                     throw new IllegalArgumentException("Unknown test result: " + testResult);
466             }
467 
468             textView.setBackgroundResource(backgroundResource);
469             textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, iconResource, 0);
470         }
471 
472         return textView;
473     }
474 
getLayout(int position)475     private int getLayout(int position) {
476         int viewType = getItemViewType(position);
477         switch (viewType) {
478             case CATEGORY_HEADER_VIEW_TYPE:
479                 return R.layout.test_category_row;
480             case TEST_VIEW_TYPE:
481                 return android.R.layout.simple_list_item_1;
482             default:
483                 throw new IllegalArgumentException("Illegal view type: " + viewType);
484 
485         }
486     }
487 
deserialize(byte[] bytes)488     public static Object deserialize(byte[] bytes) {
489         if (bytes == null || bytes.length == 0) {
490             return null;
491         }
492         ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
493         ObjectInputStream objectInput = null;
494         try {
495             objectInput = new ObjectInputStream(byteStream);
496             return objectInput.readObject();
497         } catch (IOException e) {
498             return null;
499         } catch (ClassNotFoundException e) {
500             return null;
501         } finally {
502             try {
503                 if (objectInput != null) {
504                     objectInput.close();
505                 }
506                 byteStream.close();
507             } catch (IOException e) {
508                 // Ignore close exception.
509             }
510         }
511     }
512 }
513