1 /* 2 * Copyright (C) 2016 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.location.base; 18 19 import com.android.cts.verifier.R; 20 import com.android.cts.verifier.TestResult; 21 import com.android.cts.verifier.location.reporting.GnssTestDetails; 22 23 import junit.framework.Assert; 24 25 import com.android.cts.verifier.PassFailButtons; 26 27 import android.content.Context; 28 import android.content.Intent; 29 import android.hardware.cts.helpers.ActivityResultMultiplexedLatch; 30 import android.media.MediaPlayer; 31 import android.os.Bundle; 32 import android.os.SystemClock; 33 import android.os.Vibrator; 34 import android.provider.Settings; 35 import android.text.TextUtils; 36 import android.text.format.DateUtils; 37 import android.util.Log; 38 import android.view.View; 39 import android.widget.Button; 40 import android.widget.LinearLayout; 41 import android.widget.ScrollView; 42 import android.widget.TextView; 43 44 import java.util.ArrayList; 45 import java.util.concurrent.CountDownLatch; 46 import java.util.concurrent.ExecutorService; 47 import java.util.concurrent.Executors; 48 import java.util.concurrent.TimeUnit; 49 50 import android.test.AndroidTestCase; 51 52 /** 53 * A base Activity that is used to build different methods to execute tests inside CtsVerifier. 54 * i.e. CTS tests, and semi-automated CtsVerifier tests. 55 * 56 * This class provides access to the following flow: 57 * Activity set up 58 * Execute tests (implemented by sub-classes) 59 * Activity clean up 60 * 61 * Currently the following class structure is available: 62 * - BaseGnssTestActivity : provides the platform to execute Gnss tests inside 63 * | CtsVerifier. 64 * | 65 * -- GnssCtsTestActivity : an activity that can be inherited from to wrap a CTS 66 * | Gnss test, and execute it inside CtsVerifier 67 * | these tests do not require any operator interaction 68 */ 69 public abstract class BaseGnssTestActivity extends PassFailButtons.Activity 70 implements View.OnClickListener, Runnable, IGnssTestStateContainer { 71 @Deprecated 72 protected static final String LOG_TAG = "GnssTest"; 73 74 protected final Class mTestClass; 75 76 private final int mLayoutId; 77 78 private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor(); 79 private final ActivityResultMultiplexedLatch mActivityResultMultiplexedLatch = 80 new ActivityResultMultiplexedLatch(); 81 private final ArrayList<CountDownLatch> mWaitForUserLatches = new ArrayList<CountDownLatch>(); 82 83 private ScrollView mLogScrollView; 84 private LinearLayout mLogLayout; 85 private Button mNextButton; 86 protected TextView mTextView; 87 88 /** 89 * Constructor to be used by subclasses. 90 * 91 * @param testClass The class that contains the tests. It is dependant on test executor 92 * implemented by subclasses. 93 */ BaseGnssTestActivity(Class<? extends AndroidTestCase> testClass)94 protected BaseGnssTestActivity(Class<? extends AndroidTestCase> testClass) { 95 this(testClass, R.layout.gnss_test); 96 } 97 98 /** 99 * Constructor to be used by subclasses. It allows to provide a custom layout for the test UI. 100 * 101 * @param testClass The class that contains the tests. It is dependant on test executor 102 * implemented by subclasses. 103 * @param layoutId The Id of the layout to use for the test UI. The layout must contain all the 104 * elements in the base layout {@code R.layout.gnss_test}. 105 */ BaseGnssTestActivity(Class testClass, int layoutId)106 protected BaseGnssTestActivity(Class testClass, int layoutId) { 107 mTestClass = testClass; 108 mLayoutId = layoutId; 109 } 110 111 @Override onCreate(Bundle savedInstanceState)112 protected void onCreate(Bundle savedInstanceState) { 113 super.onCreate(savedInstanceState); 114 setContentView(mLayoutId); 115 116 mLogScrollView = (ScrollView) findViewById(R.id.log_scroll_view); 117 mLogLayout = (LinearLayout) findViewById(R.id.log_layout); 118 mNextButton = (Button) findViewById(R.id.next_button); 119 mNextButton.setOnClickListener(this); 120 mTextView = (TextView) findViewById(R.id.text); 121 122 mTextView.setText(R.string.location_gnss_test_info); 123 124 updateNextButton(false /*not enabled*/); 125 mExecutorService.execute(this); 126 } 127 128 @Override onDestroy()129 protected void onDestroy() { 130 super.onDestroy(); 131 mExecutorService.shutdownNow(); 132 } 133 134 @Override onPause()135 protected void onPause() { 136 super.onPause(); 137 } 138 139 @Override onResume()140 protected void onResume() { 141 super.onResume(); 142 } 143 144 @Override onClick(View target)145 public void onClick(View target) { 146 synchronized (mWaitForUserLatches) { 147 for (CountDownLatch latch : mWaitForUserLatches) { 148 latch.countDown(); 149 } 150 mWaitForUserLatches.clear(); 151 } 152 } 153 154 @Override onActivityResult(int requestCode, int resultCode, Intent data)155 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 156 mActivityResultMultiplexedLatch.onActivityResult(requestCode, resultCode); 157 } 158 159 /** 160 * The main execution {@link Thread}. 161 * 162 * This function executes in a background thread, allowing the test run freely behind the 163 * scenes. It provides the following execution hooks: 164 * - Activity SetUp/CleanUp (not available in JUnit) 165 * - executeTests: to implement several execution engines 166 */ 167 @Override run()168 public void run() { 169 long startTimeNs = SystemClock.elapsedRealtimeNanos(); 170 String testName = getTestClassName(); 171 172 GnssTestDetails testDetails; 173 try { 174 testDetails = new GnssTestDetails(testName, GnssTestDetails.ResultCode.PASS); 175 } catch (Throwable e) { 176 testDetails = new GnssTestDetails(testName, "DeactivateFeatures", e); 177 } 178 179 GnssTestDetails.ResultCode resultCode = testDetails.getResultCode(); 180 if (resultCode == GnssTestDetails.ResultCode.SKIPPED) { 181 // this is an invalid state at this point of the test setup 182 throw new IllegalStateException("Deactivation of features cannot skip the test."); 183 } 184 if (resultCode == GnssTestDetails.ResultCode.PASS) { 185 testDetails = executeActivityTests(testName); 186 } 187 188 // This set the test UI so the operator can report the result of the test 189 updateResult(testDetails); 190 } 191 192 /** 193 * A general set up routine. It executes only once before the first test case. 194 * 195 * NOTE: implementers must be aware of the interrupted status of the worker thread, and let 196 * {@link InterruptedException} propagate. 197 * 198 * @throws Throwable An exception that denotes the failure of set up. No tests will be executed. 199 */ activitySetUp()200 protected void activitySetUp() throws Throwable {} 201 202 /** 203 * A general clean up routine. It executes upon successful execution of {@link #activitySetUp()} 204 * and after all the test cases. 205 * 206 * NOTE: implementers must be aware of the interrupted status of the worker thread, and handle 207 * it in two cases: 208 * - let {@link InterruptedException} propagate 209 * - if it is invoked with the interrupted status, prevent from showing any UI 210 211 * @throws Throwable An exception that will be logged and ignored, for ease of implementation 212 * by subclasses. 213 */ activityCleanUp()214 protected void activityCleanUp() throws Throwable {} 215 216 /** 217 * Performs the work of executing the tests. 218 * Sub-classes implementing different execution methods implement this method. 219 * 220 * @return A {@link GnssTestDetails} object containing information about the executed tests. 221 */ executeTests()222 protected abstract GnssTestDetails executeTests() throws InterruptedException; 223 224 @Deprecated appendText(String text)225 protected void appendText(String text) { 226 TextAppender textAppender = new TextAppender(R.layout.snsr_instruction); 227 textAppender.setText(text); 228 textAppender.append(); 229 } 230 231 @Deprecated clearText()232 protected void clearText() { 233 this.runOnUiThread(new Runnable() { 234 @Override 235 public void run() { 236 mLogLayout.removeAllViews(); 237 } 238 }); 239 } 240 241 /** 242 * Waits for the operator to acknowledge a requested action. 243 * 244 * @param waitMessageResId The action requested to the operator. 245 */ waitForUser(int waitMessageResId)246 protected void waitForUser(int waitMessageResId) throws InterruptedException { 247 CountDownLatch latch = new CountDownLatch(1); 248 synchronized (mWaitForUserLatches) { 249 mWaitForUserLatches.add(latch); 250 } 251 252 updateNextButton(true); 253 latch.await(); 254 updateNextButton(false); 255 } 256 257 /** 258 * Waits for the operator to acknowledge to begin execution. 259 */ waitForUserToBegin()260 protected void waitForUserToBegin() throws InterruptedException { 261 waitForUser(R.string.snsr_wait_to_begin); 262 } 263 264 /** 265 * {@inheritDoc} 266 */ 267 @Override waitForUserToContinue()268 public void waitForUserToContinue() throws InterruptedException { 269 waitForUser(R.string.snsr_wait_for_user); 270 } 271 272 /** 273 * {@inheritDoc} 274 */ 275 @Override executeActivity(String action)276 public int executeActivity(String action) throws InterruptedException { 277 return executeActivity(new Intent(action)); 278 } 279 280 /** 281 * {@inheritDoc} 282 */ 283 @Override executeActivity(Intent intent)284 public int executeActivity(Intent intent) throws InterruptedException { 285 ActivityResultMultiplexedLatch.Latch latch = mActivityResultMultiplexedLatch.bindThread(); 286 startActivityForResult(intent, latch.getRequestCode()); 287 return latch.await(); 288 } 289 290 /** 291 * Plays a (default) sound as a notification for the operator. 292 */ playSound()293 protected void playSound() throws InterruptedException { 294 MediaPlayer player = MediaPlayer.create(this, Settings.System.DEFAULT_NOTIFICATION_URI); 295 if (player == null) { 296 Log.e(LOG_TAG, "MediaPlayer unavailable."); 297 return; 298 } 299 player.start(); 300 try { 301 Thread.sleep(500); 302 } finally { 303 player.stop(); 304 } 305 } 306 307 /** 308 * Makes the device vibrate for the given amount of time. 309 */ vibrate(int timeInMs)310 protected void vibrate(int timeInMs) { 311 Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); 312 vibrator.vibrate(timeInMs); 313 } 314 315 /** 316 * Makes the device vibrate following the given pattern. 317 * See {@link Vibrator#vibrate(long[], int)} for more information. 318 */ vibrate(long[] pattern)319 protected void vibrate(long[] pattern) { 320 Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); 321 vibrator.vibrate(pattern, -1); 322 } 323 getTestClassName()324 protected String getTestClassName() { 325 if (mTestClass == null) { 326 return "<unknown>"; 327 } 328 return mTestClass.getName(); 329 } 330 setLogScrollViewListener(View.OnTouchListener listener)331 protected void setLogScrollViewListener(View.OnTouchListener listener) { 332 mLogScrollView.setOnTouchListener(listener); 333 } 334 setTestResult(GnssTestDetails testDetails)335 private void setTestResult(GnssTestDetails testDetails) { 336 // the name here, must be the Activity's name because it is what CtsVerifier expects 337 String name = super.getClass().getName(); 338 GnssTestDetails.ResultCode resultCode = testDetails.getResultCode(); 339 switch(resultCode) { 340 case SKIPPED: 341 TestResult.setPassedResult(this, name, ""); 342 break; 343 case PASS: 344 TestResult.setPassedResult(this, name, ""); 345 break; 346 case FAIL: 347 TestResult.setFailedResult(this, name, ""); 348 break; 349 case INTERRUPTED: 350 // do not set a result, just return so the test can complete 351 break; 352 default: 353 throw new IllegalStateException("Unknown ResultCode: " + resultCode); 354 } 355 } 356 executeActivityTests(String testName)357 private GnssTestDetails executeActivityTests(String testName) { 358 GnssTestDetails testDetails; 359 try { 360 activitySetUp(); 361 testDetails = new GnssTestDetails(testName, GnssTestDetails.ResultCode.PASS); 362 } catch (Throwable e) { 363 testDetails = new GnssTestDetails(testName, "ActivitySetUp", e); 364 } 365 366 GnssTestDetails.ResultCode resultCode = testDetails.getResultCode(); 367 if (resultCode == GnssTestDetails.ResultCode.PASS) { 368 // TODO: implement execution filters: 369 // - execute all tests and report results officially 370 // - execute single test or failed tests only 371 try { 372 testDetails = executeTests(); 373 } catch (Throwable e) { 374 // we catch and continue because we have to guarantee a proper clean-up sequence 375 testDetails = new GnssTestDetails(testName, "TestExecution", e); 376 } 377 } 378 379 // clean-up executes for all states, even on SKIPPED and INTERRUPTED there might be some 380 // intermediate state that needs to be taken care of 381 try { 382 activityCleanUp(); 383 } catch (Throwable e) { 384 testDetails = new GnssTestDetails(testName, "ActivityCleanUp", e); 385 } 386 387 return testDetails; 388 } 389 updateResult(final GnssTestDetails testDetails)390 private void updateResult(final GnssTestDetails testDetails) { 391 runOnUiThread(new Runnable() { 392 @Override 393 public void run() { 394 setTestResult(testDetails); 395 } 396 }); 397 } 398 updateNextButton(final boolean enabled)399 private void updateNextButton(final boolean enabled) { 400 runOnUiThread(new Runnable() { 401 @Override 402 public void run() { 403 mNextButton.setEnabled(enabled); 404 } 405 }); 406 } 407 408 private class ViewAppender { 409 protected final View mView; 410 ViewAppender(View view)411 public ViewAppender(View view) { 412 mView = view; 413 } 414 append()415 public void append() { 416 runOnUiThread(new Runnable() { 417 @Override 418 public void run() { 419 mLogLayout.addView(mView); 420 mLogScrollView.post(new Runnable() { 421 @Override 422 public void run() { 423 mLogScrollView.fullScroll(View.FOCUS_DOWN); 424 } 425 }); 426 } 427 }); 428 } 429 } 430 431 private class TextAppender extends ViewAppender{ 432 private final TextView mTextView; 433 TextAppender(int textViewResId)434 public TextAppender(int textViewResId) { 435 super(getLayoutInflater().inflate(textViewResId, null /* viewGroup */)); 436 mTextView = (TextView) mView; 437 } 438 setText(String text)439 public void setText(String text) { 440 mTextView.setText(text); 441 } 442 setText(int textResId)443 public void setText(int textResId) { 444 mTextView.setText(textResId); 445 } 446 } 447 }