Home | History | Annotate | Download | only in notifications
      1 /*
      2  * Copyright (C) 2013 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.notifications;
     18 
     19 import android.app.Activity;
     20 import android.app.NotificationManager;
     21 import android.app.PendingIntent;
     22 import android.app.Service;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.pm.PackageManager;
     27 import android.os.Bundle;
     28 import android.os.IBinder;
     29 import android.provider.Settings.Secure;
     30 import android.util.Log;
     31 import android.view.LayoutInflater;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import android.widget.Button;
     35 import android.widget.ImageView;
     36 import android.widget.TextView;
     37 import com.android.cts.verifier.PassFailButtons;
     38 import com.android.cts.verifier.R;
     39 
     40 import java.util.ArrayList;
     41 import java.util.Iterator;
     42 import java.util.List;
     43 import java.util.concurrent.LinkedBlockingQueue;
     44 
     45 public abstract class InteractiveVerifierActivity extends PassFailButtons.Activity
     46         implements Runnable {
     47     private static final String TAG = "InteractiveVerifier";
     48     private static final String STATE = "state";
     49     private static final String STATUS = "status";
     50     private static LinkedBlockingQueue<String> sDeletedQueue = new LinkedBlockingQueue<String>();
     51     protected static final String LISTENER_PATH = "com.android.cts.verifier/" +
     52             "com.android.cts.verifier.notifications.MockListener";
     53     protected static final int SETUP = 0;
     54     protected static final int READY = 1;
     55     protected static final int RETEST = 2;
     56     protected static final int PASS = 3;
     57     protected static final int FAIL = 4;
     58     protected static final int WAIT_FOR_USER = 5;
     59 
     60     protected static final int NOTIFICATION_ID = 1001;
     61 
     62     // TODO remove these once b/10023397 is fixed
     63     public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
     64     public static final String NOTIFICATION_LISTENER_SETTINGS =
     65             "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
     66 
     67     protected InteractiveTestCase mCurrentTest;
     68     protected PackageManager mPackageManager;
     69     protected NotificationManager mNm;
     70     protected Context mContext;
     71     protected Runnable mRunner;
     72     protected View mHandler;
     73     protected String mPackageString;
     74 
     75     private LayoutInflater mInflater;
     76     private ViewGroup mItemList;
     77     private List<InteractiveTestCase> mTestList;
     78     private Iterator<InteractiveTestCase> mTestOrder;
     79 
     80     public static class DismissService extends Service {
     81         @Override
     82         public IBinder onBind(Intent intent) {
     83             return null;
     84         }
     85 
     86         @Override
     87         public void onStart(Intent intent, int startId) {
     88             if(intent != null) { sDeletedQueue.offer(intent.getAction()); }
     89         }
     90     }
     91 
     92     protected abstract class InteractiveTestCase {
     93         int status;
     94         private View view;
     95 
     96         abstract View inflate(ViewGroup parent);
     97         View getView(ViewGroup parent) {
     98             if (view == null) {
     99                 view = inflate(parent);
    100             }
    101             return view;
    102         }
    103 
    104         /** @return true if the test should re-run when the test activity starts. */
    105         boolean autoStart() {
    106             return false;
    107         }
    108 
    109         /** Set status to {@link #READY} to proceed, or {@link #SETUP} to try again. */
    110         void setUp() { status = READY; next(); };
    111 
    112         /** Set status to {@link #PASS} or @{link #FAIL} to proceed, or {@link #READY} to retry. */
    113         void test() { status = FAIL; next(); };
    114 
    115         /** Do not modify status. */
    116         void tearDown() { next(); };
    117 
    118         protected void logFail() {
    119             logFail(null);
    120         }
    121 
    122         protected void logFail(String message) {
    123             logWithStack("failed " + this.getClass().getSimpleName() +
    124                     ((message == null) ? "" : ": " + message));
    125         }
    126     }
    127 
    128     abstract int getTitleResource();
    129     abstract int getInstructionsResource();
    130 
    131     protected void onCreate(Bundle savedState) {
    132         super.onCreate(savedState);
    133         int savedStateIndex = (savedState == null) ? 0 : savedState.getInt(STATE, 0);
    134         int savedStatus = (savedState == null) ? SETUP : savedState.getInt(STATUS, SETUP);
    135         Log.i(TAG, "restored state(" + savedStateIndex + "}, status(" + savedStatus + ")");
    136         mContext = this;
    137         mRunner = this;
    138         mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    139         mPackageManager = getPackageManager();
    140         mInflater = getLayoutInflater();
    141         View view = mInflater.inflate(R.layout.nls_main, null);
    142         mItemList = (ViewGroup) view.findViewById(R.id.nls_test_items);
    143         mHandler = mItemList;
    144         mTestList = new ArrayList<>();
    145         mTestList.addAll(createTestItems());
    146         for (InteractiveTestCase test: mTestList) {
    147             mItemList.addView(test.getView(mItemList));
    148         }
    149         mTestOrder = mTestList.iterator();
    150         for (int i = 0; i < savedStateIndex; i++) {
    151             mCurrentTest = mTestOrder.next();
    152             mCurrentTest.status = PASS;
    153         }
    154         mCurrentTest = mTestOrder.next();
    155         mCurrentTest.status = savedStatus;
    156 
    157         setContentView(view);
    158         setPassFailButtonClickListeners();
    159         getPassButton().setEnabled(false);
    160 
    161         setInfoResources(getTitleResource(), getInstructionsResource(), -1);
    162     }
    163 
    164     @Override
    165     protected void onSaveInstanceState (Bundle outState) {
    166         final int stateIndex = mTestList.indexOf(mCurrentTest);
    167         outState.putInt(STATE, stateIndex);
    168         outState.putInt(STATUS, mCurrentTest.status);
    169         Log.i(TAG, "saved state(" + stateIndex + "}, status(" + (mCurrentTest.status) + ")");
    170     }
    171 
    172     @Override
    173     protected void onResume() {
    174         super.onResume();
    175         if (mCurrentTest.autoStart()) {
    176             mCurrentTest.status = READY;
    177         }
    178         next();
    179     }
    180 
    181     // Interface Utilities
    182 
    183     protected void markItem(InteractiveTestCase test) {
    184         if (test == null) { return; }
    185         View item = test.view;
    186         ImageView status = (ImageView) item.findViewById(R.id.nls_status);
    187         View button = item.findViewById(R.id.nls_action_button);
    188         switch (test.status) {
    189             case WAIT_FOR_USER:
    190                 status.setImageResource(R.drawable.fs_warning);
    191                 break;
    192 
    193             case SETUP:
    194             case READY:
    195             case RETEST:
    196                 status.setImageResource(R.drawable.fs_clock);
    197                 break;
    198 
    199             case FAIL:
    200                 status.setImageResource(R.drawable.fs_error);
    201                 button.setClickable(false);
    202                 button.setEnabled(false);
    203                 break;
    204 
    205             case PASS:
    206                 status.setImageResource(R.drawable.fs_good);
    207                 button.setClickable(false);
    208                 button.setEnabled(false);
    209                 break;
    210 
    211         }
    212         status.invalidate();
    213     }
    214 
    215     protected View createNlsSettingsItem(ViewGroup parent, int messageId) {
    216         return createUserItem(parent, R.string.nls_start_settings, messageId);
    217     }
    218 
    219     protected View createRetryItem(ViewGroup parent, int messageId, Object... messageFormatArgs) {
    220         return createUserItem(parent, R.string.attention_ready, messageId, messageFormatArgs);
    221     }
    222 
    223     protected View createUserItem(ViewGroup parent, int actionId, int messageId,
    224             Object... messageFormatArgs) {
    225         View item = mInflater.inflate(R.layout.nls_item, parent, false);
    226         TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
    227         instructions.setText(getString(messageId, messageFormatArgs));
    228         Button button = (Button) item.findViewById(R.id.nls_action_button);
    229         button.setText(actionId);
    230         button.setTag(actionId);
    231         return item;
    232     }
    233 
    234     protected View  createAutoItem(ViewGroup parent, int stringId) {
    235         View item = mInflater.inflate(R.layout.nls_item, parent, false);
    236         TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
    237         instructions.setText(stringId);
    238         View button = item.findViewById(R.id.nls_action_button);
    239         button.setVisibility(View.GONE);
    240         return item;
    241     }
    242 
    243     // Test management
    244 
    245     abstract protected List<InteractiveTestCase> createTestItems();
    246 
    247     public void run() {
    248         if (mCurrentTest == null) { return; }
    249         markItem(mCurrentTest);
    250         switch (mCurrentTest.status) {
    251             case SETUP:
    252                 Log.i(TAG, "running setup for: " + mCurrentTest.getClass().getSimpleName());
    253                 mCurrentTest.setUp();
    254                 break;
    255 
    256             case WAIT_FOR_USER:
    257                 Log.i(TAG, "waiting for user: " + mCurrentTest.getClass().getSimpleName());
    258                 break;
    259 
    260             case READY:
    261             case RETEST:
    262                 Log.i(TAG, "running test for: " + mCurrentTest.getClass().getSimpleName());
    263                 mCurrentTest.test();
    264                 break;
    265 
    266             case FAIL:
    267                 Log.i(TAG, "FAIL: " + mCurrentTest.getClass().getSimpleName());
    268                 mCurrentTest = null;
    269                 break;
    270 
    271             case PASS:
    272                 Log.i(TAG, "pass for: " + mCurrentTest.getClass().getSimpleName());
    273                 mCurrentTest.tearDown();
    274                 if (mTestOrder.hasNext()) {
    275                     mCurrentTest = mTestOrder.next();
    276                     Log.i(TAG, "next test is: " + mCurrentTest.getClass().getSimpleName());
    277                 } else {
    278                     Log.i(TAG, "no more tests");
    279                     mCurrentTest = null;
    280                     getPassButton().setEnabled(true);
    281                     mNm.cancelAll();
    282                 }
    283                 break;
    284         }
    285         markItem(mCurrentTest);
    286     }
    287 
    288     /**
    289      * Return to the state machine to progress through the tests.
    290      */
    291     protected void next() {
    292         mHandler.removeCallbacks(mRunner);
    293         mHandler.post(mRunner);
    294     }
    295 
    296     /**
    297      * Wait for things to settle before returning to the state machine.
    298      */
    299     protected void delay() {
    300         delay(3000);
    301     }
    302 
    303     /**
    304      * Wait for some time.
    305      */
    306     protected void delay(long waitTime) {
    307         mHandler.removeCallbacks(mRunner);
    308         mHandler.postDelayed(mRunner, waitTime);
    309     }
    310 
    311     // UI callbacks
    312 
    313     public void launchSettings() {
    314         startActivity(new Intent(NOTIFICATION_LISTENER_SETTINGS));
    315     }
    316 
    317     public void actionPressed(View v) {
    318         Object tag = v.getTag();
    319         if (tag instanceof Integer) {
    320             int id = ((Integer) tag).intValue();
    321             if (id == R.string.nls_start_settings) {
    322                 launchSettings();
    323             } else if (id == R.string.attention_ready) {
    324                 mCurrentTest.status = READY;
    325                 next();
    326             }
    327         }
    328     }
    329 
    330     // Utilities
    331 
    332     protected PendingIntent makeIntent(int code, String tag) {
    333         Intent intent = new Intent(tag);
    334         intent.setComponent(new ComponentName(mContext, DismissService.class));
    335         PendingIntent pi = PendingIntent.getService(mContext, code, intent,
    336                 PendingIntent.FLAG_UPDATE_CURRENT);
    337         return pi;
    338     }
    339 
    340     protected boolean checkEquals(long expected, long actual, String message) {
    341         if (expected == actual) {
    342             return true;
    343         }
    344         logWithStack(String.format(message, expected, actual));
    345         return false;
    346     }
    347 
    348     protected boolean checkEquals(String expected, String actual, String message) {
    349         if (expected.equals(actual)) {
    350             return true;
    351         }
    352         logWithStack(String.format(message, expected, actual));
    353         return false;
    354     }
    355 
    356     protected boolean checkFlagSet(int expected, int actual, String message) {
    357         if ((expected & actual) != 0) {
    358             return true;
    359         }
    360         logWithStack(String.format(message, expected, actual));
    361         return false;
    362     };
    363 
    364     protected void logWithStack(String message) {
    365         Throwable stackTrace = new Throwable();
    366         stackTrace.fillInStackTrace();
    367         Log.e(TAG, message, stackTrace);
    368     }
    369 
    370     // Common Tests: useful for the side-effects they generate
    371 
    372     protected class IsEnabledTest extends InteractiveTestCase {
    373         @Override
    374         View inflate(ViewGroup parent) {
    375             return createNlsSettingsItem(parent, R.string.nls_enable_service);
    376         }
    377 
    378         @Override
    379         boolean autoStart() {
    380             return true;
    381         }
    382 
    383         @Override
    384         void test() {
    385             Intent settings = new Intent(NOTIFICATION_LISTENER_SETTINGS);
    386             if (settings.resolveActivity(mPackageManager) == null) {
    387                 logFail("no settings activity");
    388                 status = FAIL;
    389             } else {
    390                 String listeners = Secure.getString(getContentResolver(),
    391                         ENABLED_NOTIFICATION_LISTENERS);
    392                 if (listeners != null && listeners.contains(LISTENER_PATH)) {
    393                     status = PASS;
    394                 } else {
    395                     status = WAIT_FOR_USER;
    396                 }
    397                 next();
    398             }
    399         }
    400 
    401         void tearDown() {
    402             // wait for the service to start
    403             delay();
    404         }
    405     }
    406 
    407     protected class ServiceStartedTest extends InteractiveTestCase {
    408         @Override
    409         View inflate(ViewGroup parent) {
    410             return createAutoItem(parent, R.string.nls_service_started);
    411         }
    412 
    413         @Override
    414         void test() {
    415             MockListener.probeListenerStatus(mContext,
    416                     new MockListener.StatusCatcher() {
    417                         @Override
    418                         public void accept(int result) {
    419                             if (result == Activity.RESULT_OK) {
    420                                 status = PASS;
    421                                 next();
    422                             } else {
    423                                 logFail();
    424                                 status = RETEST;
    425                                 delay();
    426                             }
    427                         }
    428                     });
    429             delay();  // in case the catcher never returns
    430         }
    431 
    432         @Override
    433         void tearDown() {
    434             MockListener.resetListenerData(mContext);
    435             delay();
    436         }
    437     }
    438 }
    439