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