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     public 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 
updatePassButton()266         public void updatePassButton() {
267             getPassButton().setEnabled(mAdapter.allTestsPassed());
268         }
269     }
270 
271     protected static <T extends android.app.Activity & PassFailActivity>
setPassFailClickListeners(final T activity)272             void setPassFailClickListeners(final T activity) {
273         View.OnClickListener clickListener = new View.OnClickListener() {
274             @Override
275             public void onClick(View target) {
276                 setTestResultAndFinish(activity, activity.getTestId(), activity.getTestDetails(),
277                         activity.getReportLog(), target);
278             }
279         };
280 
281         View passButton = activity.findViewById(R.id.pass_button);
282         passButton.setOnClickListener(clickListener);
283         passButton.setOnLongClickListener(new View.OnLongClickListener() {
284             @Override
285             public boolean onLongClick(View view) {
286                 Toast.makeText(activity, R.string.pass_button_text, Toast.LENGTH_SHORT).show();
287                 return true;
288             }
289         });
290 
291         View failButton = activity.findViewById(R.id.fail_button);
292         failButton.setOnClickListener(clickListener);
293         failButton.setOnLongClickListener(new View.OnLongClickListener() {
294             @Override
295             public boolean onLongClick(View view) {
296                 Toast.makeText(activity, R.string.fail_button_text, Toast.LENGTH_SHORT).show();
297                 return true;
298             }
299         });
300     }
301 
setInfo(final android.app.Activity activity, final int titleId, final int messageId, final int viewId)302     protected static void setInfo(final android.app.Activity activity, final int titleId,
303             final int messageId, final int viewId) {
304         // Show the middle "info" button and make it show the info dialog when clicked.
305         View infoButton = activity.findViewById(R.id.info_button);
306         infoButton.setVisibility(View.VISIBLE);
307         infoButton.setOnClickListener(new OnClickListener() {
308             @Override
309             public void onClick(View view) {
310                 showInfoDialog(activity, titleId, messageId, viewId);
311             }
312         });
313         infoButton.setOnLongClickListener(new View.OnLongClickListener() {
314             @Override
315             public boolean onLongClick(View view) {
316                 Toast.makeText(activity, R.string.info_button_text, Toast.LENGTH_SHORT).show();
317                 return true;
318             }
319         });
320 
321         // Show the info dialog if the user has never seen it before.
322         if (!hasSeenInfoDialog(activity)) {
323             showInfoDialog(activity, titleId, messageId, viewId);
324         }
325     }
326 
hasSeenInfoDialog(android.app.Activity activity)327     protected static boolean hasSeenInfoDialog(android.app.Activity activity) {
328         ContentResolver resolver = activity.getContentResolver();
329         Cursor cursor = null;
330         try {
331             cursor = resolver.query(TestResultsProvider.getTestNameUri(activity),
332                     new String[] {TestResultsProvider.COLUMN_TEST_INFO_SEEN}, null, null, null);
333             return cursor.moveToFirst() && cursor.getInt(0) > 0;
334         } finally {
335             if (cursor != null) {
336                 cursor.close();
337             }
338         }
339     }
340 
showInfoDialog(final android.app.Activity activity, int titleId, int messageId, int viewId)341     protected static void showInfoDialog(final android.app.Activity activity, int titleId,
342             int messageId, int viewId) {
343         Bundle args = new Bundle();
344         args.putInt(INFO_DIALOG_TITLE_ID, titleId);
345         args.putInt(INFO_DIALOG_MESSAGE_ID, messageId);
346         args.putInt(INFO_DIALOG_VIEW_ID, viewId);
347         activity.showDialog(INFO_DIALOG_ID, args);
348     }
349 
createDialog(final android.app.Activity activity, int id, Bundle args)350     protected static Dialog createDialog(final android.app.Activity activity, int id, Bundle args) {
351         switch (id) {
352             case INFO_DIALOG_ID:
353                 return createInfoDialog(activity, id, args);
354             default:
355                 throw new IllegalArgumentException("Bad dialog id: " + id);
356         }
357     }
358 
createInfoDialog(final android.app.Activity activity, int id, Bundle args)359     protected static Dialog createInfoDialog(final android.app.Activity activity, int id,
360             Bundle args) {
361         int viewId = args.getInt(INFO_DIALOG_VIEW_ID);
362         int titleId = args.getInt(INFO_DIALOG_TITLE_ID);
363         int messageId = args.getInt(INFO_DIALOG_MESSAGE_ID);
364 
365         AlertDialog.Builder builder = new AlertDialog.Builder(activity).setIcon(
366                 android.R.drawable.ic_dialog_info).setTitle(titleId);
367         if (viewId > 0) {
368             LayoutInflater inflater = (LayoutInflater) activity
369                     .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
370             builder.setView(inflater.inflate(viewId, null));
371         } else {
372             builder.setMessage(messageId);
373         }
374         builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
375             @Override
376             public void onClick(DialogInterface dialog, int which) {
377                 markSeenInfoDialog(activity);
378             }
379         }).setOnCancelListener(new OnCancelListener() {
380             @Override
381             public void onCancel(DialogInterface dialog) {
382                 markSeenInfoDialog(activity);
383             }
384         });
385         return builder.create();
386     }
387 
markSeenInfoDialog(android.app.Activity activity)388     protected static void markSeenInfoDialog(android.app.Activity activity) {
389         ContentResolver resolver = activity.getContentResolver();
390         ContentValues values = new ContentValues(2);
391         values.put(TestResultsProvider.COLUMN_TEST_NAME, activity.getClass().getName());
392         values.put(TestResultsProvider.COLUMN_TEST_INFO_SEEN, 1);
393         int numUpdated = resolver.update(
394                 TestResultsProvider.getTestNameUri(activity), values, null, null);
395         if (numUpdated == 0) {
396             resolver.insert(TestResultsProvider.getResultContentUri(activity), values);
397         }
398     }
399 
400     /** 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)401     protected static void setTestResultAndFinish(android.app.Activity activity, String testId,
402             String testDetails, ReportLog reportLog, View target) {
403         boolean passed;
404         if (target.getId() == R.id.pass_button) {
405             passed = true;
406         } else if (target.getId() == R.id.fail_button) {
407             passed = false;
408         } else {
409             throw new IllegalArgumentException("Unknown id: " + target.getId());
410         }
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     protected 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     protected 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