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.app.AlertDialog;
22 import android.app.Dialog;
23 import android.content.ContentResolver;
24 import android.content.ContentValues;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.DialogInterface.OnCancelListener;
28 import android.content.pm.PackageManager;
29 import android.database.Cursor;
30 import android.os.Bundle;
31 import android.os.PowerManager;
32 import android.os.PowerManager.WakeLock;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.View.OnClickListener;
36 import android.widget.ImageButton;
37 import android.widget.Toast;
38 
39 import java.util.List;
40 import java.util.stream.Collectors;
41 import java.util.stream.IntStream;
42 
43 /**
44  * {@link Activity}s to handle clicks to the pass and fail buttons of the pass fail buttons layout.
45  *
46  * <ol>
47  *     <li>Include the pass fail buttons layout in your layout:
48  *         <pre><include layout="@layout/pass_fail_buttons" /></pre>
49  *     </li>
50  *     <li>Extend one of the activities and call setPassFailButtonClickListeners after
51  *         setting your content view.</li>
52  *     <li>Make sure to call setResult(RESULT_CANCEL) in your Activity initially.</li>
53  *     <li>Optionally call setInfoTextResources to add an info button that will show a
54  *         dialog with instructional text.</li>
55  * </ol>
56  */
57 public class PassFailButtons {
58 
59     private static final int INFO_DIALOG_ID = 1337;
60 
61     private static final String INFO_DIALOG_VIEW_ID = "infoDialogViewId";
62     private static final String INFO_DIALOG_TITLE_ID = "infoDialogTitleId";
63     private static final String INFO_DIALOG_MESSAGE_ID = "infoDialogMessageId";
64 
65     // Interface mostly for making documentation and refactoring easier...
66     public interface PassFailActivity {
67 
68         /**
69          * Hooks up the pass and fail buttons to click listeners that will record the test results.
70          * <p>
71          * Call from {@link Activity#onCreate} after {@link Activity #setContentView(int)}.
72          */
setPassFailButtonClickListeners()73         void setPassFailButtonClickListeners();
74 
75         /**
76          * Adds an initial informational dialog that appears when entering the test activity for
77          * the first time. Also enables the visibility of an "Info" button between the "Pass" and
78          * "Fail" buttons that can be clicked to show the information dialog again.
79          * <p>
80          * Call from {@link Activity#onCreate} after {@link Activity #setContentView(int)}.
81          *
82          * @param titleId for the text shown in the dialog title area
83          * @param messageId for the text shown in the dialog's body area
84          */
setInfoResources(int titleId, int messageId, int viewId)85         void setInfoResources(int titleId, int messageId, int viewId);
86 
getPassButton()87         View getPassButton();
88 
89         /**
90          * Returns a unique identifier for the test.  Usually, this is just the class name.
91          */
getTestId()92         String getTestId();
93 
94         /** @return null or details about the test run. */
getTestDetails()95         String getTestDetails();
96 
97         /**
98          * Set the result of the test and finish the activity.
99          *
100          * @param passed Whether or not the test passed.
101          */
setTestResultAndFinish(boolean passed)102         void setTestResultAndFinish(boolean passed);
103 
104         /** @return A {@link ReportLog} that is used to record test metric data. */
getReportLog()105         ReportLog getReportLog();
106 
107         /**
108          * @return A {@link TestResultHistoryCollection} that is used to record test execution time.
109          */
getHistoryCollection()110         TestResultHistoryCollection getHistoryCollection();
111     }
112 
113     public static class Activity extends android.app.Activity implements PassFailActivity {
114         private WakeLock mWakeLock;
115         private final ReportLog reportLog;
116         private final TestResultHistoryCollection mHistoryCollection;
117 
Activity()118         public Activity() {
119            this.reportLog = new CtsVerifierReportLog();
120            this.mHistoryCollection = new TestResultHistoryCollection();
121         }
122 
123         @Override
onResume()124         protected void onResume() {
125             super.onResume();
126             if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
127                 mWakeLock = ((PowerManager) getSystemService(Context.POWER_SERVICE))
128                         .newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "PassFailButtons");
129                 mWakeLock.acquire();
130             }
131         }
132 
133         @Override
onPause()134         protected void onPause() {
135             super.onPause();
136             if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
137                 mWakeLock.release();
138             }
139         }
140 
141         @Override
setPassFailButtonClickListeners()142         public void setPassFailButtonClickListeners() {
143             setPassFailClickListeners(this);
144         }
145 
146         @Override
setInfoResources(int titleId, int messageId, int viewId)147         public void setInfoResources(int titleId, int messageId, int viewId) {
148             setInfo(this, titleId, messageId, viewId);
149         }
150 
151         @Override
getPassButton()152         public View getPassButton() {
153             return getPassButtonView(this);
154         }
155 
156         @Override
onCreateDialog(int id, Bundle args)157         public Dialog onCreateDialog(int id, Bundle args) {
158             return createDialog(this, id, args);
159         }
160 
161         @Override
getTestId()162         public String getTestId() {
163             return getClass().getName();
164         }
165 
166         @Override
getTestDetails()167         public String getTestDetails() {
168             return null;
169         }
170 
171         @Override
setTestResultAndFinish(boolean passed)172         public void setTestResultAndFinish(boolean passed) {
173             PassFailButtons.setTestResultAndFinishHelper(
174                     this, getTestId(), getTestDetails(), passed, getReportLog(),
175                     getHistoryCollection());
176         }
177 
178         @Override
getReportLog()179         public ReportLog getReportLog() { return reportLog; }
180 
181         @Override
getHistoryCollection()182         public TestResultHistoryCollection getHistoryCollection() { return mHistoryCollection; }
183     }
184 
185     public static class ListActivity extends android.app.ListActivity implements PassFailActivity {
186 
187         private final ReportLog reportLog;
188         private final TestResultHistoryCollection mHistoryCollection;
189 
ListActivity()190         public ListActivity() {
191             this.reportLog = new CtsVerifierReportLog();
192             this.mHistoryCollection = new TestResultHistoryCollection();
193         }
194 
195         @Override
setPassFailButtonClickListeners()196         public void setPassFailButtonClickListeners() {
197             setPassFailClickListeners(this);
198         }
199 
200         @Override
setInfoResources(int titleId, int messageId, int viewId)201         public void setInfoResources(int titleId, int messageId, int viewId) {
202             setInfo(this, titleId, messageId, viewId);
203         }
204 
205         @Override
getPassButton()206         public View getPassButton() {
207             return getPassButtonView(this);
208         }
209 
210         @Override
onCreateDialog(int id, Bundle args)211         public Dialog onCreateDialog(int id, Bundle args) {
212             return createDialog(this, id, args);
213         }
214 
215         @Override
getTestId()216         public String getTestId() {
217             return getClass().getName();
218         }
219 
220         @Override
getTestDetails()221         public String getTestDetails() {
222             return null;
223         }
224 
225         @Override
setTestResultAndFinish(boolean passed)226         public void setTestResultAndFinish(boolean passed) {
227             PassFailButtons.setTestResultAndFinishHelper(
228                     this, getTestId(), getTestDetails(), passed, getReportLog(),
229                     getHistoryCollection());
230         }
231 
232         @Override
getReportLog()233         public ReportLog getReportLog() { return reportLog; }
234 
235         @Override
getHistoryCollection()236         public TestResultHistoryCollection getHistoryCollection() { return mHistoryCollection; }
237     }
238 
239     public static class TestListActivity extends AbstractTestListActivity
240             implements PassFailActivity {
241 
242         private final ReportLog reportLog;
243 
TestListActivity()244         public TestListActivity() {
245             this.reportLog = new CtsVerifierReportLog();
246         }
247 
248         @Override
setPassFailButtonClickListeners()249         public void setPassFailButtonClickListeners() {
250             setPassFailClickListeners(this);
251         }
252 
253         @Override
setInfoResources(int titleId, int messageId, int viewId)254         public void setInfoResources(int titleId, int messageId, int viewId) {
255             setInfo(this, titleId, messageId, viewId);
256         }
257 
258         @Override
getPassButton()259         public View getPassButton() {
260             return getPassButtonView(this);
261         }
262 
263         @Override
onCreateDialog(int id, Bundle args)264         public Dialog onCreateDialog(int id, Bundle args) {
265             return createDialog(this, id, args);
266         }
267 
268         @Override
getTestId()269         public String getTestId() {
270             return getClass().getName();
271         }
272 
273         @Override
getTestDetails()274         public String getTestDetails() {
275             return null;
276         }
277 
278         @Override
setTestResultAndFinish(boolean passed)279         public void setTestResultAndFinish(boolean passed) {
280             PassFailButtons.setTestResultAndFinishHelper(
281                     this, getTestId(), getTestDetails(), passed, getReportLog(),
282                     getHistoryCollection());
283         }
284 
285         @Override
getReportLog()286         public ReportLog getReportLog() { return reportLog; }
287 
288         /**
289          * Get existing test history to aggregate.
290          */
291         @Override
getHistoryCollection()292         public TestResultHistoryCollection getHistoryCollection() {
293             List<TestResultHistoryCollection> histories =
294                 IntStream.range(0, mAdapter.getCount())
295                 .mapToObj(mAdapter::getHistoryCollection)
296                 .collect(Collectors.toList());
297             TestResultHistoryCollection historyCollection = new TestResultHistoryCollection();
298             historyCollection.merge(getTestId(), histories);
299             return historyCollection;
300         }
301 
updatePassButton()302         public void updatePassButton() {
303             getPassButton().setEnabled(mAdapter.allTestsPassed());
304         }
305     }
306 
307     protected static <T extends android.app.Activity & PassFailActivity>
setPassFailClickListeners(final T activity)308             void setPassFailClickListeners(final T activity) {
309         View.OnClickListener clickListener = new View.OnClickListener() {
310             @Override
311             public void onClick(View target) {
312                 setTestResultAndFinish(activity, activity.getTestId(), activity.getTestDetails(),
313                         activity.getReportLog(), activity.getHistoryCollection(), target);
314             }
315         };
316 
317         View passButton = activity.findViewById(R.id.pass_button);
318         passButton.setOnClickListener(clickListener);
319         passButton.setOnLongClickListener(new View.OnLongClickListener() {
320             @Override
321             public boolean onLongClick(View view) {
322                 Toast.makeText(activity, R.string.pass_button_text, Toast.LENGTH_SHORT).show();
323                 return true;
324             }
325         });
326 
327         View failButton = activity.findViewById(R.id.fail_button);
328         failButton.setOnClickListener(clickListener);
329         failButton.setOnLongClickListener(new View.OnLongClickListener() {
330             @Override
331             public boolean onLongClick(View view) {
332                 Toast.makeText(activity, R.string.fail_button_text, Toast.LENGTH_SHORT).show();
333                 return true;
334             }
335         });
336     }
337 
setInfo(final android.app.Activity activity, final int titleId, final int messageId, final int viewId)338     protected static void setInfo(final android.app.Activity activity, final int titleId,
339             final int messageId, final int viewId) {
340         // Show the middle "info" button and make it show the info dialog when clicked.
341         View infoButton = activity.findViewById(R.id.info_button);
342         infoButton.setVisibility(View.VISIBLE);
343         infoButton.setOnClickListener(new OnClickListener() {
344             @Override
345             public void onClick(View view) {
346                 showInfoDialog(activity, titleId, messageId, viewId);
347             }
348         });
349         infoButton.setOnLongClickListener(new View.OnLongClickListener() {
350             @Override
351             public boolean onLongClick(View view) {
352                 Toast.makeText(activity, R.string.info_button_text, Toast.LENGTH_SHORT).show();
353                 return true;
354             }
355         });
356 
357         // Show the info dialog if the user has never seen it before.
358         if (!hasSeenInfoDialog(activity)) {
359             showInfoDialog(activity, titleId, messageId, viewId);
360         }
361     }
362 
hasSeenInfoDialog(android.app.Activity activity)363     protected static boolean hasSeenInfoDialog(android.app.Activity activity) {
364         ContentResolver resolver = activity.getContentResolver();
365         Cursor cursor = null;
366         try {
367             cursor = resolver.query(TestResultsProvider.getTestNameUri(activity),
368                     new String[] {TestResultsProvider.COLUMN_TEST_INFO_SEEN}, null, null, null);
369             return cursor.moveToFirst() && cursor.getInt(0) > 0;
370         } finally {
371             if (cursor != null) {
372                 cursor.close();
373             }
374         }
375     }
376 
showInfoDialog(final android.app.Activity activity, int titleId, int messageId, int viewId)377     protected static void showInfoDialog(final android.app.Activity activity, int titleId,
378             int messageId, int viewId) {
379         Bundle args = new Bundle();
380         args.putInt(INFO_DIALOG_TITLE_ID, titleId);
381         args.putInt(INFO_DIALOG_MESSAGE_ID, messageId);
382         args.putInt(INFO_DIALOG_VIEW_ID, viewId);
383         activity.showDialog(INFO_DIALOG_ID, args);
384     }
385 
createDialog(final android.app.Activity activity, int id, Bundle args)386     protected static Dialog createDialog(final android.app.Activity activity, int id, Bundle args) {
387         switch (id) {
388             case INFO_DIALOG_ID:
389                 return createInfoDialog(activity, id, args);
390             default:
391                 throw new IllegalArgumentException("Bad dialog id: " + id);
392         }
393     }
394 
createInfoDialog(final android.app.Activity activity, int id, Bundle args)395     protected static Dialog createInfoDialog(final android.app.Activity activity, int id,
396             Bundle args) {
397         int viewId = args.getInt(INFO_DIALOG_VIEW_ID);
398         int titleId = args.getInt(INFO_DIALOG_TITLE_ID);
399         int messageId = args.getInt(INFO_DIALOG_MESSAGE_ID);
400 
401         AlertDialog.Builder builder = new AlertDialog.Builder(activity).setIcon(
402                 android.R.drawable.ic_dialog_info).setTitle(titleId);
403         if (viewId > 0) {
404             LayoutInflater inflater = (LayoutInflater) activity
405                     .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
406             builder.setView(inflater.inflate(viewId, null));
407         } else {
408             builder.setMessage(messageId);
409         }
410         builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
411             @Override
412             public void onClick(DialogInterface dialog, int which) {
413                 markSeenInfoDialog(activity);
414             }
415         }).setOnCancelListener(new OnCancelListener() {
416             @Override
417             public void onCancel(DialogInterface dialog) {
418                 markSeenInfoDialog(activity);
419             }
420         });
421         return builder.create();
422     }
423 
markSeenInfoDialog(android.app.Activity activity)424     protected static void markSeenInfoDialog(android.app.Activity activity) {
425         ContentResolver resolver = activity.getContentResolver();
426         ContentValues values = new ContentValues(2);
427         values.put(TestResultsProvider.COLUMN_TEST_NAME, activity.getClass().getName());
428         values.put(TestResultsProvider.COLUMN_TEST_INFO_SEEN, 1);
429         int numUpdated = resolver.update(
430                 TestResultsProvider.getTestNameUri(activity), values, null, null);
431         if (numUpdated == 0) {
432             resolver.insert(TestResultsProvider.getResultContentUri(activity), values);
433         }
434     }
435 
436     /** Set the test result corresponding to the button clicked and finish the activity. */
setTestResultAndFinish(android.app.Activity activity, String testId, String testDetails, ReportLog reportLog, TestResultHistoryCollection historyCollection, View target)437     protected static void setTestResultAndFinish(android.app.Activity activity, String testId,
438             String testDetails, ReportLog reportLog, TestResultHistoryCollection historyCollection,
439             View target) {
440         boolean passed;
441         if (target.getId() == R.id.pass_button) {
442             passed = true;
443         } else if (target.getId() == R.id.fail_button) {
444             passed = false;
445         } else {
446             throw new IllegalArgumentException("Unknown id: " + target.getId());
447         }
448 
449         setTestResultAndFinishHelper(activity, testId, testDetails, passed, reportLog, historyCollection);
450     }
451 
452     /** Set the test result and finish the activity. */
setTestResultAndFinishHelper(android.app.Activity activity, String testId, String testDetails, boolean passed, ReportLog reportLog, TestResultHistoryCollection historyCollection)453     protected static void setTestResultAndFinishHelper(android.app.Activity activity, String testId,
454             String testDetails, boolean passed, ReportLog reportLog,
455             TestResultHistoryCollection historyCollection) {
456         if (passed) {
457             TestResult.setPassedResult(activity, testId, testDetails, reportLog, historyCollection);
458         } else {
459             TestResult.setFailedResult(activity, testId, testDetails, reportLog, historyCollection);
460         }
461 
462         activity.finish();
463     }
464 
getPassButtonView(android.app.Activity activity)465     protected static ImageButton getPassButtonView(android.app.Activity activity) {
466         return (ImageButton) activity.findViewById(R.id.pass_button);
467     }
468 
469     public static class CtsVerifierReportLog extends ReportLog {
470 
471     }
472 }
473