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