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 */ 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 */ 81 void setInfoResources(int titleId, int messageId, int viewId); 82 83 View getPassButton(); 84 85 /** 86 * Returns a unique identifier for the test. Usually, this is just the class name. 87 */ 88 String getTestId(); 89 90 /** @return null or details about the test run. */ 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 */ 98 void setTestResultAndFinish(boolean passed); 99 100 /** @return A {@link ReportLog} that is used to record test metric data. */ 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 108 public Activity() { 109 this.reportLog = new CtsVerifierReportLog(); 110 } 111 112 @Override 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 123 protected void onPause() { 124 super.onPause(); 125 if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { 126 mWakeLock.release(); 127 } 128 } 129 130 @Override 131 public void setPassFailButtonClickListeners() { 132 setPassFailClickListeners(this); 133 } 134 135 @Override 136 public void setInfoResources(int titleId, int messageId, int viewId) { 137 setInfo(this, titleId, messageId, viewId); 138 } 139 140 @Override 141 public View getPassButton() { 142 return getPassButtonView(this); 143 } 144 145 @Override 146 public Dialog onCreateDialog(int id, Bundle args) { 147 return createDialog(this, id, args); 148 } 149 150 @Override 151 public String getTestId() { 152 return getClass().getName(); 153 } 154 155 @Override 156 public String getTestDetails() { 157 return null; 158 } 159 160 @Override 161 public void setTestResultAndFinish(boolean passed) { 162 PassFailButtons.setTestResultAndFinishHelper( 163 this, getTestId(), getTestDetails(), passed, getReportLog()); 164 } 165 166 @Override 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 174 public ListActivity() { 175 this.reportLog = new CtsVerifierReportLog(); 176 } 177 178 @Override 179 public void setPassFailButtonClickListeners() { 180 setPassFailClickListeners(this); 181 } 182 183 @Override 184 public void setInfoResources(int titleId, int messageId, int viewId) { 185 setInfo(this, titleId, messageId, viewId); 186 } 187 188 @Override 189 public View getPassButton() { 190 return getPassButtonView(this); 191 } 192 193 @Override 194 public Dialog onCreateDialog(int id, Bundle args) { 195 return createDialog(this, id, args); 196 } 197 198 @Override 199 public String getTestId() { 200 return getClass().getName(); 201 } 202 203 @Override 204 public String getTestDetails() { 205 return null; 206 } 207 208 @Override 209 public void setTestResultAndFinish(boolean passed) { 210 PassFailButtons.setTestResultAndFinishHelper( 211 this, getTestId(), getTestDetails(), passed, getReportLog()); 212 } 213 214 @Override 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 223 public TestListActivity() { 224 this.reportLog = new CtsVerifierReportLog(); 225 } 226 227 @Override 228 public void setPassFailButtonClickListeners() { 229 setPassFailClickListeners(this); 230 } 231 232 @Override 233 public void setInfoResources(int titleId, int messageId, int viewId) { 234 setInfo(this, titleId, messageId, viewId); 235 } 236 237 @Override 238 public View getPassButton() { 239 return getPassButtonView(this); 240 } 241 242 @Override 243 public Dialog onCreateDialog(int id, Bundle args) { 244 return createDialog(this, id, args); 245 } 246 247 @Override 248 public String getTestId() { 249 return getClass().getName(); 250 } 251 252 @Override 253 public String getTestDetails() { 254 return null; 255 } 256 257 @Override 258 public void setTestResultAndFinish(boolean passed) { 259 PassFailButtons.setTestResultAndFinishHelper( 260 this, getTestId(), getTestDetails(), passed, getReportLog()); 261 } 262 263 @Override 264 public ReportLog getReportLog() { return reportLog; } 265 } 266 267 private static <T extends android.app.Activity & PassFailActivity> 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 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 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 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 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 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 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. */ 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. */ 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 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