Home | History | Annotate | Download | only in base
      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      */
     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      */
    106     protected BaseGnssTestActivity(Class testClass, int layoutId) {
    107         mTestClass = testClass;
    108         mLayoutId = layoutId;
    109     }
    110 
    111     @Override
    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
    129     protected void onDestroy() {
    130         super.onDestroy();
    131         mExecutorService.shutdownNow();
    132     }
    133 
    134     @Override
    135     protected void onPause() {
    136         super.onPause();
    137     }
    138 
    139     @Override
    140     protected void onResume() {
    141         super.onResume();
    142     }
    143 
    144     @Override
    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
    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
    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      */
    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      */
    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      */
    222     protected abstract GnssTestDetails executeTests() throws InterruptedException;
    223 
    224     @Deprecated
    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
    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      */
    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      */
    260     protected void waitForUserToBegin() throws InterruptedException {
    261         waitForUser(R.string.snsr_wait_to_begin);
    262     }
    263 
    264     /**
    265      * {@inheritDoc}
    266      */
    267     @Override
    268     public void waitForUserToContinue() throws InterruptedException {
    269         waitForUser(R.string.snsr_wait_for_user);
    270     }
    271 
    272     /**
    273      * {@inheritDoc}
    274      */
    275     @Override
    276     public int executeActivity(String action) throws InterruptedException {
    277         return executeActivity(new Intent(action));
    278     }
    279 
    280     /**
    281      * {@inheritDoc}
    282      */
    283     @Override
    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      */
    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      */
    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      */
    319     protected void vibrate(long[] pattern) {
    320         Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
    321         vibrator.vibrate(pattern, -1);
    322     }
    323 
    324     protected String getTestClassName() {
    325         if (mTestClass == null) {
    326             return "<unknown>";
    327         }
    328         return mTestClass.getName();
    329     }
    330 
    331     protected void setLogScrollViewListener(View.OnTouchListener listener) {
    332         mLogScrollView.setOnTouchListener(listener);
    333     }
    334 
    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 
    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 
    390     private void updateResult(final GnssTestDetails testDetails) {
    391         runOnUiThread(new Runnable() {
    392             @Override
    393             public void run() {
    394                 setTestResult(testDetails);
    395             }
    396         });
    397     }
    398 
    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 
    411         public ViewAppender(View view) {
    412             mView = view;
    413         }
    414 
    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 
    434         public TextAppender(int textViewResId) {
    435             super(getLayoutInflater().inflate(textViewResId, null /* viewGroup */));
    436             mTextView = (TextView) mView;
    437         }
    438 
    439         public void setText(String text) {
    440             mTextView.setText(text);
    441         }
    442 
    443         public void setText(int textResId) {
    444             mTextView.setText(textResId);
    445         }
    446     }
    447 }