1 /* 2 3 * Copyright (C) 2014 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.cts.verifier.sensors.base; 19 20 import com.android.cts.verifier.PassFailButtons; 21 import com.android.cts.verifier.R; 22 import com.android.cts.verifier.TestResult; 23 import com.android.cts.verifier.sensors.helpers.SensorFeaturesDeactivator; 24 import com.android.cts.verifier.sensors.reporting.SensorTestDetails; 25 26 import android.content.ActivityNotFoundException; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.pm.PackageManager; 30 import android.hardware.cts.helpers.ActivityResultMultiplexedLatch; 31 import android.media.MediaPlayer; 32 import android.opengl.GLSurfaceView; 33 import android.os.Bundle; 34 import android.os.SystemClock; 35 import android.os.Vibrator; 36 import android.provider.Settings; 37 import android.text.TextUtils; 38 import android.text.format.DateUtils; 39 import android.util.Log; 40 import android.view.View; 41 import android.widget.Button; 42 import android.widget.LinearLayout; 43 import android.widget.ScrollView; 44 import android.widget.TextView; 45 46 import junit.framework.Assert; 47 import java.util.ArrayList; 48 import java.util.concurrent.CountDownLatch; 49 import java.util.concurrent.ExecutorService; 50 import java.util.concurrent.Executors; 51 import java.util.concurrent.TimeUnit; 52 53 /** 54 * A base Activity that is used to build different methods to execute tests inside CtsVerifier. 55 * i.e. CTS tests, and semi-automated CtsVerifier tests. 56 * 57 * This class provides access to the following flow: 58 * Activity set up 59 * Execute tests (implemented by sub-classes) 60 * Activity clean up 61 * 62 * Currently the following class structure is available: 63 * - BaseSensorTestActivity : provides the platform to execute Sensor tests inside 64 * | CtsVerifier, and logging support 65 * | 66 * -- SensorCtsTestActivity : an activity that can be inherited from to wrap a CTS 67 * | sensor test, and execute it inside CtsVerifier 68 * | these tests do not require any operator interaction 69 * | 70 * -- SensorCtsVerifierTestActivity : an activity that can be inherited to write sensor 71 * tests that require operator interaction 72 */ 73 public abstract class BaseSensorTestActivity 74 extends PassFailButtons.Activity 75 implements View.OnClickListener, Runnable, ISensorTestStateContainer { 76 @Deprecated 77 protected static final String LOG_TAG = "SensorTest"; 78 79 protected final Class mTestClass; 80 81 private final int mLayoutId; 82 private final SensorFeaturesDeactivator mSensorFeaturesDeactivator; 83 84 private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor(); 85 private final SensorTestLogger mTestLogger = new SensorTestLogger(); 86 private final ActivityResultMultiplexedLatch mActivityResultMultiplexedLatch = 87 new ActivityResultMultiplexedLatch(); 88 private final ArrayList<CountDownLatch> mWaitForUserLatches = new ArrayList<CountDownLatch>(); 89 90 private ScrollView mLogScrollView; 91 private LinearLayout mLogLayout; 92 private Button mNextButton; 93 private Button mPassButton; 94 private Button mFailButton; 95 private Button mRetryButton; 96 97 private GLSurfaceView mGLSurfaceView; 98 private boolean mUsingGlSurfaceView; 99 100 // Flag for Retry button appearance. 101 private boolean mShouldRetry = false; 102 private int mRetryCount = 0; 103 104 /** 105 * Constructor to be used by subclasses. 106 * 107 * @param testClass The class that contains the tests. It is dependant on test executor 108 * implemented by subclasses. 109 */ BaseSensorTestActivity(Class testClass)110 protected BaseSensorTestActivity(Class testClass) { 111 this(testClass, R.layout.sensor_test); 112 } 113 114 /** 115 * Constructor to be used by subclasses. It allows to provide a custom layout for the test UI. 116 * 117 * @param testClass The class that contains the tests. It is dependant on test executor 118 * implemented by subclasses. 119 * @param layoutId The Id of the layout to use for the test UI. The layout must contain all the 120 * elements in the base layout {@code R.layout.sensor_test}. 121 */ BaseSensorTestActivity(Class testClass, int layoutId)122 protected BaseSensorTestActivity(Class testClass, int layoutId) { 123 mTestClass = testClass; 124 mLayoutId = layoutId; 125 mSensorFeaturesDeactivator = new SensorFeaturesDeactivator(this); 126 } 127 128 @Override onCreate(Bundle savedInstanceState)129 protected void onCreate(Bundle savedInstanceState) { 130 super.onCreate(savedInstanceState); 131 setContentView(mLayoutId); 132 133 mLogScrollView = (ScrollView) findViewById(R.id.log_scroll_view); 134 mLogLayout = (LinearLayout) findViewById(R.id.log_layout); 135 mNextButton = (Button) findViewById(R.id.next_button); 136 mNextButton.setOnClickListener(this); 137 mPassButton = (Button) findViewById(R.id.pass_button); 138 mFailButton = (Button) findViewById(R.id.fail_button); 139 mGLSurfaceView = (GLSurfaceView) findViewById(R.id.gl_surface_view); 140 mRetryButton = (Button) findViewById(R.id.retry_button); 141 mRetryButton.setOnClickListener(new retryButtonListener()); 142 143 updateNextButton(false /*enabled*/); 144 mExecutorService.execute(this); 145 } 146 147 @Override onDestroy()148 protected void onDestroy() { 149 super.onDestroy(); 150 mExecutorService.shutdownNow(); 151 } 152 153 @Override onPause()154 protected void onPause() { 155 super.onPause(); 156 if (mUsingGlSurfaceView) { 157 mGLSurfaceView.onPause(); 158 } 159 } 160 161 @Override onResume()162 protected void onResume() { 163 super.onResume(); 164 if (mUsingGlSurfaceView) { 165 mGLSurfaceView.onResume(); 166 } 167 } 168 169 @Override onClick(View target)170 public void onClick(View target) { 171 mShouldRetry = false; 172 173 synchronized (mWaitForUserLatches) { 174 for (CountDownLatch latch : mWaitForUserLatches) { 175 latch.countDown(); 176 } 177 mWaitForUserLatches.clear(); 178 } 179 } 180 181 private class retryButtonListener implements View.OnClickListener { 182 183 @Override onClick(View v)184 public void onClick(View v) { 185 mShouldRetry = true; 186 ++mRetryCount; 187 188 synchronized (mWaitForUserLatches) { 189 for (CountDownLatch latch : mWaitForUserLatches) { 190 latch.countDown(); 191 } 192 mWaitForUserLatches.clear(); 193 } 194 } 195 } 196 197 @Override onActivityResult(int requestCode, int resultCode, Intent data)198 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 199 mActivityResultMultiplexedLatch.onActivityResult(requestCode, resultCode); 200 } 201 202 /** 203 * The main execution {@link Thread}. 204 * 205 * This function executes in a background thread, allowing the test run freely behind the 206 * scenes. It provides the following execution hooks: 207 * - Activity SetUp/CleanUp (not available in JUnit) 208 * - executeTests: to implement several execution engines 209 */ 210 @Override run()211 public void run() { 212 long startTimeNs = SystemClock.elapsedRealtimeNanos(); 213 String testName = getTestClassName(); 214 215 SensorTestDetails testDetails; 216 try { 217 mSensorFeaturesDeactivator.requestDeactivationOfFeatures(); 218 testDetails = new SensorTestDetails(testName, SensorTestDetails.ResultCode.PASS); 219 } catch (Throwable e) { 220 testDetails = new SensorTestDetails(testName, "DeactivateSensorFeatures", e); 221 } 222 223 SensorTestDetails.ResultCode resultCode = testDetails.getResultCode(); 224 if (resultCode == SensorTestDetails.ResultCode.SKIPPED) { 225 // this is an invalid state at this point of the test setup 226 throw new IllegalStateException("Deactivation of features cannot skip the test."); 227 } 228 if (resultCode == SensorTestDetails.ResultCode.PASS) { 229 testDetails = executeActivityTests(testName); 230 } 231 232 // we consider all remaining states at this point, because we could have been half way 233 // deactivating features 234 try { 235 mSensorFeaturesDeactivator.requestToRestoreFeatures(); 236 } catch (Throwable e) { 237 testDetails = new SensorTestDetails(testName, "RestoreSensorFeatures", e); 238 } 239 240 mTestLogger.logTestDetails(testDetails); 241 mTestLogger.logExecutionTime(startTimeNs); 242 243 // because we cannot enforce test failures in several devices, set the test UI so the 244 // operator can report the result of the test 245 promptUserToSetResult(testDetails); 246 } 247 248 /** 249 * A general set up routine. It executes only once before the first test case. 250 * 251 * NOTE: implementers must be aware of the interrupted status of the worker thread, and let 252 * {@link InterruptedException} propagate. 253 * 254 * @throws Throwable An exception that denotes the failure of set up. No tests will be executed. 255 */ activitySetUp()256 protected void activitySetUp() throws Throwable {} 257 258 /** 259 * A general clean up routine. It executes upon successful execution of {@link #activitySetUp()} 260 * and after all the test cases. 261 * 262 * NOTE: implementers must be aware of the interrupted status of the worker thread, and handle 263 * it in two cases: 264 * - let {@link InterruptedException} propagate 265 * - if it is invoked with the interrupted status, prevent from showing any UI 266 267 * @throws Throwable An exception that will be logged and ignored, for ease of implementation 268 * by subclasses. 269 */ activityCleanUp()270 protected void activityCleanUp() throws Throwable {} 271 272 /** 273 * Performs the work of executing the tests. 274 * Sub-classes implementing different execution methods implement this method. 275 * 276 * @return A {@link SensorTestDetails} object containing information about the executed tests. 277 */ executeTests()278 protected abstract SensorTestDetails executeTests() throws InterruptedException; 279 280 /** 281 * Get mShouldRetry to check if test is required to retry. 282 */ getShouldRetry()283 protected boolean getShouldRetry() { 284 return mShouldRetry; 285 } 286 287 @Override getTestLogger()288 public SensorTestLogger getTestLogger() { 289 return mTestLogger; 290 } 291 292 @Deprecated appendText(int resId)293 protected void appendText(int resId) { 294 mTestLogger.logInstructions(resId); 295 } 296 297 @Deprecated appendText(String text)298 protected void appendText(String text) { 299 TextAppender textAppender = new TextAppender(R.layout.snsr_instruction); 300 textAppender.setText(text); 301 textAppender.append(); 302 } 303 304 @Deprecated clearText()305 protected void clearText() { 306 this.runOnUiThread(new Runnable() { 307 @Override 308 public void run() { 309 mLogLayout.removeAllViews(); 310 } 311 }); 312 } 313 314 /** 315 * Waits for the operator to acknowledge a requested action. 316 * 317 * @param waitMessageResId The action requested to the operator. 318 */ waitForUser(int waitMessageResId)319 protected void waitForUser(int waitMessageResId) throws InterruptedException { 320 CountDownLatch latch = new CountDownLatch(1); 321 synchronized (mWaitForUserLatches) { 322 mWaitForUserLatches.add(latch); 323 } 324 325 mTestLogger.logInstructions(waitMessageResId); 326 setNextButtonText(waitMessageResId); 327 328 updateRetryButton(true); 329 updateNextButton(true); 330 latch.await(); 331 updateRetryButton(false); 332 updateNextButton(false); 333 } 334 335 /** 336 * Waits for the operator to acknowledge to begin execution. 337 */ waitForUserToBegin()338 protected void waitForUserToBegin() throws InterruptedException { 339 waitForUser(R.string.snsr_wait_to_begin); 340 } 341 342 /** 343 * Waits for the operator to acknowledge to retry execution. 344 */ waitForUserToRetry()345 protected void waitForUserToRetry() throws InterruptedException { 346 mShouldRetry = true; 347 waitForUser(R.string.snsr_wait_to_retry); 348 } 349 350 /** 351 * Waits for the operator to acknowledge to finish execution. 352 */ waitForUserToFinish()353 protected void waitForUserToFinish() throws InterruptedException { 354 mShouldRetry = true; 355 waitForUser(R.string.snsr_wait_to_finish); 356 } 357 358 /** 359 * {@inheritDoc} 360 */ 361 @Override waitForUserToContinue()362 public void waitForUserToContinue() throws InterruptedException { 363 waitForUser(R.string.snsr_wait_for_user); 364 } 365 366 /** 367 * {@inheritDoc} 368 */ 369 @Override executeActivity(String action)370 public int executeActivity(String action) throws InterruptedException { 371 return executeActivity(new Intent(action)); 372 } 373 374 /** 375 * {@inheritDoc} 376 */ 377 @Override executeActivity(Intent intent)378 public int executeActivity(Intent intent) throws InterruptedException { 379 ActivityResultMultiplexedLatch.Latch latch = mActivityResultMultiplexedLatch.bindThread(); 380 try { 381 startActivityForResult(intent, latch.getRequestCode()); 382 } catch (ActivityNotFoundException e) { 383 // handle exception gracefully 384 // Among all defined activity results, RESULT_CANCELED offers the semantic closest to 385 // represent absent setting activity. 386 return RESULT_CANCELED; 387 } 388 return latch.await(); 389 } 390 391 /** 392 * {@inheritDoc} 393 */ 394 @Override hasSystemFeature(String feature)395 public boolean hasSystemFeature(String feature) { 396 PackageManager pm = getPackageManager(); 397 return pm.hasSystemFeature(feature); 398 } 399 400 /** 401 * {@inheritDoc} 402 */ 403 @Override hasActivity(String action)404 public boolean hasActivity(String action) { 405 PackageManager pm = getPackageManager(); 406 return pm.resolveActivity(new Intent(action), PackageManager.MATCH_DEFAULT_ONLY) != null; 407 } 408 409 /** 410 * Initializes and shows the {@link GLSurfaceView} available to tests. 411 * NOTE: initialization can be performed only once, usually inside {@link #activitySetUp()}. 412 */ initializeGlSurfaceView(final GLSurfaceView.Renderer renderer)413 protected void initializeGlSurfaceView(final GLSurfaceView.Renderer renderer) { 414 runOnUiThread(new Runnable() { 415 @Override 416 public void run() { 417 mGLSurfaceView.setVisibility(View.VISIBLE); 418 mGLSurfaceView.setRenderer(renderer); 419 mUsingGlSurfaceView = true; 420 } 421 }); 422 } 423 424 /** 425 * Closes and hides the {@link GLSurfaceView}. 426 */ closeGlSurfaceView()427 protected void closeGlSurfaceView() { 428 runOnUiThread(new Runnable() { 429 @Override 430 public void run() { 431 if (!mUsingGlSurfaceView) { 432 return; 433 } 434 mGLSurfaceView.setVisibility(View.GONE); 435 mGLSurfaceView.onPause(); 436 mUsingGlSurfaceView = false; 437 } 438 }); 439 } 440 441 /** 442 * Plays a (default) sound as a notification for the operator. 443 */ playSound()444 protected void playSound() throws InterruptedException { 445 MediaPlayer player = MediaPlayer.create(this, Settings.System.DEFAULT_NOTIFICATION_URI); 446 if (player == null) { 447 Log.e(LOG_TAG, "MediaPlayer unavailable."); 448 return; 449 } 450 player.start(); 451 try { 452 Thread.sleep(500); 453 } finally { 454 player.stop(); 455 } 456 } 457 458 /** 459 * Makes the device vibrate for the given amount of time. 460 */ vibrate(int timeInMs)461 protected void vibrate(int timeInMs) { 462 Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); 463 vibrator.vibrate(timeInMs); 464 } 465 466 /** 467 * Makes the device vibrate following the given pattern. 468 * See {@link Vibrator#vibrate(long[], int)} for more information. 469 */ vibrate(long[] pattern)470 protected void vibrate(long[] pattern) { 471 Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); 472 vibrator.vibrate(pattern, -1); 473 } 474 475 // TODO: move to sensor assertions assertTimestampSynchronization( long eventTimestamp, long receivedTimestamp, long deltaThreshold, String sensorName)476 protected String assertTimestampSynchronization( 477 long eventTimestamp, 478 long receivedTimestamp, 479 long deltaThreshold, 480 String sensorName) { 481 long timestampDelta = Math.abs(eventTimestamp - receivedTimestamp); 482 String timestampMessage = getString( 483 R.string.snsr_event_time, 484 receivedTimestamp, 485 eventTimestamp, 486 timestampDelta, 487 deltaThreshold, 488 sensorName); 489 Assert.assertTrue(timestampMessage, timestampDelta < deltaThreshold); 490 return timestampMessage; 491 } 492 getTestClassName()493 protected String getTestClassName() { 494 if (mTestClass == null) { 495 return "<unknown>"; 496 } 497 return mTestClass.getName(); 498 } 499 setLogScrollViewListener(View.OnTouchListener listener)500 protected void setLogScrollViewListener(View.OnTouchListener listener) { 501 mLogScrollView.setOnTouchListener(listener); 502 } 503 setTestResult(SensorTestDetails testDetails)504 private void setTestResult(SensorTestDetails testDetails) { 505 // the name here, must be the Activity's name because it is what CtsVerifier expects 506 String name = super.getClass().getName(); 507 String summary = mTestLogger.getOverallSummary(); 508 SensorTestDetails.ResultCode resultCode = testDetails.getResultCode(); 509 switch(resultCode) { 510 case SKIPPED: 511 TestResult.setPassedResult(this, name, summary); 512 break; 513 case PASS: 514 TestResult.setPassedResult(this, name, summary); 515 break; 516 case FAIL: 517 TestResult.setFailedResult(this, name, summary); 518 break; 519 case INTERRUPTED: 520 // do not set a result, just return so the test can complete 521 break; 522 default: 523 throw new IllegalStateException("Unknown ResultCode: " + resultCode); 524 } 525 } 526 executeActivityTests(String testName)527 private SensorTestDetails executeActivityTests(String testName) { 528 SensorTestDetails testDetails; 529 try { 530 activitySetUp(); 531 testDetails = new SensorTestDetails(testName, SensorTestDetails.ResultCode.PASS); 532 } catch (Throwable e) { 533 testDetails = new SensorTestDetails(testName, "ActivitySetUp", e); 534 } 535 536 SensorTestDetails.ResultCode resultCode = testDetails.getResultCode(); 537 if (resultCode == SensorTestDetails.ResultCode.PASS) { 538 // TODO: implement execution filters: 539 // - execute all tests and report results officially 540 // - execute single test or failed tests only 541 try { 542 testDetails = executeTests(); 543 } catch (Throwable e) { 544 // we catch and continue because we have to guarantee a proper clean-up sequence 545 testDetails = new SensorTestDetails(testName, "TestExecution", e); 546 } 547 } 548 549 // clean-up executes for all states, even on SKIPPED and INTERRUPTED there might be some 550 // intermediate state that needs to be taken care of 551 try { 552 activityCleanUp(); 553 } catch (Throwable e) { 554 testDetails = new SensorTestDetails(testName, "ActivityCleanUp", e); 555 } 556 557 return testDetails; 558 } 559 promptUserToSetResult(SensorTestDetails testDetails)560 private void promptUserToSetResult(SensorTestDetails testDetails) { 561 SensorTestDetails.ResultCode resultCode = testDetails.getResultCode(); 562 if (resultCode == SensorTestDetails.ResultCode.FAIL) { 563 mTestLogger.logInstructions(R.string.snsr_test_complete_with_errors); 564 enableTestResultButton( 565 mFailButton, 566 R.string.fail_button_text, 567 testDetails.cloneAndChangeResultCode(SensorTestDetails.ResultCode.FAIL)); 568 } else if (resultCode != SensorTestDetails.ResultCode.INTERRUPTED) { 569 mTestLogger.logInstructions(R.string.snsr_test_complete); 570 enableTestResultButton( 571 mPassButton, 572 R.string.pass_button_text, 573 testDetails.cloneAndChangeResultCode(SensorTestDetails.ResultCode.PASS)); 574 } 575 } 576 updateNextButton(final boolean enabled)577 private void updateNextButton(final boolean enabled) { 578 runOnUiThread(new Runnable() { 579 @Override 580 public void run() { 581 mNextButton.setEnabled(enabled); 582 } 583 }); 584 } 585 586 /** 587 * Set the text for next button by instruction message. 588 * During retry, next button text is changed to notify users. 589 * 590 * @param waitMessageResId The action requested to the operator. 591 */ setNextButtonText(int waitMessageResId)592 private void setNextButtonText(int waitMessageResId) { 593 int nextButtonText; 594 switch (waitMessageResId) { 595 case R.string.snsr_wait_to_retry: 596 nextButtonText = R.string.fail_and_next_button_text; 597 break; 598 case R.string.snsr_wait_to_finish: 599 nextButtonText = R.string.finish_button_text; 600 break; 601 default: 602 nextButtonText = R.string.next_button_text; 603 break; 604 } 605 runOnUiThread(new Runnable() { 606 @Override 607 public void run() { 608 mNextButton.setText(nextButtonText); 609 } 610 }); 611 } 612 613 /** 614 * Update the retry button status. 615 * During retry, show retry execution count. If not to retry, make retry button invisible. 616 * 617 * @param enabled The status of button. 618 */ updateRetryButton(final boolean enabled)619 private void updateRetryButton(final boolean enabled) { 620 runOnUiThread(new Runnable() { 621 @Override 622 public void run() { 623 if (mShouldRetry) { 624 String showRetryCount = String.format( 625 "%s (%d)", getResources().getText(R.string.retry_button_text), mRetryCount); 626 mRetryButton.setText(showRetryCount); 627 mRetryButton.setVisibility(View.VISIBLE); 628 mRetryButton.setEnabled(enabled); 629 } else { 630 mRetryButton.setVisibility(View.GONE); 631 mRetryCount = 0; 632 } 633 } 634 }); 635 } 636 enableTestResultButton( final Button button, final int textResId, final SensorTestDetails testDetails)637 private void enableTestResultButton( 638 final Button button, 639 final int textResId, 640 final SensorTestDetails testDetails) { 641 final View.OnClickListener listener = new View.OnClickListener() { 642 @Override 643 public void onClick(View v) { 644 setTestResult(testDetails); 645 finish(); 646 } 647 }; 648 649 runOnUiThread(new Runnable() { 650 @Override 651 public void run() { 652 mNextButton.setVisibility(View.GONE); 653 button.setText(textResId); 654 button.setOnClickListener(listener); 655 button.setVisibility(View.VISIBLE); 656 } 657 }); 658 } 659 660 // a logger available until sensor reporting is in place 661 public class SensorTestLogger { 662 private static final String SUMMARY_SEPARATOR = " | "; 663 664 private final StringBuilder mOverallSummaryBuilder = new StringBuilder("\n"); 665 logCustomView(View view)666 public void logCustomView(View view) { 667 new ViewAppender(view).append(); 668 } 669 logTestStart(String testName)670 void logTestStart(String testName) { 671 // TODO: log the sensor information and expected execution time of each test 672 TextAppender textAppender = new TextAppender(R.layout.snsr_test_title); 673 textAppender.setText(testName); 674 textAppender.append(); 675 } 676 logInstructions(int instructionsResId, Object ... params)677 public void logInstructions(int instructionsResId, Object ... params) { 678 TextAppender textAppender = new TextAppender(R.layout.snsr_instruction); 679 textAppender.setText(getString(instructionsResId, params)); 680 textAppender.append(); 681 } 682 logMessage(int messageResId, Object ... params)683 public void logMessage(int messageResId, Object ... params) { 684 TextAppender textAppender = new TextAppender(R.layout.snsr_message); 685 textAppender.setText(getString(messageResId, params)); 686 textAppender.append(); 687 } 688 logWaitForSound()689 public void logWaitForSound() { 690 logInstructions(R.string.snsr_test_play_sound); 691 } 692 logTestDetails(SensorTestDetails testDetails)693 public void logTestDetails(SensorTestDetails testDetails) { 694 String name = testDetails.getName(); 695 String summary = testDetails.getSummary(); 696 SensorTestDetails.ResultCode resultCode = testDetails.getResultCode(); 697 switch (resultCode) { 698 case SKIPPED: 699 logTestSkip(name, summary); 700 break; 701 case PASS: 702 mShouldRetry = false; 703 logTestPass(name, summary); 704 break; 705 case FAIL: 706 logTestFail(name, summary); 707 break; 708 case INTERRUPTED: 709 // do nothing, the test was interrupted so do we 710 break; 711 default: 712 throw new IllegalStateException("Unknown ResultCode: " + resultCode); 713 } 714 } 715 logTestPass(String testName, String testSummary)716 void logTestPass(String testName, String testSummary) { 717 testSummary = getValidTestSummary(testSummary, R.string.snsr_test_pass); 718 logTestEnd(R.layout.snsr_success, testSummary); 719 Log.d(LOG_TAG, testSummary); 720 saveResult(testName, SensorTestDetails.ResultCode.PASS, testSummary); 721 } 722 logTestFail(String testName, String testSummary)723 public void logTestFail(String testName, String testSummary) { 724 testSummary = getValidTestSummary(testSummary, R.string.snsr_test_fail); 725 logTestEnd(R.layout.snsr_error, testSummary); 726 Log.e(LOG_TAG, testSummary); 727 saveResult(testName, SensorTestDetails.ResultCode.FAIL, testSummary); 728 } 729 logTestSkip(String testName, String testSummary)730 void logTestSkip(String testName, String testSummary) { 731 testSummary = getValidTestSummary(testSummary, R.string.snsr_test_skipped); 732 logTestEnd(R.layout.snsr_warning, testSummary); 733 Log.i(LOG_TAG, testSummary); 734 saveResult(testName, SensorTestDetails.ResultCode.SKIPPED, testSummary); 735 } 736 getOverallSummary()737 String getOverallSummary() { 738 return mOverallSummaryBuilder.toString(); 739 } 740 logExecutionTime(long startTimeNs)741 void logExecutionTime(long startTimeNs) { 742 if (Thread.currentThread().isInterrupted()) { 743 return; 744 } 745 long executionTimeNs = SystemClock.elapsedRealtimeNanos() - startTimeNs; 746 long executionTimeSec = TimeUnit.NANOSECONDS.toSeconds(executionTimeNs); 747 // TODO: find a way to format times with nanosecond accuracy and longer than 24hrs 748 String formattedElapsedTime = DateUtils.formatElapsedTime(executionTimeSec); 749 logMessage(R.string.snsr_execution_time, formattedElapsedTime); 750 } 751 logTestEnd(int textViewResId, String testSummary)752 private void logTestEnd(int textViewResId, String testSummary) { 753 TextAppender textAppender = new TextAppender(textViewResId); 754 textAppender.setText(testSummary); 755 textAppender.append(); 756 } 757 getValidTestSummary(String testSummary, int defaultSummaryResId)758 private String getValidTestSummary(String testSummary, int defaultSummaryResId) { 759 if (TextUtils.isEmpty(testSummary)) { 760 return getString(defaultSummaryResId); 761 } 762 return testSummary; 763 } 764 saveResult( String testName, SensorTestDetails.ResultCode resultCode, String summary)765 private void saveResult( 766 String testName, 767 SensorTestDetails.ResultCode resultCode, 768 String summary) { 769 mOverallSummaryBuilder.append(testName); 770 mOverallSummaryBuilder.append(SUMMARY_SEPARATOR); 771 mOverallSummaryBuilder.append(resultCode.name()); 772 mOverallSummaryBuilder.append(SUMMARY_SEPARATOR); 773 mOverallSummaryBuilder.append(summary); 774 mOverallSummaryBuilder.append("\n"); 775 } 776 } 777 778 private class ViewAppender { 779 protected final View mView; 780 ViewAppender(View view)781 public ViewAppender(View view) { 782 mView = view; 783 } 784 append()785 public void append() { 786 runOnUiThread(new Runnable() { 787 @Override 788 public void run() { 789 mLogLayout.addView(mView); 790 mLogScrollView.post(new Runnable() { 791 @Override 792 public void run() { 793 mLogScrollView.fullScroll(View.FOCUS_DOWN); 794 } 795 }); 796 } 797 }); 798 } 799 } 800 801 private class TextAppender extends ViewAppender{ 802 private final TextView mTextView; 803 TextAppender(int textViewResId)804 public TextAppender(int textViewResId) { 805 super(getLayoutInflater().inflate(textViewResId, null /* viewGroup */)); 806 mTextView = (TextView) mView; 807 } 808 setText(String text)809 public void setText(String text) { 810 mTextView.setText(text); 811 } 812 setText(int textResId)813 public void setText(int textResId) { 814 mTextView.setText(textResId); 815 } 816 } 817 } 818