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 static android.app.NotificationManager.IMPORTANCE_LOW;
     20 import static android.app.NotificationManager.IMPORTANCE_NONE;
     21 import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
     22 import static android.provider.Settings.EXTRA_APP_PACKAGE;
     23 import static android.provider.Settings.EXTRA_CHANNEL_ID;
     24 
     25 import static com.android.cts.verifier.notifications.MockListener.JSON_FLAGS;
     26 import static com.android.cts.verifier.notifications.MockListener.JSON_ICON;
     27 import static com.android.cts.verifier.notifications.MockListener.JSON_ID;
     28 import static com.android.cts.verifier.notifications.MockListener.JSON_PACKAGE;
     29 import static com.android.cts.verifier.notifications.MockListener.JSON_REASON;
     30 import static com.android.cts.verifier.notifications.MockListener.JSON_STATS;
     31 import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
     32 import static com.android.cts.verifier.notifications.MockListener.JSON_WHEN;
     33 import static com.android.cts.verifier.notifications.MockListener.REASON_LISTENER_CANCEL;
     34 
     35 import android.annotation.SuppressLint;
     36 import android.app.ActivityManager;
     37 import android.app.AlertDialog;
     38 import android.app.Notification;
     39 import android.app.NotificationChannel;
     40 import android.app.NotificationChannelGroup;
     41 import android.content.Context;
     42 import android.content.DialogInterface;
     43 import android.content.Intent;
     44 import android.content.SharedPreferences;
     45 import android.os.Bundle;
     46 import android.provider.Settings;
     47 import android.provider.Settings.Secure;
     48 import android.service.notification.StatusBarNotification;
     49 import androidx.core.app.NotificationCompat;
     50 import android.util.Log;
     51 import android.view.View;
     52 import android.view.ViewGroup;
     53 import android.widget.Button;
     54 
     55 import com.android.cts.verifier.R;
     56 
     57 import org.json.JSONException;
     58 import org.json.JSONObject;
     59 
     60 import java.util.ArrayList;
     61 import java.util.HashSet;
     62 import java.util.List;
     63 import java.util.Set;
     64 import java.util.UUID;
     65 
     66 public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity
     67         implements Runnable {
     68     private static final String TAG = "NoListenerVerifier";
     69     private static final String NOTIFICATION_CHANNEL_ID = TAG;
     70     protected static final String PREFS = "listener_prefs";
     71     final int NUM_NOTIFICATIONS_SENT = 3; // # notifications sent by sendNotifications()
     72 
     73     private String mTag1;
     74     private String mTag2;
     75     private String mTag3;
     76     private int mIcon1;
     77     private int mIcon2;
     78     private int mIcon3;
     79     private int mId1;
     80     private int mId2;
     81     private int mId3;
     82     private long mWhen1;
     83     private long mWhen2;
     84     private long mWhen3;
     85     private int mFlag1;
     86     private int mFlag2;
     87     private int mFlag3;
     88 
     89     @Override
     90     protected int getTitleResource() {
     91         return R.string.nls_test;
     92     }
     93 
     94     @Override
     95     protected int getInstructionsResource() {
     96         return R.string.nls_info;
     97     }
     98 
     99     // Test Setup
    100 
    101     @Override
    102     protected List<InteractiveTestCase> createTestItems() {
    103         List<InteractiveTestCase> tests = new ArrayList<>(17);
    104         ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    105         if (am.isLowRamDevice()) {
    106             tests.add(new CannotBeEnabledTest());
    107             tests.add(new ServiceStoppedTest());
    108             tests.add(new NotificationNotReceivedTest());
    109         } else {
    110             tests.add(new IsEnabledTest());
    111             tests.add(new ServiceStartedTest());
    112             tests.add(new NotificationReceivedTest());
    113             tests.add(new DataIntactTest());
    114             tests.add(new DismissOneTest());
    115             tests.add(new DismissOneWithReasonTest());
    116             tests.add(new DismissOneWithStatsTest());
    117             tests.add(new DismissAllTest());
    118             tests.add(new SnoozeNotificationForTimeTest());
    119             tests.add(new SnoozeNotificationForTimeCancelTest());
    120             tests.add(new GetSnoozedNotificationTest());
    121             tests.add(new EnableHintsTest());
    122             tests.add(new ReceiveAppBlockNoticeTest());
    123             tests.add(new ReceiveAppUnblockNoticeTest());
    124             tests.add(new ReceiveChannelBlockNoticeTest());
    125             tests.add(new ReceiveGroupBlockNoticeTest());
    126             tests.add(new RequestUnbindTest());
    127             tests.add(new RequestBindTest());
    128             tests.add(new MessageBundleTest());
    129             tests.add(new EnableHintsTest());
    130             tests.add(new IsDisabledTest());
    131             tests.add(new ServiceStoppedTest());
    132             tests.add(new NotificationNotReceivedTest());
    133         }
    134         return tests;
    135     }
    136 
    137     private void createChannel() {
    138         NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
    139                 NOTIFICATION_CHANNEL_ID, IMPORTANCE_LOW);
    140         mNm.createNotificationChannel(channel);
    141     }
    142 
    143     private void deleteChannel() {
    144         mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
    145     }
    146 
    147     @SuppressLint("NewApi")
    148     private void sendNotifications() {
    149         mTag1 = UUID.randomUUID().toString();
    150         Log.d(TAG, "Sending " + mTag1);
    151         mTag2 = UUID.randomUUID().toString();
    152         Log.d(TAG, "Sending " + mTag2);
    153         mTag3 = UUID.randomUUID().toString();
    154         Log.d(TAG, "Sending " + mTag3);
    155 
    156         mWhen1 = System.currentTimeMillis() + 1;
    157         mWhen2 = System.currentTimeMillis() + 2;
    158         mWhen3 = System.currentTimeMillis() + 3;
    159 
    160         mIcon1 = R.drawable.ic_stat_alice;
    161         mIcon2 = R.drawable.ic_stat_bob;
    162         mIcon3 = R.drawable.ic_stat_charlie;
    163 
    164         mId1 = NOTIFICATION_ID + 1;
    165         mId2 = NOTIFICATION_ID + 2;
    166         mId3 = NOTIFICATION_ID + 3;
    167 
    168         mPackageString = "com.android.cts.verifier";
    169 
    170         Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
    171                 .setContentTitle("ClearTest 1")
    172                 .setContentText(mTag1)
    173                 .setSmallIcon(mIcon1)
    174                 .setWhen(mWhen1)
    175                 .setDeleteIntent(makeIntent(1, mTag1))
    176                 .setOnlyAlertOnce(true)
    177                 .build();
    178         mNm.notify(mTag1, mId1, n1);
    179         mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
    180 
    181         Notification n2 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
    182                 .setContentTitle("ClearTest 2")
    183                 .setContentText(mTag2)
    184                 .setSmallIcon(mIcon2)
    185                 .setWhen(mWhen2)
    186                 .setDeleteIntent(makeIntent(2, mTag2))
    187                 .setAutoCancel(true)
    188                 .build();
    189         mNm.notify(mTag2, mId2, n2);
    190         mFlag2 = Notification.FLAG_AUTO_CANCEL;
    191 
    192         Notification n3 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
    193                 .setContentTitle("ClearTest 3")
    194                 .setContentText(mTag3)
    195                 .setSmallIcon(mIcon3)
    196                 .setWhen(mWhen3)
    197                 .setDeleteIntent(makeIntent(3, mTag3))
    198                 .setAutoCancel(true)
    199                 .setOnlyAlertOnce(true)
    200                 .build();
    201         mNm.notify(mTag3, mId3, n3);
    202         mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
    203     }
    204 
    205     // Tests
    206     private class NotificationReceivedTest extends InteractiveTestCase {
    207         @Override
    208         protected View inflate(ViewGroup parent) {
    209             return createAutoItem(parent, R.string.nls_note_received);
    210 
    211         }
    212 
    213         @Override
    214         protected void setUp() {
    215             createChannel();
    216             sendNotifications();
    217             status = READY;
    218         }
    219 
    220         @Override
    221         protected void tearDown() {
    222             mNm.cancelAll();
    223             MockListener.getInstance().resetData();
    224             deleteChannel();
    225         }
    226 
    227         @Override
    228         protected void test() {
    229             List<String> result = new ArrayList<>(MockListener.getInstance().mPosted);
    230             if (result.size() > 0 && result.contains(mTag1)) {
    231                 status = PASS;
    232             } else {
    233                 logFail();
    234                 status = FAIL;
    235             }
    236         }
    237     }
    238 
    239     /**
    240      * Creates a notification channel. Sends the user to settings to block the channel. Waits
    241      * to receive the broadcast that the channel was blocked, and confirms that the broadcast
    242      * contains the correct extras.
    243      */
    244     protected class ReceiveChannelBlockNoticeTest extends InteractiveTestCase {
    245         private String mChannelId;
    246         private int mRetries = 2;
    247         private View mView;
    248         @Override
    249         protected View inflate(ViewGroup parent) {
    250             mView = createNlsSettingsItem(parent, R.string.nls_block_channel);
    251             Button button = mView.findViewById(R.id.nls_action_button);
    252             button.setEnabled(false);
    253             return mView;
    254         }
    255 
    256         @Override
    257         protected void setUp() {
    258             mChannelId = UUID.randomUUID().toString();
    259             NotificationChannel channel = new NotificationChannel(
    260                     mChannelId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW);
    261             mNm.createNotificationChannel(channel);
    262             status = READY;
    263             Button button = mView.findViewById(R.id.nls_action_button);
    264             button.setEnabled(true);
    265         }
    266 
    267         @Override
    268         boolean autoStart() {
    269             return true;
    270         }
    271 
    272         @Override
    273         protected void test() {
    274             NotificationChannel channel = mNm.getNotificationChannel(mChannelId);
    275             SharedPreferences prefs = mContext.getSharedPreferences(
    276                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
    277 
    278             if (channel.getImportance() == IMPORTANCE_NONE) {
    279                 if (prefs.contains(mChannelId) && prefs.getBoolean(mChannelId, false)) {
    280                     status = PASS;
    281                 } else {
    282                     if (mRetries > 0) {
    283                         mRetries--;
    284                         status = RETEST;
    285                     } else {
    286                         status = FAIL;
    287                     }
    288                 }
    289             } else {
    290                 // user hasn't jumped to settings to block the channel yet
    291                 status = WAIT_FOR_USER;
    292             }
    293 
    294             next();
    295         }
    296 
    297         protected void tearDown() {
    298             MockListener.getInstance().resetData();
    299             mNm.deleteNotificationChannel(mChannelId);
    300             SharedPreferences prefs = mContext.getSharedPreferences(
    301                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
    302             SharedPreferences.Editor editor = prefs.edit();
    303             editor.remove(mChannelId);
    304         }
    305 
    306         @Override
    307         protected Intent getIntent() {
    308          return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
    309                  .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName())
    310                  .putExtra(EXTRA_CHANNEL_ID, mChannelId);
    311         }
    312     }
    313 
    314     /**
    315      * Creates a notification channel group. Sends the user to settings to block the group. Waits
    316      * to receive the broadcast that the group was blocked, and confirms that the broadcast contains
    317      * the correct extras.
    318      */
    319     protected class ReceiveGroupBlockNoticeTest extends InteractiveTestCase {
    320         private String mGroupId;
    321         private int mRetries = 2;
    322         private View mView;
    323         @Override
    324         protected View inflate(ViewGroup parent) {
    325             mView = createNlsSettingsItem(parent, R.string.nls_block_group);
    326             Button button = mView.findViewById(R.id.nls_action_button);
    327             button.setEnabled(false);
    328             return mView;
    329         }
    330 
    331         @Override
    332         protected void setUp() {
    333             mGroupId = UUID.randomUUID().toString();
    334             NotificationChannelGroup group
    335                     = new NotificationChannelGroup(mGroupId, "ReceiveChannelGroupBlockNoticeTest");
    336             mNm.createNotificationChannelGroup(group);
    337             NotificationChannel channel = new NotificationChannel(
    338                     mGroupId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW);
    339             channel.setGroup(mGroupId);
    340             mNm.createNotificationChannel(channel);
    341             status = READY;
    342             Button button = mView.findViewById(R.id.nls_action_button);
    343             button.setEnabled(true);
    344         }
    345 
    346         @Override
    347         boolean autoStart() {
    348             return true;
    349         }
    350 
    351         @Override
    352         protected void test() {
    353             NotificationChannelGroup group = mNm.getNotificationChannelGroup(mGroupId);
    354             SharedPreferences prefs = mContext.getSharedPreferences(
    355                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
    356 
    357             if (group.isBlocked()) {
    358                 if (prefs.contains(mGroupId) && prefs.getBoolean(mGroupId, false)) {
    359                     status = PASS;
    360                 } else {
    361                     if (mRetries > 0) {
    362                         mRetries--;
    363                         status = RETEST;
    364                     } else {
    365                         status = FAIL;
    366                     }
    367                 }
    368             } else {
    369                 // user hasn't jumped to settings to block the group yet
    370                 status = WAIT_FOR_USER;
    371             }
    372 
    373             next();
    374         }
    375 
    376         protected void tearDown() {
    377             MockListener.getInstance().resetData();
    378             mNm.deleteNotificationChannelGroup(mGroupId);
    379             SharedPreferences prefs = mContext.getSharedPreferences(
    380                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
    381             SharedPreferences.Editor editor = prefs.edit();
    382             editor.remove(mGroupId);
    383         }
    384 
    385         @Override
    386         protected Intent getIntent() {
    387             return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
    388                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
    389                     .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName());
    390         }
    391     }
    392 
    393     /**
    394      * Sends the user to settings to block the app. Waits to receive the broadcast that the app was
    395      * blocked, and confirms that the broadcast contains the correct extras.
    396      */
    397     protected class ReceiveAppBlockNoticeTest extends InteractiveTestCase {
    398         private int mRetries = 2;
    399         private View mView;
    400         @Override
    401         protected View inflate(ViewGroup parent) {
    402             mView = createNlsSettingsItem(parent, R.string.nls_block_app);
    403             Button button = mView.findViewById(R.id.nls_action_button);
    404             button.setEnabled(false);
    405             return mView;
    406         }
    407 
    408         @Override
    409         protected void setUp() {
    410             status = READY;
    411             Button button = mView.findViewById(R.id.nls_action_button);
    412             button.setEnabled(true);
    413         }
    414 
    415         @Override
    416         boolean autoStart() {
    417             return true;
    418         }
    419 
    420         @Override
    421         protected void test() {
    422             SharedPreferences prefs = mContext.getSharedPreferences(
    423                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
    424 
    425             if (!mNm.areNotificationsEnabled()) {
    426                 Log.d(TAG, "Got broadcast " + prefs.contains(mContext.getPackageName()));
    427                 Log.d(TAG, "Broadcast contains correct data? " +
    428                         prefs.getBoolean(mContext.getPackageName(), false));
    429                 if (prefs.contains(mContext.getPackageName())
    430                         && prefs.getBoolean(mContext.getPackageName(), false)) {
    431                     status = PASS;
    432                 } else {
    433                     if (mRetries > 0) {
    434                         mRetries--;
    435                         status = RETEST;
    436                     } else {
    437                         status = FAIL;
    438                     }
    439                 }
    440             } else {
    441                 Log.d(TAG, "Notifications still enabled");
    442                 // user hasn't jumped to settings to block the app yet
    443                 status = WAIT_FOR_USER;
    444             }
    445 
    446             next();
    447         }
    448 
    449         protected void tearDown() {
    450             MockListener.getInstance().resetData();
    451             SharedPreferences prefs = mContext.getSharedPreferences(
    452                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
    453             SharedPreferences.Editor editor = prefs.edit();
    454             editor.remove(mContext.getPackageName());
    455         }
    456 
    457         @Override
    458         protected Intent getIntent() {
    459             return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
    460                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
    461                     .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName());
    462         }
    463     }
    464 
    465     /**
    466      * Sends the user to settings to unblock the app. Waits to receive the broadcast that the app
    467      * was unblocked, and confirms that the broadcast contains the correct extras.
    468      */
    469     protected class ReceiveAppUnblockNoticeTest extends InteractiveTestCase {
    470         private int mRetries = 2;
    471         private View mView;
    472         @Override
    473         protected View inflate(ViewGroup parent) {
    474             mView = createNlsSettingsItem(parent, R.string.nls_unblock_app);
    475             Button button = mView.findViewById(R.id.nls_action_button);
    476             button.setEnabled(false);
    477             return mView;
    478         }
    479 
    480         @Override
    481         protected void setUp() {
    482             status = READY;
    483             Button button = mView.findViewById(R.id.nls_action_button);
    484             button.setEnabled(true);
    485         }
    486 
    487         @Override
    488         boolean autoStart() {
    489             return true;
    490         }
    491 
    492         @Override
    493         protected void test() {
    494             SharedPreferences prefs = mContext.getSharedPreferences(
    495                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
    496 
    497             if (mNm.areNotificationsEnabled()) {
    498                 if (prefs.contains(mContext.getPackageName())
    499                         && !prefs.getBoolean(mContext.getPackageName(), true)) {
    500                     status = PASS;
    501                 } else {
    502                     if (mRetries > 0) {
    503                         mRetries--;
    504                         status = RETEST;
    505                     } else {
    506                         status = FAIL;
    507                     }
    508                 }
    509             } else {
    510                 // user hasn't jumped to settings to block the app yet
    511                 status = WAIT_FOR_USER;
    512             }
    513 
    514             next();
    515         }
    516 
    517         protected void tearDown() {
    518             MockListener.getInstance().resetData();
    519             SharedPreferences prefs = mContext.getSharedPreferences(
    520                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
    521             SharedPreferences.Editor editor = prefs.edit();
    522             editor.remove(mContext.getPackageName());
    523         }
    524 
    525         @Override
    526         protected Intent getIntent() {
    527             return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
    528                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
    529                     .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName());
    530         }
    531     }
    532 
    533     private class DataIntactTest extends InteractiveTestCase {
    534         @Override
    535         protected View inflate(ViewGroup parent) {
    536             return createAutoItem(parent, R.string.nls_payload_intact);
    537         }
    538 
    539         @Override
    540         protected void setUp() {
    541             createChannel();
    542             sendNotifications();
    543             status = READY;
    544         }
    545 
    546         @Override
    547         protected void test() {
    548             List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
    549 
    550             Set<String> found = new HashSet<String>();
    551             if (result.size() == 0) {
    552                 status = FAIL;
    553                 return;
    554             }
    555             boolean pass = true;
    556             for (JSONObject payload : result) {
    557                 try {
    558                     pass &= checkEquals(mPackageString,
    559                             payload.getString(JSON_PACKAGE),
    560                             "data integrity test: notification package (%s, %s)");
    561                     String tag = payload.getString(JSON_TAG);
    562                     if (mTag1.equals(tag)) {
    563                         found.add(mTag1);
    564                         pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON),
    565                                 "data integrity test: notification icon (%d, %d)");
    566                         pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS),
    567                                 "data integrity test: notification flags (%d, %d)");
    568                         pass &= checkEquals(mId1, payload.getInt(JSON_ID),
    569                                 "data integrity test: notification ID (%d, %d)");
    570                         pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN),
    571                                 "data integrity test: notification when (%d, %d)");
    572                     } else if (mTag2.equals(tag)) {
    573                         found.add(mTag2);
    574                         pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON),
    575                                 "data integrity test: notification icon (%d, %d)");
    576                         pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS),
    577                                 "data integrity test: notification flags (%d, %d)");
    578                         pass &= checkEquals(mId2, payload.getInt(JSON_ID),
    579                                 "data integrity test: notification ID (%d, %d)");
    580                         pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN),
    581                                 "data integrity test: notification when (%d, %d)");
    582                     } else if (mTag3.equals(tag)) {
    583                         found.add(mTag3);
    584                         pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON),
    585                                 "data integrity test: notification icon (%d, %d)");
    586                         pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS),
    587                                 "data integrity test: notification flags (%d, %d)");
    588                         pass &= checkEquals(mId3, payload.getInt(JSON_ID),
    589                                 "data integrity test: notification ID (%d, %d)");
    590                         pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN),
    591                                 "data integrity test: notification when (%d, %d)");
    592                     }
    593                 } catch (JSONException e) {
    594                     pass = false;
    595                     Log.e(TAG, "failed to unpack data from mocklistener", e);
    596                 }
    597             }
    598 
    599             pass &= found.size() >= 3;
    600             status = pass ? PASS : FAIL;
    601         }
    602 
    603         @Override
    604         protected void tearDown() {
    605             mNm.cancelAll();
    606             MockListener.getInstance().resetData();
    607             deleteChannel();
    608         }
    609     }
    610 
    611     private class DismissOneTest extends InteractiveTestCase {
    612         @Override
    613         protected View inflate(ViewGroup parent) {
    614             return createAutoItem(parent, R.string.nls_clear_one);
    615         }
    616 
    617         @Override
    618         protected void setUp() {
    619             createChannel();
    620             sendNotifications();
    621             status = READY;
    622         }
    623 
    624         @Override
    625         protected void test() {
    626             if (status == READY) {
    627                 MockListener.getInstance().cancelNotification(
    628                         MockListener.getInstance().getKeyForTag(mTag1));
    629                 status = RETEST;
    630             } else {
    631                 List<String> result = new ArrayList<>(MockListener.getInstance().mRemoved);
    632                 if (result.size() != 0
    633                         && result.contains(mTag1)
    634                         && !result.contains(mTag2)
    635                         && !result.contains(mTag3)) {
    636                     status = PASS;
    637                 } else {
    638                     logFail();
    639                     status = FAIL;
    640                 }
    641             }
    642         }
    643 
    644         @Override
    645         protected void tearDown() {
    646             mNm.cancelAll();
    647             deleteChannel();
    648             MockListener.getInstance().resetData();
    649         }
    650     }
    651 
    652     private class DismissOneWithReasonTest extends InteractiveTestCase {
    653         int mRetries = 3;
    654 
    655         @Override
    656         protected View inflate(ViewGroup parent) {
    657             return createAutoItem(parent, R.string.nls_clear_one_reason);
    658         }
    659 
    660         @Override
    661         protected void setUp() {
    662             createChannel();
    663             sendNotifications();
    664             status = READY;
    665         }
    666 
    667         @Override
    668         protected void test() {
    669             if (status == READY) {
    670                 MockListener.getInstance().cancelNotification(
    671                         MockListener.getInstance().getKeyForTag(mTag1));
    672                 status = RETEST;
    673             } else {
    674                 List<JSONObject> result =
    675                         new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
    676                 boolean pass = false;
    677                 for (JSONObject payload : result) {
    678                     try {
    679                         pass |= (checkEquals(mTag1,
    680                                 payload.getString(JSON_TAG),
    681                                 "data dismissal test: notification tag (%s, %s)")
    682                                 && checkEquals(REASON_LISTENER_CANCEL,
    683                                 payload.getInt(JSON_REASON),
    684                                 "data dismissal test: reason (%d, %d)"));
    685                         if(pass) {
    686                             break;
    687                         }
    688                     } catch (JSONException e) {
    689                         e.printStackTrace();
    690                     }
    691                 }
    692                 if (pass) {
    693                     status = PASS;
    694                 } else {
    695                     if (--mRetries > 0) {
    696                         sleep(100);
    697                         status = RETEST;
    698                     } else {
    699                         status = FAIL;
    700                     }
    701                 }
    702             }
    703         }
    704 
    705         @Override
    706         protected void tearDown() {
    707             mNm.cancelAll();
    708             deleteChannel();
    709             MockListener.getInstance().resetData();
    710         }
    711     }
    712 
    713     private class DismissOneWithStatsTest extends InteractiveTestCase {
    714         int mRetries = 3;
    715 
    716         @Override
    717         protected View inflate(ViewGroup parent) {
    718             return createAutoItem(parent, R.string.nls_clear_one_stats);
    719         }
    720 
    721         @Override
    722         protected void setUp() {
    723             createChannel();
    724             sendNotifications();
    725             status = READY;
    726         }
    727 
    728         @Override
    729         protected void test() {
    730             if (status == READY) {
    731                 MockListener.getInstance().cancelNotification(
    732                         MockListener.getInstance().getKeyForTag(mTag1));
    733                 status = RETEST;
    734             } else {
    735                 List<JSONObject> result =
    736                         new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
    737                 boolean pass = true;
    738                 for (JSONObject payload : result) {
    739                     try {
    740                         pass &= (payload.getBoolean(JSON_STATS) == false);
    741                     } catch (JSONException e) {
    742                         e.printStackTrace();
    743                         pass = false;
    744                     }
    745                 }
    746                 if (pass) {
    747                     status = PASS;
    748                 } else {
    749                     if (--mRetries > 0) {
    750                         sleep(100);
    751                         status = RETEST;
    752                     } else {
    753                         logFail("Notification listener got populated stats object.");
    754                         status = FAIL;
    755                     }
    756                 }
    757             }
    758         }
    759 
    760         @Override
    761         protected void tearDown() {
    762             mNm.cancelAll();
    763             deleteChannel();
    764             MockListener.getInstance().resetData();
    765         }
    766     }
    767 
    768     private class DismissAllTest extends InteractiveTestCase {
    769         @Override
    770         protected View inflate(ViewGroup parent) {
    771             return createAutoItem(parent, R.string.nls_clear_all);
    772         }
    773 
    774         @Override
    775         protected void setUp() {
    776             createChannel();
    777             sendNotifications();
    778             status = READY;
    779         }
    780 
    781         @Override
    782         protected void test() {
    783             if (status == READY) {
    784                 MockListener.getInstance().cancelAllNotifications();
    785                 status = RETEST;
    786             } else {
    787                 List<String> result = new ArrayList<>(MockListener.getInstance().mRemoved);
    788                 if (result.size() != 0
    789                         && result.contains(mTag1)
    790                         && result.contains(mTag2)
    791                         && result.contains(mTag3)) {
    792                     status = PASS;
    793                 } else {
    794                     logFail();
    795                     status = FAIL;
    796                 }
    797             }
    798         }
    799 
    800         @Override
    801         protected void tearDown() {
    802             mNm.cancelAll();
    803             deleteChannel();
    804             MockListener.getInstance().resetData();
    805         }
    806     }
    807 
    808     private class IsDisabledTest extends InteractiveTestCase {
    809         @Override
    810         protected View inflate(ViewGroup parent) {
    811             return createNlsSettingsItem(parent, R.string.nls_disable_service);
    812         }
    813 
    814         @Override
    815         boolean autoStart() {
    816             return true;
    817         }
    818 
    819         @Override
    820         protected void test() {
    821             String listeners = Secure.getString(getContentResolver(),
    822                     ENABLED_NOTIFICATION_LISTENERS);
    823             if (listeners == null || !listeners.contains(LISTENER_PATH)) {
    824                 status = PASS;
    825             } else {
    826                 status = WAIT_FOR_USER;
    827             }
    828         }
    829 
    830         @Override
    831         protected void tearDown() {
    832             MockListener.getInstance().resetData();
    833         }
    834 
    835         @Override
    836         protected Intent getIntent() {
    837             return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
    838         }
    839     }
    840 
    841     private class ServiceStoppedTest extends InteractiveTestCase {
    842         int mRetries = 3;
    843         @Override
    844         protected View inflate(ViewGroup parent) {
    845             return createAutoItem(parent, R.string.nls_service_stopped);
    846         }
    847 
    848         @Override
    849         protected void test() {
    850             if (mNm.getEffectsSuppressor() == null && (MockListener.getInstance() == null
    851                     || !MockListener.getInstance().isConnected)) {
    852                 status = PASS;
    853             } else {
    854                 if (--mRetries > 0) {
    855                     sleep(100);
    856                     status = RETEST;
    857                 } else {
    858                     status = FAIL;
    859                 }
    860             }
    861         }
    862 
    863         @Override
    864         protected Intent getIntent() {
    865             return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
    866         }
    867     }
    868 
    869     private class NotificationNotReceivedTest extends InteractiveTestCase {
    870         @Override
    871         protected View inflate(ViewGroup parent) {
    872             return createAutoItem(parent, R.string.nls_note_missed);
    873 
    874         }
    875 
    876         @Override
    877         protected void setUp() {
    878             createChannel();
    879             sendNotifications();
    880             status = READY;
    881         }
    882 
    883         @Override
    884         protected void test() {
    885             if (MockListener.getInstance() == null) {
    886                 status = PASS;
    887             } else {
    888                 List<String> result = new ArrayList<>(MockListener.getInstance().mPosted);
    889                 if (result.size() == 0) {
    890                     status = PASS;
    891                 } else {
    892                     logFail();
    893                     status = FAIL;
    894                 }
    895             }
    896             next();
    897         }
    898 
    899         @Override
    900         protected void tearDown() {
    901             mNm.cancelAll();
    902             deleteChannel();
    903             if (MockListener.getInstance() != null) {
    904                 MockListener.getInstance().resetData();
    905             }
    906         }
    907     }
    908 
    909     private class RequestUnbindTest extends InteractiveTestCase {
    910         int mRetries = 5;
    911 
    912         @Override
    913         protected View inflate(ViewGroup parent) {
    914             return createAutoItem(parent, R.string.nls_snooze);
    915 
    916         }
    917 
    918         @Override
    919         protected void setUp() {
    920             status = READY;
    921             MockListener.getInstance().requestInterruptionFilter(
    922                     MockListener.HINT_HOST_DISABLE_CALL_EFFECTS);
    923         }
    924 
    925         @Override
    926         protected void test() {
    927             if (status == READY) {
    928                 MockListener.getInstance().requestUnbind();
    929                 status = RETEST;
    930             } else {
    931                 if (mNm.getEffectsSuppressor() == null && !MockListener.getInstance().isConnected) {
    932                     status = PASS;
    933                 } else {
    934                     if (--mRetries > 0) {
    935                         status = RETEST;
    936                     } else {
    937                         logFail();
    938                         status = FAIL;
    939                     }
    940                 }
    941                 next();
    942             }
    943         }
    944     }
    945 
    946     private class RequestBindTest extends InteractiveTestCase {
    947         int mRetries = 5;
    948 
    949         @Override
    950         protected View inflate(ViewGroup parent) {
    951             return createAutoItem(parent, R.string.nls_unsnooze);
    952 
    953         }
    954 
    955         @Override
    956         protected void test() {
    957             if (status == READY) {
    958                 MockListener.requestRebind(MockListener.COMPONENT_NAME);
    959                 status = RETEST;
    960             } else {
    961                 if (MockListener.getInstance().isConnected) {
    962                     status = PASS;
    963                     next();
    964                 } else {
    965                     if (--mRetries > 0) {
    966                         status = RETEST;
    967                         next();
    968                     } else {
    969                         logFail();
    970                         status = FAIL;
    971                     }
    972                 }
    973             }
    974         }
    975     }
    976 
    977     private class EnableHintsTest extends InteractiveTestCase {
    978         @Override
    979         protected View inflate(ViewGroup parent) {
    980             return createAutoItem(parent, R.string.nls_hints);
    981 
    982         }
    983 
    984         @Override
    985         protected void test() {
    986             if (status == READY) {
    987                 MockListener.getInstance().requestListenerHints(
    988                         MockListener.HINT_HOST_DISABLE_CALL_EFFECTS);
    989                 status = RETEST;
    990             } else {
    991                 int result = MockListener.getInstance().getCurrentListenerHints();
    992                 if ((result & MockListener.HINT_HOST_DISABLE_CALL_EFFECTS)
    993                         == MockListener.HINT_HOST_DISABLE_CALL_EFFECTS) {
    994                     status = PASS;
    995                     next();
    996                 } else {
    997                     logFail();
    998                     status = FAIL;
    999                 }
   1000             }
   1001         }
   1002     }
   1003 
   1004     private class SnoozeNotificationForTimeTest extends InteractiveTestCase {
   1005         final static int READY_TO_SNOOZE = 0;
   1006         final static int SNOOZED = 1;
   1007         final static int READY_TO_CHECK_FOR_UNSNOOZE = 2;
   1008         int state = -1;
   1009         long snoozeTime = 3000;
   1010 
   1011         @Override
   1012         protected View inflate(ViewGroup parent) {
   1013             return createAutoItem(parent, R.string.nls_snooze_one_time);
   1014         }
   1015 
   1016         @Override
   1017         protected void setUp() {
   1018             createChannel();
   1019             sendNotifications();
   1020             status = READY;
   1021             state = READY_TO_SNOOZE;
   1022             delay();
   1023         }
   1024 
   1025         @Override
   1026         protected void test() {
   1027             status = RETEST;
   1028             if (state == READY_TO_SNOOZE) {
   1029                 MockListener.getInstance().snoozeNotification(
   1030                         MockListener.getInstance().getKeyForTag(mTag1), snoozeTime);
   1031                 state = SNOOZED;
   1032             } else if (state == SNOOZED) {
   1033                 List<JSONObject> result =
   1034                         new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
   1035                 boolean pass = false;
   1036                 for (JSONObject payload : result) {
   1037                     try {
   1038                         pass |= (checkEquals(mTag1,
   1039                                 payload.getString(JSON_TAG),
   1040                                 "data dismissal test: notification tag (%s, %s)")
   1041                                 && checkEquals(MockListener.REASON_SNOOZED,
   1042                                 payload.getInt(JSON_REASON),
   1043                                 "data dismissal test: reason (%d, %d)"));
   1044                         if (pass) {
   1045                             break;
   1046                         }
   1047                     } catch (JSONException e) {
   1048                         e.printStackTrace();
   1049                     }
   1050                 }
   1051                 if (!pass) {
   1052                     logFail();
   1053                     status = FAIL;
   1054                     next();
   1055                     return;
   1056                 } else {
   1057                     state = READY_TO_CHECK_FOR_UNSNOOZE;
   1058                 }
   1059             } else {
   1060                 List<String> result = new ArrayList<>(MockListener.getInstance().mPosted);
   1061                 if (result.size() > 0 && result.contains(mTag1)) {
   1062                     status = PASS;
   1063                 } else {
   1064                     logFail();
   1065                     status = FAIL;
   1066                 }
   1067             }
   1068         }
   1069 
   1070         @Override
   1071         protected void tearDown() {
   1072             mNm.cancelAll();
   1073             deleteChannel();
   1074             MockListener.getInstance().resetData();
   1075             delay();
   1076         }
   1077     }
   1078 
   1079     /**
   1080      * Posts notifications, snoozes one of them. Verifies that it is snoozed. Cancels all
   1081      * notifications and reposts them. Confirms that the original notification is still snoozed.
   1082      */
   1083     private class SnoozeNotificationForTimeCancelTest extends InteractiveTestCase {
   1084 
   1085         final static int READY_TO_SNOOZE = 0;
   1086         final static int SNOOZED = 1;
   1087         final static int READY_TO_CHECK_FOR_SNOOZE = 2;
   1088         int state = -1;
   1089         long snoozeTime = 10000;
   1090         private String tag;
   1091 
   1092         @Override
   1093         protected View inflate(ViewGroup parent) {
   1094             return createAutoItem(parent, R.string.nls_snooze_one_time);
   1095         }
   1096 
   1097         @Override
   1098         protected void setUp() {
   1099             createChannel();
   1100             sendNotifications();
   1101             tag = mTag1;
   1102             status = READY;
   1103             state = READY_TO_SNOOZE;
   1104             delay();
   1105         }
   1106 
   1107         @Override
   1108         protected void test() {
   1109             status = RETEST;
   1110             if (state == READY_TO_SNOOZE) {
   1111                 MockListener.getInstance().snoozeNotification(
   1112                         MockListener.getInstance().getKeyForTag(tag), snoozeTime);
   1113                 state = SNOOZED;
   1114             } else if (state == SNOOZED) {
   1115                 List<String> result = getSnoozed();
   1116                 if (result.size() >= 1
   1117                         && result.contains(tag)) {
   1118                     // cancel and repost
   1119                     sendNotifications();
   1120                     state = READY_TO_CHECK_FOR_SNOOZE;
   1121                 } else {
   1122                     logFail();
   1123                     status = FAIL;
   1124                 }
   1125             } else {
   1126                 List<String> result = getSnoozed();
   1127                 if (result.size() >= 1
   1128                         && result.contains(tag)) {
   1129                     status = PASS;
   1130                 } else {
   1131                     logFail();
   1132                     status = FAIL;
   1133                 }
   1134             }
   1135         }
   1136 
   1137         private List<String> getSnoozed() {
   1138             List<String> result = new ArrayList<>();
   1139             StatusBarNotification[] snoozed = MockListener.getInstance().getSnoozedNotifications();
   1140             for (StatusBarNotification sbn : snoozed) {
   1141                 result.add(sbn.getTag());
   1142             }
   1143             return result;
   1144         }
   1145 
   1146         @Override
   1147         protected void tearDown() {
   1148             mNm.cancelAll();
   1149             deleteChannel();
   1150             MockListener.getInstance().resetData();
   1151         }
   1152     }
   1153 
   1154     private class GetSnoozedNotificationTest extends InteractiveTestCase {
   1155         final static int READY_TO_SNOOZE = 0;
   1156         final static int SNOOZED = 1;
   1157         final static int READY_TO_CHECK_FOR_GET_SNOOZE = 2;
   1158         int state = -1;
   1159         long snoozeTime = 30000;
   1160 
   1161         @Override
   1162         protected View inflate(ViewGroup parent) {
   1163             return createAutoItem(parent, R.string.nls_get_snoozed);
   1164         }
   1165 
   1166         @Override
   1167         protected void setUp() {
   1168             createChannel();
   1169             sendNotifications();
   1170             status = READY;
   1171             state = READY_TO_SNOOZE;
   1172         }
   1173 
   1174         @Override
   1175         protected void test() {
   1176             status = RETEST;
   1177             if (state == READY_TO_SNOOZE) {
   1178                 MockListener.getInstance().snoozeNotification(
   1179                         MockListener.getInstance().getKeyForTag(mTag1), snoozeTime);
   1180                 MockListener.getInstance().snoozeNotification(
   1181                         MockListener.getInstance().getKeyForTag(mTag2), snoozeTime);
   1182                 state = SNOOZED;
   1183             } else if (state == SNOOZED) {
   1184                 List<JSONObject> result =
   1185                         new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
   1186                 if (result.size() == 0) {
   1187                     status = FAIL;
   1188                     return;
   1189                 }
   1190                 boolean pass = false;
   1191                 for (JSONObject payload : result) {
   1192                     try {
   1193                         pass |= (checkEquals(mTag1,
   1194                                 payload.getString(JSON_TAG),
   1195                                 "data dismissal test: notification tag (%s, %s)")
   1196                                 && checkEquals(MockListener.REASON_SNOOZED,
   1197                                 payload.getInt(JSON_REASON),
   1198                                 "data dismissal test: reason (%d, %d)"));
   1199                         if (pass) {
   1200                             break;
   1201                         }
   1202                     } catch (JSONException e) {
   1203                         e.printStackTrace();
   1204                     }
   1205                 }
   1206                 if (!pass) {
   1207                     logFail();
   1208                     status = FAIL;
   1209                 } else {
   1210                     state = READY_TO_CHECK_FOR_GET_SNOOZE;
   1211                 }
   1212             } else {
   1213                 List<String> result = new ArrayList<>();
   1214                 StatusBarNotification[] snoozed =
   1215                         MockListener.getInstance().getSnoozedNotifications();
   1216                 for (StatusBarNotification sbn : snoozed) {
   1217                     result.add(sbn.getTag());
   1218                 }
   1219                 if (result.size() >= 2
   1220                         && result.contains(mTag1)
   1221                         && result.contains(mTag2)) {
   1222                     status = PASS;
   1223                 } else {
   1224                     logFail();
   1225                     status = FAIL;
   1226                 }
   1227             }
   1228         }
   1229 
   1230         @Override
   1231         protected void tearDown() {
   1232             mNm.cancelAll();
   1233             deleteChannel();
   1234             MockListener.getInstance().resetData();
   1235             delay();
   1236         }
   1237     }
   1238 
   1239     /** Tests that the extras {@link Bundle} in a MessagingStyle#Message is preserved. */
   1240     private class MessageBundleTest extends InteractiveTestCase {
   1241         private final String extrasKey1 = "extras_key_1";
   1242         private final CharSequence extrasValue1 = "extras_value_1";
   1243         private final String extrasKey2 = "extras_key_2";
   1244         private final CharSequence extrasValue2 = "extras_value_2";
   1245 
   1246         @Override
   1247         protected View inflate(ViewGroup parent) {
   1248             return createAutoItem(parent, R.string.msg_extras_preserved);
   1249         }
   1250 
   1251         @Override
   1252         protected void setUp() {
   1253             createChannel();
   1254             sendMessagingNotification();
   1255             status = READY;
   1256         }
   1257 
   1258         @Override
   1259         protected void tearDown() {
   1260             mNm.cancelAll();
   1261             deleteChannel();
   1262             delay();
   1263         }
   1264 
   1265         private void sendMessagingNotification() {
   1266             mTag1 = UUID.randomUUID().toString();
   1267             mNm.cancelAll();
   1268             mWhen1 = System.currentTimeMillis() + 1;
   1269             mIcon1 = R.drawable.ic_stat_alice;
   1270             mId1 = NOTIFICATION_ID + 1;
   1271 
   1272             Notification.MessagingStyle.Message msg1 =
   1273                     new Notification.MessagingStyle.Message("text1", 0 /* timestamp */, "sender1");
   1274             msg1.getExtras().putCharSequence(extrasKey1, extrasValue1);
   1275 
   1276             Notification.MessagingStyle.Message msg2 =
   1277                     new Notification.MessagingStyle.Message("text2", 1 /* timestamp */, "sender2");
   1278             msg2.getExtras().putCharSequence(extrasKey2, extrasValue2);
   1279 
   1280             Notification.MessagingStyle style = new Notification.MessagingStyle("display_name");
   1281             style.addMessage(msg1);
   1282             style.addMessage(msg2);
   1283 
   1284             Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
   1285                     .setContentTitle("ClearTest 1")
   1286                     .setContentText(mTag1.toString())
   1287                     .setPriority(Notification.PRIORITY_LOW)
   1288                     .setSmallIcon(mIcon1)
   1289                     .setWhen(mWhen1)
   1290                     .setDeleteIntent(makeIntent(1, mTag1))
   1291                     .setOnlyAlertOnce(true)
   1292                     .setStyle(style)
   1293                     .build();
   1294             mNm.notify(mTag1, mId1, n1);
   1295             mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
   1296         }
   1297 
   1298         // Returns true on success.
   1299         private boolean verifyMessage(
   1300                 NotificationCompat.MessagingStyle.Message message,
   1301                 String extrasKey,
   1302                 CharSequence extrasValue) {
   1303             return message.getExtras() != null
   1304                     && message.getExtras().getCharSequence(extrasKey) != null
   1305                     && message.getExtras().getCharSequence(extrasKey).equals(extrasValue);
   1306         }
   1307 
   1308         @Override
   1309         protected void test() {
   1310             List<Notification> result =
   1311                     new ArrayList<>(MockListener.getInstance().mPostedNotifications);
   1312             if (result.size() != 1 || result.get(0) == null) {
   1313                 logFail();
   1314                 status = FAIL;
   1315                 next();
   1316                 return;
   1317             }
   1318             // Can only read in MessagingStyle using the compat class.
   1319             NotificationCompat.MessagingStyle readStyle =
   1320                     NotificationCompat.MessagingStyle
   1321                             .extractMessagingStyleFromNotification(
   1322                                     result.get(0));
   1323             if (readStyle == null || readStyle.getMessages().size() != 2) {
   1324                 status = FAIL;
   1325                 logFail();
   1326                 next();
   1327                 return;
   1328             }
   1329 
   1330             if (!verifyMessage(readStyle.getMessages().get(0), extrasKey1,
   1331                     extrasValue1)
   1332                     || !verifyMessage(
   1333                     readStyle.getMessages().get(1), extrasKey2, extrasValue2)) {
   1334                 status = FAIL;
   1335                 logFail();
   1336                 next();
   1337                 return;
   1338             }
   1339 
   1340             status = PASS;
   1341         }
   1342     }
   1343 }
   1344