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