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