• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
20 import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
21 
22 import android.app.ActionBar;
23 import android.app.AlertDialog;
24 import android.app.Dialog;
25 import android.content.ContentResolver;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.DialogInterface.OnCancelListener;
30 import android.content.pm.PackageManager;
31 import android.database.Cursor;
32 import android.os.Bundle;
33 import android.os.PowerManager;
34 import android.os.PowerManager.WakeLock;
35 import android.view.LayoutInflater;
36 import android.view.MenuItem;
37 import android.view.View;
38 import android.view.View.OnClickListener;
39 import android.widget.ImageButton;
40 import android.widget.Toast;
41 
42 import com.android.compatibility.common.util.ReportLog;
43 
44 import java.util.List;
45 import java.util.stream.Collectors;
46 import java.util.stream.IntStream;
47 
48 /**
49  * {@link Activity}s to handle clicks to the pass and fail buttons of the pass fail buttons layout.
50  *
51  * <ol>
52  *     <li>Include the pass fail buttons layout in your layout:
53  *         <pre><include layout="@layout/pass_fail_buttons" /></pre>
54  *     </li>
55  *     <li>Extend one of the activities and call setPassFailButtonClickListeners after
56  *         setting your content view.</li>
57  *     <li>Make sure to call setResult(RESULT_CANCEL) in your Activity initially.</li>
58  *     <li>Optionally call setInfoTextResources to add an info button that will show a
59  *         dialog with instructional text.</li>
60  * </ol>
61  */
62 public class PassFailButtons {
63     private static final String TAG = PassFailButtons.class.getSimpleName();
64 
65     private static final int INFO_DIALOG_ID = 1337;
66 
67     private static final String INFO_DIALOG_VIEW_ID = "infoDialogViewId";
68     private static final String INFO_DIALOG_TITLE_ID = "infoDialogTitleId";
69     private static final String INFO_DIALOG_MESSAGE_ID = "infoDialogMessageId";
70 
71     // ReportLog file for CTS-Verifier. The "stream" name gets mapped to the test class name.
72     public static final String GENERAL_TESTS_REPORT_LOG_NAME = "CtsVerifierGeneralTestCases";
73     public static final String AUDIO_TESTS_REPORT_LOG_NAME = "CtsVerifierAudioTestCases";
74 
75     private static final String SECTION_UNDEFINED = "undefined_section_name";
76 
77     // Interface mostly for making documentation and refactoring easier...
78     public interface PassFailActivity {
79 
80         /**
81          * Hooks up the pass and fail buttons to click listeners that will record the test results.
82          * <p>
83          * Call from {@link Activity#onCreate} after {@link Activity #setContentView(int)}.
84          */
setPassFailButtonClickListeners()85         void setPassFailButtonClickListeners();
86 
87         /**
88          * Adds an initial informational dialog that appears when entering the test activity for
89          * the first time. Also enables the visibility of an "Info" button between the "Pass" and
90          * "Fail" buttons that can be clicked to show the information dialog again.
91          * <p>
92          * Call from {@link Activity#onCreate} after {@link Activity #setContentView(int)}.
93          *
94          * @param titleId for the text shown in the dialog title area
95          * @param messageId for the text shown in the dialog's body area
96          */
setInfoResources(int titleId, int messageId, int viewId)97         void setInfoResources(int titleId, int messageId, int viewId);
98 
getPassButton()99         View getPassButton();
100 
101         /**
102          * Returns a unique identifier for the test.  Usually, this is just the class name.
103          */
getTestId()104         String getTestId();
105 
106         /** @return null or details about the test run. */
getTestDetails()107         String getTestDetails();
108 
109         /**
110          * Set the result of the test and finish the activity.
111          *
112          * @param passed Whether or not the test passed.
113          */
setTestResultAndFinish(boolean passed)114         void setTestResultAndFinish(boolean passed);
115 
116         /**
117          * @return The name of the file to store the (suite of) ReportLog information.
118          */
getReportFileName()119         public String getReportFileName();
120 
121         /**
122          * @return A unique name to serve as a section header in the CtsVerifierReportLog file.
123          * Tests need to conform to the underscore_delineated_name standard for use with
124          * the protobuff/json ReportLog parsing in Google3
125          */
getReportSectionName()126         public String getReportSectionName();
127 
128         /**
129          * Test subclasses can override this to record their CtsVerifierReportLogs.
130          * This is called when the test is exited
131          */
recordTestResults()132         void recordTestResults();
133 
134         /** @return A {@link ReportLog} that is used to record test metric data. */
getReportLog()135         CtsVerifierReportLog getReportLog();
136 
137         /**
138          * @return A {@link TestResultHistoryCollection} that is used to record test execution time.
139          */
getHistoryCollection()140         TestResultHistoryCollection getHistoryCollection();
141     }   /* class PassFailButtons.PassFailActivity */
142 
143     public static class Activity extends android.app.Activity implements PassFailActivity {
144         private WakeLock mWakeLock;
145         private CtsVerifierReportLog mReportLog;
146         private final TestResultHistoryCollection mHistoryCollection;
147 
148         protected boolean mRequireReportLogToPass;
149 
Activity()150         public Activity() {
151             this.mHistoryCollection = new TestResultHistoryCollection();
152             if (requiresReportLog()) {
153                 // if the subclass reports a report filename, they need a ReportLog object
154                 newReportLog();
155             }
156         }
157 
158         @Override
onResume()159         protected void onResume() {
160             super.onResume();
161             if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
162                 mWakeLock = ((PowerManager) getSystemService(Context.POWER_SERVICE))
163                         .newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "PassFailButtons");
164                 mWakeLock.acquire();
165             }
166 
167             if (mReportLog != null && !mReportLog.isOpen()) {
168                 showReportLogWarningDialog(this);
169             }
170         }
171 
172         @Override
onPause()173         protected void onPause() {
174             super.onPause();
175             if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
176                 mWakeLock.release();
177             }
178         }
179 
180         @Override
setPassFailButtonClickListeners()181         public void setPassFailButtonClickListeners() {
182             setPassFailClickListeners(this);
183         }
184 
185         @Override
setInfoResources(int titleId, int messageId, int viewId)186         public void setInfoResources(int titleId, int messageId, int viewId) {
187             setInfo(this, titleId, messageId, viewId);
188         }
189 
190         @Override
getPassButton()191         public View getPassButton() {
192             return getPassButtonView(this);
193         }
194 
195         @Override
onCreateDialog(int id, Bundle args)196         public Dialog onCreateDialog(int id, Bundle args) {
197             return createDialog(this, id, args);
198         }
199 
200         @Override
getTestId()201         public String getTestId() {
202             return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
203         }
204 
205         @Override
getTestDetails()206         public String getTestDetails() {
207             return null;
208         }
209 
210         @Override
setTestResultAndFinish(boolean passed)211         public void setTestResultAndFinish(boolean passed) {
212             PassFailButtons.setTestResultAndFinishHelper(
213                     this, getTestId(), getTestDetails(), passed, getReportLog(),
214                     getHistoryCollection());
215         }
216 
newReportLog()217         protected CtsVerifierReportLog newReportLog() {
218             return mReportLog = new CtsVerifierReportLog(
219                     getReportFileName(), getReportSectionName());
220         }
221 
222         /**
223          * Specifies if the test module will write a ReportLog entry
224          * @return true if the test module will write a ReportLog entry
225          */
requiresReportLog()226         public boolean requiresReportLog() {
227             return false;
228         }
229 
230         @Override
getReportLog()231         public CtsVerifierReportLog getReportLog() {
232             return mReportLog;
233         }
234 
235         /**
236          * A mechanism to block tests from passing if no ReportLog data has been collected.
237          * @return true if the ReportLog is open OR if the test does not require that.
238          */
isReportLogOkToPass()239         public boolean isReportLogOkToPass() {
240             return !mRequireReportLogToPass || (mReportLog != null & mReportLog.isOpen());
241         }
242 
243         /**
244          * @return The name of the file to store the (suite of) ReportLog information.
245          */
246         @Override
getReportFileName()247         public String getReportFileName() { return GENERAL_TESTS_REPORT_LOG_NAME; }
248 
249         @Override
getReportSectionName()250         public String getReportSectionName() {
251             return setTestNameSuffix(sCurrentDisplayMode, SECTION_UNDEFINED);
252         }
253 
254         @Override
getHistoryCollection()255         public TestResultHistoryCollection getHistoryCollection() { return mHistoryCollection; }
256 
257         @Override
onCreate(Bundle savedInstanceState)258         protected void onCreate(Bundle savedInstanceState) {
259             super.onCreate(savedInstanceState);
260             ActionBar actBar = getActionBar();
261             if (actBar != null) {
262                 actBar.setDisplayHomeAsUpEnabled(true);
263             }
264         }
265 
266         @Override
onOptionsItemSelected(MenuItem item)267         public boolean onOptionsItemSelected(MenuItem item) {
268             if (item.getItemId() == android.R.id.home) {
269                 onBackPressed();
270                 return true;
271             }
272             return super.onOptionsItemSelected(item);
273         }
274 
275         @Override
recordTestResults()276         public void recordTestResults() {
277             // default - NOP
278         }
279     }   /* class PassFailButtons.Activity */
280 
281     public static class ListActivity extends android.app.ListActivity implements PassFailActivity {
282 
283         private final CtsVerifierReportLog mReportLog;
284         private final TestResultHistoryCollection mHistoryCollection;
285 
ListActivity()286         public ListActivity() {
287             mHistoryCollection = new TestResultHistoryCollection();
288             mReportLog = null;
289         }
290 
291         @Override
setPassFailButtonClickListeners()292         public void setPassFailButtonClickListeners() {
293             setPassFailClickListeners(this);
294         }
295 
296         @Override
setInfoResources(int titleId, int messageId, int viewId)297         public void setInfoResources(int titleId, int messageId, int viewId) {
298             setInfo(this, titleId, messageId, viewId);
299         }
300 
301         @Override
getPassButton()302         public View getPassButton() {
303             return getPassButtonView(this);
304         }
305 
306         @Override
onCreateDialog(int id, Bundle args)307         public Dialog onCreateDialog(int id, Bundle args) {
308             return createDialog(this, id, args);
309         }
310 
311         @Override
getTestId()312         public String getTestId() {
313             return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
314         }
315 
316         @Override
getTestDetails()317         public String getTestDetails() {
318             return null;
319         }
320 
321         @Override
setTestResultAndFinish(boolean passed)322         public void setTestResultAndFinish(boolean passed) {
323             PassFailButtons.setTestResultAndFinishHelper(
324                     this, getTestId(), getTestDetails(), passed, getReportLog(),
325                     getHistoryCollection());
326         }
327 
328         @Override
getReportLog()329         public CtsVerifierReportLog getReportLog() {
330             return mReportLog;
331         }
332 
333         /**
334          * @return The name of the file to store the (suite of) ReportLog information.
335          */
336         @Override
getReportFileName()337         public String getReportFileName() { return GENERAL_TESTS_REPORT_LOG_NAME; }
338 
339         @Override
getReportSectionName()340         public String getReportSectionName() {
341             return setTestNameSuffix(sCurrentDisplayMode, SECTION_UNDEFINED);
342         }
343 
344         @Override
getHistoryCollection()345         public TestResultHistoryCollection getHistoryCollection() { return mHistoryCollection; }
346 
347         @Override
onCreate(Bundle savedInstanceState)348         protected void onCreate(Bundle savedInstanceState) {
349             super.onCreate(savedInstanceState);
350             ActionBar actBar = getActionBar();
351             if (actBar != null) {
352                 actBar.setDisplayHomeAsUpEnabled(true);
353             }
354         }
355 
356         @Override
onOptionsItemSelected(MenuItem item)357         public boolean onOptionsItemSelected(MenuItem item) {
358             if (item.getItemId() == android.R.id.home) {
359                 onBackPressed();
360                 return true;
361             }
362             return super.onOptionsItemSelected(item);
363         }
364 
365         @Override
recordTestResults()366         public void recordTestResults() {
367             // default - NOP
368         }
369     } // class PassFailButtons.ListActivity
370 
371     public static class TestListActivity extends AbstractTestListActivity
372             implements PassFailActivity {
373 
374         private final CtsVerifierReportLog mReportLog;
375 
TestListActivity()376         public TestListActivity() {
377             // TODO(b/186555602): temporary hack^H^H^H^H workaround to fix crash
378             // This DOES NOT in fact fix that bug.
379             // if (true) this.mReportLog = new CtsVerifierReportLog(b/186555602, "42"); else
380 
381             this.mReportLog = new CtsVerifierReportLog(getReportFileName(), getReportSectionName());
382         }
383 
384         @Override
setPassFailButtonClickListeners()385         public void setPassFailButtonClickListeners() {
386             setPassFailClickListeners(this);
387         }
388 
389         @Override
setInfoResources(int titleId, int messageId, int viewId)390         public void setInfoResources(int titleId, int messageId, int viewId) {
391             setInfo(this, titleId, messageId, viewId);
392         }
393 
394         @Override
getPassButton()395         public View getPassButton() {
396             return getPassButtonView(this);
397         }
398 
399         @Override
onCreateDialog(int id, Bundle args)400         public Dialog onCreateDialog(int id, Bundle args) {
401             return createDialog(this, id, args);
402         }
403 
404         @Override
getTestId()405         public String getTestId() {
406             return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
407         }
408 
409         @Override
getTestDetails()410         public String getTestDetails() {
411             return null;
412         }
413 
414         @Override
setTestResultAndFinish(boolean passed)415         public void setTestResultAndFinish(boolean passed) {
416             PassFailButtons.setTestResultAndFinishHelper(
417                     this, getTestId(), getTestDetails(), passed, getReportLog(),
418                     getHistoryCollection());
419         }
420 
421         @Override
getReportLog()422         public CtsVerifierReportLog getReportLog() {
423             return mReportLog;
424         }
425 
426         /**
427          * @return The name of the file to store the (suite of) ReportLog information.
428          */
429         @Override
getReportFileName()430         public String getReportFileName() { return GENERAL_TESTS_REPORT_LOG_NAME; }
431 
432         @Override
getReportSectionName()433         public String getReportSectionName() {
434             return setTestNameSuffix(sCurrentDisplayMode, SECTION_UNDEFINED);
435         }
436 
437 
438         /**
439          * Get existing test history to aggregate.
440          */
441         @Override
getHistoryCollection()442         public TestResultHistoryCollection getHistoryCollection() {
443             List<TestResultHistoryCollection> histories =
444                 IntStream.range(0, mAdapter.getCount())
445                 .mapToObj(mAdapter::getHistoryCollection)
446                 .collect(Collectors.toList());
447             TestResultHistoryCollection historyCollection = new TestResultHistoryCollection();
448             historyCollection.merge(getTestId(), histories);
449             return historyCollection;
450         }
451 
updatePassButton()452         public void updatePassButton() {
453             getPassButton().setEnabled(mAdapter.allTestsPassed());
454         }
455 
456         @Override
onCreate(Bundle savedInstanceState)457         protected void onCreate(Bundle savedInstanceState) {
458             super.onCreate(savedInstanceState);
459             ActionBar actBar = getActionBar();
460             if (actBar != null) {
461                 actBar.setDisplayHomeAsUpEnabled(true);
462             }
463         }
464 
465         @Override
onOptionsItemSelected(MenuItem item)466         public boolean onOptionsItemSelected(MenuItem item) {
467             if (item.getItemId() == android.R.id.home) {
468                 onBackPressed();
469                 return true;
470             }
471             return super.onOptionsItemSelected(item);
472         }
473 
474         @Override
recordTestResults()475         public void recordTestResults() {
476             // default - NOP
477         }
478     } // class PassFailButtons.TestListActivity
479 
480     protected static <T extends android.app.Activity & PassFailActivity>
setPassFailClickListeners(final T activity)481             void setPassFailClickListeners(final T activity) {
482         View.OnClickListener clickListener = new View.OnClickListener() {
483             @Override
484             public void onClick(View target) {
485                 setTestResultAndFinish(activity, activity.getTestId(), activity.getTestDetails(),
486                         activity.getReportLog(), activity.getHistoryCollection(), target);
487             }
488         };
489 
490         View passButton = activity.findViewById(R.id.pass_button);
491         passButton.setOnClickListener(clickListener);
492         passButton.setOnLongClickListener(new View.OnLongClickListener() {
493             @Override
494             public boolean onLongClick(View view) {
495                 Toast.makeText(activity, R.string.pass_button_text, Toast.LENGTH_SHORT).show();
496                 return true;
497             }
498         });
499 
500         View failButton = activity.findViewById(R.id.fail_button);
501         failButton.setOnClickListener(clickListener);
502         failButton.setOnLongClickListener(new View.OnLongClickListener() {
503             @Override
504             public boolean onLongClick(View view) {
505                 Toast.makeText(activity, R.string.fail_button_text, Toast.LENGTH_SHORT).show();
506                 return true;
507             }
508         });
509     } // class PassFailButtons.<T extends android.app.Activity & PassFailActivity>
510 
setInfo(final android.app.Activity activity, final int titleId, final int messageId, final int viewId)511     protected static void setInfo(final android.app.Activity activity, final int titleId,
512             final int messageId, final int viewId) {
513         // Show the middle "info" button and make it show the info dialog when clicked.
514         View infoButton = activity.findViewById(R.id.info_button);
515         infoButton.setVisibility(View.VISIBLE);
516         infoButton.setOnClickListener(new OnClickListener() {
517             @Override
518             public void onClick(View view) {
519                 showInfoDialog(activity, titleId, messageId, viewId);
520             }
521         });
522         infoButton.setOnLongClickListener(new View.OnLongClickListener() {
523             @Override
524             public boolean onLongClick(View view) {
525                 Toast.makeText(activity, R.string.info_button_text, Toast.LENGTH_SHORT).show();
526                 return true;
527             }
528         });
529 
530         // Show the info dialog if the user has never seen it before.
531         if (!hasSeenInfoDialog(activity)) {
532             showInfoDialog(activity, titleId, messageId, viewId);
533         }
534     }
535 
hasSeenInfoDialog(android.app.Activity activity)536     protected static boolean hasSeenInfoDialog(android.app.Activity activity) {
537         ContentResolver resolver = activity.getContentResolver();
538         Cursor cursor = null;
539         try {
540             cursor = resolver.query(TestResultsProvider.getTestNameUri(activity),
541                     new String[] {TestResultsProvider.COLUMN_TEST_INFO_SEEN}, null, null, null);
542             return cursor.moveToFirst() && cursor.getInt(0) > 0;
543         } finally {
544             if (cursor != null) {
545                 cursor.close();
546             }
547         }
548     }
549 
showInfoDialog(final android.app.Activity activity, int titleId, int messageId, int viewId)550     protected static void showInfoDialog(final android.app.Activity activity, int titleId,
551             int messageId, int viewId) {
552         Bundle args = new Bundle();
553         args.putInt(INFO_DIALOG_TITLE_ID, titleId);
554         args.putInt(INFO_DIALOG_MESSAGE_ID, messageId);
555         args.putInt(INFO_DIALOG_VIEW_ID, viewId);
556         activity.showDialog(INFO_DIALOG_ID, args);
557     }
558 
showReportLogWarningDialog(final android.app.Activity activity)559     protected static void showReportLogWarningDialog(final android.app.Activity activity) {
560         showInfoDialog(activity,
561                 R.string.reportlog_warning_title, R.string.reportlog_warning_body, -1);
562     }
563 
564 
createDialog(final android.app.Activity activity, int id, Bundle args)565     protected static Dialog createDialog(final android.app.Activity activity, int id, Bundle args) {
566         switch (id) {
567             case INFO_DIALOG_ID:
568                 return createInfoDialog(activity, id, args);
569             default:
570                 throw new IllegalArgumentException("Bad dialog id: " + id);
571         }
572     }
573 
createInfoDialog(final android.app.Activity activity, int id, Bundle args)574     protected static Dialog createInfoDialog(final android.app.Activity activity, int id,
575             Bundle args) {
576         int viewId = args.getInt(INFO_DIALOG_VIEW_ID);
577         int titleId = args.getInt(INFO_DIALOG_TITLE_ID);
578         int messageId = args.getInt(INFO_DIALOG_MESSAGE_ID);
579 
580         AlertDialog.Builder builder = new AlertDialog.Builder(activity).setIcon(
581                 android.R.drawable.ic_dialog_info).setTitle(titleId);
582         if (viewId > 0) {
583             LayoutInflater inflater = (LayoutInflater) activity
584                     .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
585             builder.setView(inflater.inflate(viewId, null));
586         } else {
587             builder.setMessage(messageId);
588         }
589         builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
590             @Override
591             public void onClick(DialogInterface dialog, int which) {
592                 markSeenInfoDialog(activity);
593             }
594         }).setOnCancelListener(new OnCancelListener() {
595             @Override
596             public void onCancel(DialogInterface dialog) {
597                 markSeenInfoDialog(activity);
598             }
599         });
600         return builder.create();
601     }
602 
markSeenInfoDialog(android.app.Activity activity)603     protected static void markSeenInfoDialog(android.app.Activity activity) {
604         ContentResolver resolver = activity.getContentResolver();
605         ContentValues values = new ContentValues(2);
606         String activityName = setTestNameSuffix(sCurrentDisplayMode, activity.getClass().getName());
607         values.put(TestResultsProvider.COLUMN_TEST_NAME, activityName);
608         values.put(TestResultsProvider.COLUMN_TEST_INFO_SEEN, 1);
609         int numUpdated = resolver.update(
610                 TestResultsProvider.getTestNameUri(activity), values, null, null);
611         if (numUpdated == 0) {
612             resolver.insert(TestResultsProvider.getResultContentUri(activity), values);
613         }
614     }
615 
616     /** 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)617     protected static void setTestResultAndFinish(android.app.Activity activity, String testId,
618             String testDetails, ReportLog reportLog, TestResultHistoryCollection historyCollection,
619             View target) {
620 
621         boolean passed;
622         if (target.getId() == R.id.pass_button) {
623             passed = true;
624         } else if (target.getId() == R.id.fail_button) {
625             passed = false;
626         } else {
627             throw new IllegalArgumentException("Unknown id: " + target.getId());
628         }
629 
630         // Let test classes record their CTSVerifierReportLogs
631         ((PassFailActivity) activity).recordTestResults();
632 
633         setTestResultAndFinishHelper(activity, testId, testDetails, passed, reportLog, historyCollection);
634     }
635 
636     /** Set the test result and finish the activity. */
setTestResultAndFinishHelper(android.app.Activity activity, String testId, String testDetails, boolean passed, ReportLog reportLog, TestResultHistoryCollection historyCollection)637     protected static void setTestResultAndFinishHelper(android.app.Activity activity, String testId,
638             String testDetails, boolean passed, ReportLog reportLog,
639             TestResultHistoryCollection historyCollection) {
640         if (passed) {
641             TestResult.setPassedResult(activity, testId, testDetails, reportLog, historyCollection);
642         } else {
643             TestResult.setFailedResult(activity, testId, testDetails, reportLog, historyCollection);
644         }
645 
646         activity.finish();
647     }
648 
getPassButtonView(android.app.Activity activity)649     protected static ImageButton getPassButtonView(android.app.Activity activity) {
650         return (ImageButton) activity.findViewById(R.id.pass_button);
651     }
652 
653 } // class PassFailButtons
654