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