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.Notification.VISIBILITY_PRIVATE; 20 import static android.app.NotificationManager.IMPORTANCE_LOW; 21 import static android.app.NotificationManager.IMPORTANCE_MAX; 22 import static android.app.NotificationManager.IMPORTANCE_NONE; 23 import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS; 24 import static android.provider.Settings.EXTRA_APP_PACKAGE; 25 import static android.provider.Settings.EXTRA_CHANNEL_ID; 26 27 import static com.android.cts.verifier.notifications.MockListener.JSON_FLAGS; 28 import static com.android.cts.verifier.notifications.MockListener.JSON_ICON; 29 import static com.android.cts.verifier.notifications.MockListener.JSON_ID; 30 import static com.android.cts.verifier.notifications.MockListener.JSON_LAST_AUDIBLY_ALERTED; 31 import static com.android.cts.verifier.notifications.MockListener.JSON_PACKAGE; 32 import static com.android.cts.verifier.notifications.MockListener.JSON_REASON; 33 import static com.android.cts.verifier.notifications.MockListener.JSON_STATS; 34 import static com.android.cts.verifier.notifications.MockListener.JSON_TAG; 35 import static com.android.cts.verifier.notifications.MockListener.JSON_WHEN; 36 import static com.android.cts.verifier.notifications.MockListener.REASON_LISTENER_CANCEL; 37 38 import android.annotation.SuppressLint; 39 import android.app.Notification; 40 import android.app.NotificationChannel; 41 import android.app.NotificationChannelGroup; 42 import android.app.Person; 43 import android.content.Context; 44 import android.content.Intent; 45 import android.content.SharedPreferences; 46 import android.content.pm.PackageManager; 47 import android.content.pm.ShortcutInfo; 48 import android.content.pm.ShortcutManager; 49 import android.graphics.drawable.Icon; 50 import android.os.Bundle; 51 import android.os.SystemClock; 52 import android.provider.Settings; 53 import android.provider.Settings.Secure; 54 import android.service.notification.NotificationListenerService; 55 import android.service.notification.StatusBarNotification; 56 import android.util.ArraySet; 57 import android.util.Log; 58 import android.view.View; 59 import android.view.ViewGroup; 60 import android.widget.Button; 61 import android.widget.RemoteViews; 62 63 import androidx.core.app.NotificationCompat; 64 65 import com.android.cts.verifier.R; 66 67 import org.json.JSONException; 68 import org.json.JSONObject; 69 70 import java.util.ArrayList; 71 import java.util.Arrays; 72 import java.util.HashSet; 73 import java.util.List; 74 import java.util.Set; 75 import java.util.UUID; 76 77 public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity 78 implements Runnable { 79 static final String TAG = "NoListenerVerifier"; 80 private static final String NOTIFICATION_CHANNEL_ID = TAG; 81 private static final String NOISY_NOTIFICATION_CHANNEL_ID = TAG + "noisy"; 82 protected static final String PREFS = "listener_prefs"; 83 final int NUM_NOTIFICATIONS_SENT = 3; // # notifications sent by sendNotifications() 84 85 private String mTag1; 86 private String mTag2; 87 private String mTag3; 88 private String mTag4; 89 private int mIcon1; 90 private int mIcon2; 91 private int mIcon3; 92 private int mIcon4; 93 private int mId1; 94 private int mId2; 95 private int mId3; 96 private int mId4; 97 private long mWhen1; 98 private long mWhen2; 99 private long mWhen3; 100 private long mWhen4; 101 private int mFlag1; 102 private int mFlag2; 103 private int mFlag3; 104 105 @Override getTitleResource()106 protected int getTitleResource() { 107 return R.string.nls_test; 108 } 109 110 @Override getInstructionsResource()111 protected int getInstructionsResource() { 112 return R.string.nls_info; 113 } 114 115 // Test Setup 116 117 @Override createTestItems()118 protected List<InteractiveTestCase> createTestItems() { 119 boolean isAutomotive = getPackageManager().hasSystemFeature( 120 PackageManager.FEATURE_AUTOMOTIVE); 121 List<InteractiveTestCase> tests = new ArrayList<>(17); 122 tests.add(new IsEnabledTest()); 123 tests.add(new ServiceStartedTest()); 124 tests.add(new NotificationReceivedTest()); 125 tests.add(new LongMessageTest()); 126 tests.add(new DataIntactTest()); 127 tests.add(new AudiblyAlertedTest()); 128 tests.add(new DismissOneTest()); 129 tests.add(new DismissOneWithReasonTest()); 130 tests.add(new DismissOneWithStatsTest()); 131 tests.add(new DismissAllTest()); 132 tests.add(new SnoozeNotificationForTimeTest()); 133 tests.add(new SnoozeNotificationForTimeCancelTest()); 134 tests.add(new GetSnoozedNotificationTest()); 135 tests.add(new EnableHintsTest()); 136 if (!isAutomotive) { 137 tests.add(new LockscreenVisibilityTest()); 138 } 139 tests.add(new ReceiveAppBlockNoticeTest()); 140 tests.add(new ReceiveAppUnblockNoticeTest()); 141 if (!isAutomotive) { 142 tests.add(new ReceiveChannelBlockNoticeTest()); 143 tests.add(new ReceiveGroupBlockNoticeTest()); 144 } 145 tests.add(new RequestUnbindTest()); 146 tests.add(new RequestBindTest()); 147 tests.add(new MessageBundleTest()); 148 tests.add(new ConversationOrderingTest()); 149 tests.add(new EnableHintsTest()); 150 tests.add(new IsDisabledTest()); 151 tests.add(new ServiceStoppedTest()); 152 tests.add(new NotificationNotReceivedTest()); 153 return tests; 154 } 155 createChannels()156 private void createChannels() { 157 NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, 158 NOTIFICATION_CHANNEL_ID, IMPORTANCE_LOW); 159 NotificationChannel noisyChannel = new NotificationChannel(NOISY_NOTIFICATION_CHANNEL_ID, 160 NOISY_NOTIFICATION_CHANNEL_ID, IMPORTANCE_MAX); 161 noisyChannel.setVibrationPattern(new long[]{100, 0, 100}); 162 mNm.createNotificationChannel(channel); 163 mNm.createNotificationChannel(noisyChannel); 164 } 165 deleteChannels()166 private void deleteChannels() { 167 mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID); 168 mNm.deleteNotificationChannel(NOISY_NOTIFICATION_CHANNEL_ID); 169 } 170 171 @SuppressLint("NewApi") sendNotifications()172 private void sendNotifications() { 173 mTag1 = UUID.randomUUID().toString(); 174 Log.d(TAG, "Sending #1: " + mTag1); 175 mTag2 = UUID.randomUUID().toString(); 176 Log.d(TAG, "Sending #2: " + mTag2); 177 mTag3 = UUID.randomUUID().toString(); 178 Log.d(TAG, "Sending #3: " + mTag3); 179 180 mWhen1 = System.currentTimeMillis() + 1; 181 mWhen2 = System.currentTimeMillis() + 2; 182 mWhen3 = System.currentTimeMillis() + 3; 183 184 mIcon1 = R.drawable.ic_stat_alice; 185 mIcon2 = R.drawable.ic_stat_bob; 186 mIcon3 = R.drawable.ic_stat_charlie; 187 188 mId1 = NOTIFICATION_ID + 1; 189 mId2 = NOTIFICATION_ID + 2; 190 mId3 = NOTIFICATION_ID + 3; 191 192 mPackageString = "com.android.cts.verifier"; 193 194 Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 195 .setContentTitle("ClearTest 1") 196 .setContentText(mTag1) 197 .setSmallIcon(mIcon1) 198 .setWhen(mWhen1) 199 .setDeleteIntent(makeIntent(1, mTag1)) 200 .setOnlyAlertOnce(true) 201 .build(); 202 mNm.notify(mTag1, mId1, n1); 203 mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE; 204 205 Notification n2 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 206 .setContentTitle("ClearTest 2") 207 .setContentText(mTag2) 208 .setSmallIcon(mIcon2) 209 .setWhen(mWhen2) 210 .setDeleteIntent(makeIntent(2, mTag2)) 211 .setAutoCancel(true) 212 .build(); 213 mNm.notify(mTag2, mId2, n2); 214 mFlag2 = Notification.FLAG_AUTO_CANCEL; 215 216 Notification n3 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 217 .setContentTitle("ClearTest 3") 218 .setContentText(mTag3) 219 .setSmallIcon(mIcon3) 220 .setWhen(mWhen3) 221 .setDeleteIntent(makeIntent(3, mTag3)) 222 .setAutoCancel(true) 223 .setOnlyAlertOnce(true) 224 .build(); 225 mNm.notify(mTag3, mId3, n3); 226 mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL; 227 } 228 sendNoisyNotification()229 private void sendNoisyNotification() { 230 mTag4 = UUID.randomUUID().toString(); 231 Log.d(TAG, "Sending noisy notif: " + mTag4); 232 233 mWhen4 = System.currentTimeMillis() + 4; 234 mIcon4 = R.drawable.ic_stat_charlie; 235 mId4 = NOTIFICATION_ID + 4; 236 mPackageString = "com.android.cts.verifier"; 237 238 Notification n1 = new Notification.Builder(mContext, NOISY_NOTIFICATION_CHANNEL_ID) 239 .setContentTitle("NoisyTest 1") 240 .setContentText(mTag4) 241 .setSmallIcon(mIcon4) 242 .setWhen(mWhen4) 243 .setDeleteIntent(makeIntent(4, mTag4)) 244 .setCategory(Notification.CATEGORY_REMINDER) 245 .build(); 246 mNm.notify(mTag4, mId4, n1); 247 } 248 249 // Tests 250 private class NotificationReceivedTest extends InteractiveTestCase { 251 @Override inflate(ViewGroup parent)252 protected View inflate(ViewGroup parent) { 253 return createAutoItem(parent, R.string.nls_note_received); 254 255 } 256 257 @Override setUp()258 protected void setUp() { 259 createChannels(); 260 sendNotifications(); 261 status = READY; 262 } 263 264 @Override tearDown()265 protected void tearDown() { 266 mNm.cancelAll(); 267 MockListener.getInstance().resetData(); 268 deleteChannels(); 269 } 270 271 @Override test()272 protected void test() { 273 if (MockListener.getInstance().getPosted(mTag1) != null) { 274 status = PASS; 275 } else { 276 logFail(); 277 status = FAIL; 278 } 279 } 280 } 281 282 private class LongMessageTest extends InteractiveTestCase { 283 private ViewGroup mParent; 284 @Override inflate(ViewGroup parent)285 protected View inflate(ViewGroup parent) { 286 mParent = createAutoItem(parent, R.string.nls_anr); 287 return mParent; 288 } 289 290 @Override setUp()291 protected void setUp() { 292 createChannels(); 293 StringBuilder sb = new StringBuilder(); 294 for (int i = 0; i < 20000; i++) { 295 sb.append("\u2009\u200a" + "\u200E\u200F" + "stuff"); 296 } 297 Notification.Builder builder = new Notification.Builder( 298 mContext, NOTIFICATION_CHANNEL_ID) 299 .setSmallIcon(R.drawable.ic_stat_alice) 300 .setContentTitle("This is an long notification") 301 .setContentText("Innocuous content") 302 .setStyle(new Notification.MessagingStyle("Fake person") 303 .addMessage("hey how is it goin", 0, "Person 1") 304 .addMessage("hey", 0, "Person 1") 305 .addMessage("u there", 0, "Person 1") 306 .addMessage("how you like tHIS", 0, "Person 1") 307 .addMessage(sb.toString(), 0, "Person 1") 308 ); 309 mTag1 = UUID.randomUUID().toString(); 310 mId1 = NOTIFICATION_ID + 1; 311 mPackageString = "com.android.cts.verifier"; 312 mNm.notify(mTag1, mId1, builder.build()); 313 status = READY; 314 } 315 316 @Override tearDown()317 protected void tearDown() { 318 mNm.cancelAll(); 319 MockListener.getInstance().resetData(); 320 deleteChannels(); 321 } 322 323 @Override test()324 protected void test() { 325 StatusBarNotification sbn = MockListener.getInstance().getPosted(mTag1); 326 if (sbn == null) { 327 logFail(); 328 status = FAIL; 329 } else { 330 ViewGroup parent = mParent.findViewById(R.id.feedback); 331 parent.setVisibility(View.VISIBLE); 332 final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder( 333 NotificationListenerVerifierActivity.this, 334 sbn.getNotification()); 335 RemoteViews rv = recoveredBuilder.createContentView(); 336 View v = rv.apply(NotificationListenerVerifierActivity.this, parent); 337 parent.addView(v); 338 } 339 if (MockListener.getInstance().getPosted(mTag1) != null) { 340 status = PASS; 341 } else { 342 logFail(); 343 status = FAIL; 344 } 345 } 346 } 347 348 /** 349 * Creates a notification channel. Sends the user to settings to block the channel. Waits 350 * to receive the broadcast that the channel was blocked, and confirms that the broadcast 351 * contains the correct extras. 352 */ 353 protected class ReceiveChannelBlockNoticeTest extends InteractiveTestCase { 354 private String mChannelId; 355 private int mRetries = 2; 356 private View mView; 357 @Override inflate(ViewGroup parent)358 protected View inflate(ViewGroup parent) { 359 mView = createNlsSettingsItem(parent, R.string.nls_block_channel); 360 Button button = mView.findViewById(R.id.nls_action_button); 361 button.setEnabled(false); 362 return mView; 363 } 364 365 @Override setUp()366 protected void setUp() { 367 mChannelId = UUID.randomUUID().toString(); 368 NotificationChannel channel = new NotificationChannel( 369 mChannelId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW); 370 mNm.createNotificationChannel(channel); 371 status = READY; 372 Button button = mView.findViewById(R.id.nls_action_button); 373 button.setEnabled(true); 374 } 375 376 @Override autoStart()377 boolean autoStart() { 378 return true; 379 } 380 381 @Override test()382 protected void test() { 383 NotificationChannel channel = mNm.getNotificationChannel(mChannelId); 384 SharedPreferences prefs = mContext.getSharedPreferences( 385 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 386 387 if (channel.getImportance() == IMPORTANCE_NONE) { 388 if (prefs.contains(mChannelId) && prefs.getBoolean(mChannelId, false)) { 389 status = PASS; 390 } else { 391 if (mRetries > 0) { 392 mRetries--; 393 status = RETEST; 394 } else { 395 status = FAIL; 396 } 397 } 398 } else { 399 // user hasn't jumped to settings to block the channel yet 400 status = WAIT_FOR_USER; 401 } 402 403 next(); 404 } 405 tearDown()406 protected void tearDown() { 407 MockListener.getInstance().resetData(); 408 mNm.deleteNotificationChannel(mChannelId); 409 SharedPreferences prefs = mContext.getSharedPreferences( 410 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 411 SharedPreferences.Editor editor = prefs.edit(); 412 editor.remove(mChannelId); 413 } 414 415 @Override getIntent()416 protected Intent getIntent() { 417 return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) 418 .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()) 419 .putExtra(EXTRA_CHANNEL_ID, mChannelId); 420 } 421 } 422 423 /** 424 * Creates a notification channel group. Sends the user to settings to block the group. Waits 425 * to receive the broadcast that the group was blocked, and confirms that the broadcast contains 426 * the correct extras. 427 */ 428 protected class ReceiveGroupBlockNoticeTest extends InteractiveTestCase { 429 private String mGroupId; 430 private int mRetries = 2; 431 private View mView; 432 @Override inflate(ViewGroup parent)433 protected View inflate(ViewGroup parent) { 434 mView = createNlsSettingsItem(parent, R.string.nls_block_group); 435 Button button = mView.findViewById(R.id.nls_action_button); 436 button.setEnabled(false); 437 return mView; 438 } 439 440 @Override setUp()441 protected void setUp() { 442 mGroupId = UUID.randomUUID().toString(); 443 NotificationChannelGroup group 444 = new NotificationChannelGroup(mGroupId, "ReceiveChannelGroupBlockNoticeTest"); 445 mNm.createNotificationChannelGroup(group); 446 NotificationChannel channel = new NotificationChannel( 447 mGroupId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW); 448 channel.setGroup(mGroupId); 449 mNm.createNotificationChannel(channel); 450 status = READY; 451 Button button = mView.findViewById(R.id.nls_action_button); 452 button.setEnabled(true); 453 } 454 455 @Override autoStart()456 boolean autoStart() { 457 return true; 458 } 459 460 @Override test()461 protected void test() { 462 NotificationChannelGroup group = mNm.getNotificationChannelGroup(mGroupId); 463 SharedPreferences prefs = mContext.getSharedPreferences( 464 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 465 466 if (group.isBlocked()) { 467 if (prefs.contains(mGroupId) && prefs.getBoolean(mGroupId, false)) { 468 status = PASS; 469 } else { 470 if (mRetries > 0) { 471 mRetries--; 472 status = RETEST; 473 } else { 474 status = FAIL; 475 } 476 } 477 } else { 478 // user hasn't jumped to settings to block the group yet 479 status = WAIT_FOR_USER; 480 } 481 482 next(); 483 } 484 tearDown()485 protected void tearDown() { 486 MockListener.getInstance().resetData(); 487 mNm.deleteNotificationChannelGroup(mGroupId); 488 SharedPreferences prefs = mContext.getSharedPreferences( 489 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 490 SharedPreferences.Editor editor = prefs.edit(); 491 editor.remove(mGroupId); 492 } 493 494 @Override getIntent()495 protected Intent getIntent() { 496 return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) 497 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 498 .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()); 499 } 500 } 501 502 /** 503 * Creates a notification channel. Sends the user to settings to disallow the channel from 504 * showing on the lockscreen. Sends a notification, checks the lockscreen setting in the 505 * ranking object. 506 */ 507 protected class LockscreenVisibilityTest extends InteractiveTestCase { 508 private int mRetries = 3; 509 private View mView; 510 @Override inflate(ViewGroup parent)511 protected View inflate(ViewGroup parent) { 512 mView = createNlsSettingsItem(parent, R.string.nls_visibility); 513 Button button = mView.findViewById(R.id.nls_action_button); 514 button.setEnabled(false); 515 return mView; 516 } 517 518 @Override setUp()519 protected void setUp() { 520 createChannels(); 521 status = READY; 522 Button button = mView.findViewById(R.id.nls_action_button); 523 button.setEnabled(true); 524 } 525 526 @Override autoStart()527 boolean autoStart() { 528 return true; 529 } 530 531 @Override test()532 protected void test() { 533 NotificationChannel channel = mNm.getNotificationChannel(NOTIFICATION_CHANNEL_ID); 534 if (channel.getLockscreenVisibility() == VISIBILITY_PRIVATE) { 535 if (mRetries == 3) { 536 sendNotifications(); 537 } 538 539 NotificationListenerService.Ranking rank = 540 new NotificationListenerService.Ranking(); 541 StatusBarNotification sbn = MockListener.getInstance().getPosted(mTag1); 542 if (sbn != null) { 543 MockListener.getInstance().getCurrentRanking().getRanking(sbn.getKey(), rank); 544 if (rank.getLockscreenVisibilityOverride() == VISIBILITY_PRIVATE) { 545 status = PASS; 546 } else { 547 logFail("Actual visibility:" + rank.getLockscreenVisibilityOverride()); 548 status = FAIL; 549 } 550 } else { 551 if (mRetries > 0) { 552 mRetries--; 553 status = RETEST; 554 } else { 555 logFail("Notification wasn't posted"); 556 status = FAIL; 557 } 558 } 559 560 } else { 561 // user hasn't jumped to settings yet 562 status = WAIT_FOR_USER; 563 } 564 565 next(); 566 } 567 tearDown()568 protected void tearDown() { 569 MockListener.getInstance().resetData(); 570 deleteChannels(); 571 } 572 573 @Override getIntent()574 protected Intent getIntent() { 575 return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) 576 .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()) 577 .putExtra(EXTRA_CHANNEL_ID, NOTIFICATION_CHANNEL_ID); 578 } 579 } 580 581 /** 582 * Sends the user to settings to block the app. Waits to receive the broadcast that the app was 583 * blocked, and confirms that the broadcast contains the correct extras. 584 */ 585 protected class ReceiveAppBlockNoticeTest extends InteractiveTestCase { 586 private int mRetries = 2; 587 private View mView; 588 @Override inflate(ViewGroup parent)589 protected View inflate(ViewGroup parent) { 590 mView = createNlsSettingsItem(parent, R.string.nls_block_app); 591 Button button = mView.findViewById(R.id.nls_action_button); 592 button.setEnabled(false); 593 return mView; 594 } 595 596 @Override setUp()597 protected void setUp() { 598 status = READY; 599 Button button = mView.findViewById(R.id.nls_action_button); 600 button.setEnabled(true); 601 } 602 603 @Override autoStart()604 boolean autoStart() { 605 return true; 606 } 607 608 @Override test()609 protected void test() { 610 SharedPreferences prefs = mContext.getSharedPreferences( 611 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 612 613 if (!mNm.areNotificationsEnabled()) { 614 Log.d(TAG, "Got broadcast " + prefs.contains(mContext.getPackageName())); 615 Log.d(TAG, "Broadcast contains correct data? " + 616 prefs.getBoolean(mContext.getPackageName(), false)); 617 if (prefs.contains(mContext.getPackageName()) 618 && prefs.getBoolean(mContext.getPackageName(), false)) { 619 status = PASS; 620 } else { 621 if (mRetries > 0) { 622 mRetries--; 623 status = RETEST; 624 } else { 625 status = FAIL; 626 } 627 } 628 } else { 629 Log.d(TAG, "Notifications still enabled"); 630 // user hasn't jumped to settings to block the app yet 631 status = WAIT_FOR_USER; 632 } 633 634 next(); 635 } 636 tearDown()637 protected void tearDown() { 638 MockListener.getInstance().resetData(); 639 SharedPreferences prefs = mContext.getSharedPreferences( 640 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 641 SharedPreferences.Editor editor = prefs.edit(); 642 editor.remove(mContext.getPackageName()); 643 } 644 645 @Override getIntent()646 protected Intent getIntent() { 647 return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) 648 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 649 .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()); 650 } 651 } 652 653 /** 654 * Sends the user to settings to unblock the app. Waits to receive the broadcast that the app 655 * was unblocked, and confirms that the broadcast contains the correct extras. 656 */ 657 protected class ReceiveAppUnblockNoticeTest extends InteractiveTestCase { 658 private int mRetries = 2; 659 private View mView; 660 @Override inflate(ViewGroup parent)661 protected View inflate(ViewGroup parent) { 662 mView = createNlsSettingsItem(parent, R.string.nls_unblock_app); 663 Button button = mView.findViewById(R.id.nls_action_button); 664 button.setEnabled(false); 665 return mView; 666 } 667 668 @Override setUp()669 protected void setUp() { 670 status = READY; 671 Button button = mView.findViewById(R.id.nls_action_button); 672 button.setEnabled(true); 673 } 674 675 @Override autoStart()676 boolean autoStart() { 677 return true; 678 } 679 680 @Override test()681 protected void test() { 682 SharedPreferences prefs = mContext.getSharedPreferences( 683 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 684 685 if (mNm.areNotificationsEnabled()) { 686 if (prefs.contains(mContext.getPackageName()) 687 && !prefs.getBoolean(mContext.getPackageName(), true)) { 688 status = PASS; 689 } else { 690 if (mRetries > 0) { 691 mRetries--; 692 status = RETEST; 693 } else { 694 status = FAIL; 695 } 696 } 697 } else { 698 // user hasn't jumped to settings to block the app yet 699 status = WAIT_FOR_USER; 700 } 701 702 next(); 703 } 704 tearDown()705 protected void tearDown() { 706 MockListener.getInstance().resetData(); 707 SharedPreferences prefs = mContext.getSharedPreferences( 708 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 709 SharedPreferences.Editor editor = prefs.edit(); 710 editor.remove(mContext.getPackageName()); 711 } 712 713 @Override getIntent()714 protected Intent getIntent() { 715 return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) 716 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 717 .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()); 718 } 719 } 720 721 private class DataIntactTest extends InteractiveTestCase { 722 @Override inflate(ViewGroup parent)723 protected View inflate(ViewGroup parent) { 724 return createAutoItem(parent, R.string.nls_payload_intact); 725 } 726 727 @Override setUp()728 protected void setUp() { 729 createChannels(); 730 sendNotifications(); 731 status = READY; 732 } 733 734 @Override test()735 protected void test() { 736 List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); 737 738 Set<String> found = new HashSet<String>(); 739 if (result.size() == 0) { 740 status = FAIL; 741 return; 742 } 743 boolean pass = true; 744 for (JSONObject payload : result) { 745 try { 746 pass &= checkEquals(mPackageString, 747 payload.getString(JSON_PACKAGE), 748 "data integrity test: notification package (%s, %s)"); 749 String tag = payload.getString(JSON_TAG); 750 if (mTag1.equals(tag)) { 751 found.add(mTag1); 752 pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON), 753 "data integrity test: notification icon (%d, %d)"); 754 pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS), 755 "data integrity test: notification flags (%d, %d)"); 756 pass &= checkEquals(mId1, payload.getInt(JSON_ID), 757 "data integrity test: notification ID (%d, %d)"); 758 pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN), 759 "data integrity test: notification when (%d, %d)"); 760 } else if (mTag2.equals(tag)) { 761 found.add(mTag2); 762 pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON), 763 "data integrity test: notification icon (%d, %d)"); 764 pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS), 765 "data integrity test: notification flags (%d, %d)"); 766 pass &= checkEquals(mId2, payload.getInt(JSON_ID), 767 "data integrity test: notification ID (%d, %d)"); 768 pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN), 769 "data integrity test: notification when (%d, %d)"); 770 } else if (mTag3.equals(tag)) { 771 found.add(mTag3); 772 pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON), 773 "data integrity test: notification icon (%d, %d)"); 774 pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS), 775 "data integrity test: notification flags (%d, %d)"); 776 pass &= checkEquals(mId3, payload.getInt(JSON_ID), 777 "data integrity test: notification ID (%d, %d)"); 778 pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN), 779 "data integrity test: notification when (%d, %d)"); 780 } 781 } catch (JSONException e) { 782 pass = false; 783 Log.e(TAG, "failed to unpack data from mocklistener", e); 784 } 785 } 786 787 pass &= found.size() >= 3; 788 status = pass ? PASS : FAIL; 789 } 790 791 @Override tearDown()792 protected void tearDown() { 793 mNm.cancelAll(); 794 MockListener.getInstance().resetData(); 795 deleteChannels(); 796 } 797 } 798 799 private class AudiblyAlertedTest extends InteractiveTestCase { 800 @Override inflate(ViewGroup parent)801 protected View inflate(ViewGroup parent) { 802 return createAutoItem(parent, R.string.nls_audibly_alerted); 803 } 804 805 @Override setUp()806 protected void setUp() { 807 createChannels(); 808 sendNotifications(); 809 sendNoisyNotification(); 810 status = READY; 811 } 812 813 @Override test()814 protected void test() { 815 List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); 816 817 Set<String> found = new HashSet<>(); 818 if (result.size() == 0) { 819 status = FAIL; 820 return; 821 } 822 boolean pass = true; 823 for (JSONObject payload : result) { 824 try { 825 String tag = payload.getString(JSON_TAG); 826 if (mTag4.equals(tag)) { 827 found.add(mTag4); 828 boolean lastAudiblyAlertedSet 829 = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > -1; 830 if (!lastAudiblyAlertedSet) { 831 logWithStack( 832 "noisy notification test: getLastAudiblyAlertedMillis not set"); 833 } 834 pass &= lastAudiblyAlertedSet; 835 } else if (payload.getString(JSON_PACKAGE).equals(mPackageString)) { 836 found.add(tag); 837 boolean lastAudiblyAlertedSet 838 = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > 0; 839 if (lastAudiblyAlertedSet) { 840 logWithStack( 841 "noisy notification test: getLastAudiblyAlertedMillis set " 842 + "incorrectly"); 843 } 844 pass &= !lastAudiblyAlertedSet; 845 } 846 } catch (JSONException e) { 847 pass = false; 848 Log.e(TAG, "failed to unpack data from mocklistener", e); 849 } 850 } 851 852 pass &= found.size() >= 4; 853 status = pass ? PASS : FAIL; 854 } 855 856 @Override tearDown()857 protected void tearDown() { 858 mNm.cancelAll(); 859 MockListener.getInstance().resetData(); 860 deleteChannels(); 861 } 862 } 863 864 private class DismissOneTest extends InteractiveTestCase { 865 @Override inflate(ViewGroup parent)866 protected View inflate(ViewGroup parent) { 867 return createAutoItem(parent, R.string.nls_clear_one); 868 } 869 870 @Override setUp()871 protected void setUp() { 872 createChannels(); 873 sendNotifications(); 874 status = READY; 875 } 876 877 @Override test()878 protected void test() { 879 if (status == READY) { 880 MockListener.getInstance().cancelNotification( 881 MockListener.getInstance().getKeyForTag(mTag1)); 882 status = RETEST; 883 } else { 884 List<String> result = new ArrayList<>(MockListener.getInstance().mRemoved); 885 if (result.size() != 0 886 && result.contains(mTag1) 887 && !result.contains(mTag2) 888 && !result.contains(mTag3)) { 889 status = PASS; 890 } else { 891 logFail(); 892 status = FAIL; 893 } 894 } 895 } 896 897 @Override tearDown()898 protected void tearDown() { 899 mNm.cancelAll(); 900 deleteChannels(); 901 MockListener.getInstance().resetData(); 902 } 903 } 904 905 private class DismissOneWithReasonTest extends InteractiveTestCase { 906 int mRetries = 3; 907 908 @Override inflate(ViewGroup parent)909 protected View inflate(ViewGroup parent) { 910 return createAutoItem(parent, R.string.nls_clear_one_reason); 911 } 912 913 @Override setUp()914 protected void setUp() { 915 createChannels(); 916 sendNotifications(); 917 status = READY; 918 } 919 920 @Override test()921 protected void test() { 922 if (status == READY) { 923 MockListener.getInstance().cancelNotification( 924 MockListener.getInstance().getKeyForTag(mTag1)); 925 status = RETEST; 926 } else { 927 List<JSONObject> result = 928 new ArrayList<>(MockListener.getInstance().mRemovedReason.values()); 929 boolean pass = false; 930 for (JSONObject payload : result) { 931 try { 932 pass |= (checkEquals(mTag1, 933 payload.getString(JSON_TAG), 934 "data dismissal test: notification tag (%s, %s)") 935 && checkEquals(REASON_LISTENER_CANCEL, 936 payload.getInt(JSON_REASON), 937 "data dismissal test: reason (%d, %d)")); 938 if(pass) { 939 break; 940 } 941 } catch (JSONException e) { 942 e.printStackTrace(); 943 } 944 } 945 if (pass) { 946 status = PASS; 947 } else { 948 if (--mRetries > 0) { 949 sleep(100); 950 status = RETEST; 951 } else { 952 status = FAIL; 953 } 954 } 955 } 956 } 957 958 @Override tearDown()959 protected void tearDown() { 960 mNm.cancelAll(); 961 deleteChannels(); 962 MockListener.getInstance().resetData(); 963 } 964 } 965 966 private class DismissOneWithStatsTest extends InteractiveTestCase { 967 int mRetries = 3; 968 969 @Override inflate(ViewGroup parent)970 protected View inflate(ViewGroup parent) { 971 return createAutoItem(parent, R.string.nls_clear_one_stats); 972 } 973 974 @Override setUp()975 protected void setUp() { 976 createChannels(); 977 sendNotifications(); 978 status = READY; 979 } 980 981 @Override test()982 protected void test() { 983 if (status == READY) { 984 MockListener.getInstance().cancelNotification( 985 MockListener.getInstance().getKeyForTag(mTag1)); 986 status = RETEST; 987 } else { 988 List<JSONObject> result = 989 new ArrayList<>(MockListener.getInstance().mRemovedReason.values()); 990 boolean pass = true; 991 for (JSONObject payload : result) { 992 try { 993 pass &= (payload.getBoolean(JSON_STATS) == false); 994 } catch (JSONException e) { 995 e.printStackTrace(); 996 pass = false; 997 } 998 } 999 if (pass) { 1000 status = PASS; 1001 } else { 1002 if (--mRetries > 0) { 1003 sleep(100); 1004 status = RETEST; 1005 } else { 1006 logFail("Notification listener got populated stats object."); 1007 status = FAIL; 1008 } 1009 } 1010 } 1011 } 1012 1013 @Override tearDown()1014 protected void tearDown() { 1015 mNm.cancelAll(); 1016 deleteChannels(); 1017 MockListener.getInstance().resetData(); 1018 } 1019 } 1020 1021 private class DismissAllTest extends InteractiveTestCase { 1022 @Override inflate(ViewGroup parent)1023 protected View inflate(ViewGroup parent) { 1024 return createAutoItem(parent, R.string.nls_clear_all); 1025 } 1026 1027 @Override setUp()1028 protected void setUp() { 1029 createChannels(); 1030 sendNotifications(); 1031 status = READY; 1032 } 1033 1034 @Override test()1035 protected void test() { 1036 if (status == READY) { 1037 MockListener.getInstance().cancelAllNotifications(); 1038 status = RETEST; 1039 } else { 1040 List<String> result = new ArrayList<>(MockListener.getInstance().mRemoved); 1041 if (result.size() != 0 1042 && result.contains(mTag1) 1043 && result.contains(mTag2) 1044 && result.contains(mTag3)) { 1045 status = PASS; 1046 } else { 1047 logFail(); 1048 status = FAIL; 1049 } 1050 } 1051 } 1052 1053 @Override tearDown()1054 protected void tearDown() { 1055 mNm.cancelAll(); 1056 deleteChannels(); 1057 MockListener.getInstance().resetData(); 1058 } 1059 } 1060 1061 private class IsDisabledTest extends InteractiveTestCase { 1062 @Override inflate(ViewGroup parent)1063 protected View inflate(ViewGroup parent) { 1064 return createNlsSettingsItem(parent, R.string.nls_disable_service); 1065 } 1066 1067 @Override autoStart()1068 boolean autoStart() { 1069 return true; 1070 } 1071 1072 @Override test()1073 protected void test() { 1074 String listeners = Secure.getString(getContentResolver(), 1075 ENABLED_NOTIFICATION_LISTENERS); 1076 if (listeners == null || !listeners.contains(LISTENER_PATH)) { 1077 status = PASS; 1078 } else { 1079 status = WAIT_FOR_USER; 1080 } 1081 } 1082 1083 @Override tearDown()1084 protected void tearDown() { 1085 MockListener.getInstance().resetData(); 1086 } 1087 1088 @Override getIntent()1089 protected Intent getIntent() { 1090 return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS); 1091 } 1092 } 1093 1094 private class ServiceStoppedTest extends InteractiveTestCase { 1095 int mRetries = 3; 1096 @Override inflate(ViewGroup parent)1097 protected View inflate(ViewGroup parent) { 1098 return createAutoItem(parent, R.string.nls_service_stopped); 1099 } 1100 1101 @Override test()1102 protected void test() { 1103 if (mNm.getEffectsSuppressor() == null && (MockListener.getInstance() == null 1104 || !MockListener.getInstance().isConnected)) { 1105 status = PASS; 1106 } else { 1107 if (--mRetries > 0) { 1108 sleep(100); 1109 status = RETEST; 1110 } else { 1111 status = FAIL; 1112 } 1113 } 1114 } 1115 1116 @Override getIntent()1117 protected Intent getIntent() { 1118 return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS); 1119 } 1120 } 1121 1122 private class NotificationNotReceivedTest extends InteractiveTestCase { 1123 @Override inflate(ViewGroup parent)1124 protected View inflate(ViewGroup parent) { 1125 return createAutoItem(parent, R.string.nls_note_missed); 1126 1127 } 1128 1129 @Override setUp()1130 protected void setUp() { 1131 createChannels(); 1132 sendNotifications(); 1133 status = READY; 1134 } 1135 1136 @Override test()1137 protected void test() { 1138 if (MockListener.getInstance() == null) { 1139 status = PASS; 1140 } else { 1141 if (MockListener.getInstance().mPosted.size() == 0) { 1142 status = PASS; 1143 } else { 1144 logFail(); 1145 status = FAIL; 1146 } 1147 } 1148 next(); 1149 } 1150 1151 @Override tearDown()1152 protected void tearDown() { 1153 mNm.cancelAll(); 1154 deleteChannels(); 1155 if (MockListener.getInstance() != null) { 1156 MockListener.getInstance().resetData(); 1157 } 1158 } 1159 } 1160 1161 private class RequestUnbindTest extends InteractiveTestCase { 1162 int mRetries = 5; 1163 1164 @Override inflate(ViewGroup parent)1165 protected View inflate(ViewGroup parent) { 1166 return createAutoItem(parent, R.string.nls_snooze); 1167 1168 } 1169 1170 @Override setUp()1171 protected void setUp() { 1172 status = READY; 1173 MockListener.getInstance().requestListenerHints( 1174 MockListener.HINT_HOST_DISABLE_CALL_EFFECTS); 1175 } 1176 1177 @Override test()1178 protected void test() { 1179 if (status == READY) { 1180 MockListener.getInstance().requestUnbind(); 1181 status = RETEST; 1182 } else { 1183 if (mNm.getEffectsSuppressor() == null && !MockListener.getInstance().isConnected) { 1184 status = PASS; 1185 } else { 1186 if (--mRetries > 0) { 1187 status = RETEST; 1188 } else { 1189 logFail(); 1190 status = FAIL; 1191 } 1192 } 1193 next(); 1194 } 1195 } 1196 } 1197 1198 private class RequestBindTest extends InteractiveTestCase { 1199 int mRetries = 5; 1200 1201 @Override inflate(ViewGroup parent)1202 protected View inflate(ViewGroup parent) { 1203 return createAutoItem(parent, R.string.nls_unsnooze); 1204 1205 } 1206 1207 @Override test()1208 protected void test() { 1209 if (status == READY) { 1210 MockListener.requestRebind(MockListener.COMPONENT_NAME); 1211 status = RETEST; 1212 } else { 1213 if (MockListener.getInstance().isConnected) { 1214 status = PASS; 1215 next(); 1216 } else { 1217 if (--mRetries > 0) { 1218 status = RETEST; 1219 next(); 1220 } else { 1221 logFail(); 1222 status = FAIL; 1223 } 1224 } 1225 } 1226 } 1227 } 1228 1229 private class EnableHintsTest extends InteractiveTestCase { 1230 @Override inflate(ViewGroup parent)1231 protected View inflate(ViewGroup parent) { 1232 return createAutoItem(parent, R.string.nls_hints); 1233 1234 } 1235 1236 @Override test()1237 protected void test() { 1238 if (status == READY) { 1239 MockListener.getInstance().requestListenerHints( 1240 MockListener.HINT_HOST_DISABLE_CALL_EFFECTS); 1241 status = RETEST; 1242 } else { 1243 int result = MockListener.getInstance().getCurrentListenerHints(); 1244 if ((result & MockListener.HINT_HOST_DISABLE_CALL_EFFECTS) 1245 == MockListener.HINT_HOST_DISABLE_CALL_EFFECTS) { 1246 status = PASS; 1247 next(); 1248 } else { 1249 logFail(); 1250 status = FAIL; 1251 } 1252 } 1253 } 1254 } 1255 1256 private class SnoozeNotificationForTimeTest extends InteractiveTestCase { 1257 final static int READY_TO_SNOOZE = 0; 1258 final static int SNOOZED = 1; 1259 final static int READY_TO_CHECK_FOR_UNSNOOZE = 2; 1260 int state = -1; 1261 long snoozeTime = 3000; 1262 1263 @Override inflate(ViewGroup parent)1264 protected View inflate(ViewGroup parent) { 1265 return createAutoItem(parent, R.string.nls_snooze_one_time); 1266 } 1267 1268 @Override setUp()1269 protected void setUp() { 1270 createChannels(); 1271 sendNotifications(); 1272 status = READY; 1273 state = READY_TO_SNOOZE; 1274 delay(); 1275 } 1276 1277 @Override test()1278 protected void test() { 1279 status = RETEST; 1280 if (state == READY_TO_SNOOZE) { 1281 MockListener.getInstance().snoozeNotification( 1282 MockListener.getInstance().getKeyForTag(mTag1), snoozeTime); 1283 state = SNOOZED; 1284 } else if (state == SNOOZED) { 1285 List<JSONObject> result = 1286 new ArrayList<>(MockListener.getInstance().mRemovedReason.values()); 1287 boolean pass = false; 1288 for (JSONObject payload : result) { 1289 try { 1290 pass |= (checkEquals(mTag1, 1291 payload.getString(JSON_TAG), 1292 "data dismissal test: notification tag (%s, %s)") 1293 && checkEquals(MockListener.REASON_SNOOZED, 1294 payload.getInt(JSON_REASON), 1295 "data dismissal test: reason (%d, %d)")); 1296 if (pass) { 1297 break; 1298 } 1299 } catch (JSONException e) { 1300 e.printStackTrace(); 1301 } 1302 } 1303 if (!pass) { 1304 logFail(); 1305 status = FAIL; 1306 next(); 1307 return; 1308 } else { 1309 state = READY_TO_CHECK_FOR_UNSNOOZE; 1310 } 1311 } else { 1312 if (MockListener.getInstance().getPosted(mTag1) != null) { 1313 status = PASS; 1314 } else { 1315 logFail(); 1316 status = FAIL; 1317 } 1318 } 1319 } 1320 1321 @Override tearDown()1322 protected void tearDown() { 1323 mNm.cancelAll(); 1324 deleteChannels(); 1325 MockListener.getInstance().resetData(); 1326 delay(); 1327 } 1328 } 1329 1330 /** 1331 * Posts notifications, snoozes one of them. Verifies that it is snoozed. Cancels all 1332 * notifications and reposts them. Confirms that the original notification is still snoozed. 1333 */ 1334 private class SnoozeNotificationForTimeCancelTest extends InteractiveTestCase { 1335 1336 final static int READY_TO_SNOOZE = 0; 1337 final static int SNOOZED = 1; 1338 final static int READY_TO_CHECK_FOR_SNOOZE = 2; 1339 int state = -1; 1340 long snoozeTime = 10000; 1341 private String tag; 1342 1343 @Override inflate(ViewGroup parent)1344 protected View inflate(ViewGroup parent) { 1345 return createAutoItem(parent, R.string.nls_snooze_one_time); 1346 } 1347 1348 @Override setUp()1349 protected void setUp() { 1350 createChannels(); 1351 sendNotifications(); 1352 tag = mTag1; 1353 status = READY; 1354 state = READY_TO_SNOOZE; 1355 delay(); 1356 } 1357 1358 @Override test()1359 protected void test() { 1360 status = RETEST; 1361 if (state == READY_TO_SNOOZE) { 1362 MockListener.getInstance().snoozeNotification( 1363 MockListener.getInstance().getKeyForTag(tag), snoozeTime); 1364 state = SNOOZED; 1365 } else if (state == SNOOZED) { 1366 List<String> result = getSnoozed(); 1367 if (result.size() >= 1 1368 && result.contains(tag)) { 1369 // cancel and repost 1370 sendNotifications(); 1371 state = READY_TO_CHECK_FOR_SNOOZE; 1372 } else { 1373 logFail(); 1374 status = FAIL; 1375 } 1376 } else { 1377 List<String> result = getSnoozed(); 1378 if (result.size() >= 1 1379 && result.contains(tag)) { 1380 status = PASS; 1381 } else { 1382 logFail(); 1383 status = FAIL; 1384 } 1385 } 1386 } 1387 getSnoozed()1388 private List<String> getSnoozed() { 1389 List<String> result = new ArrayList<>(); 1390 StatusBarNotification[] snoozed = MockListener.getInstance().getSnoozedNotifications(); 1391 for (StatusBarNotification sbn : snoozed) { 1392 result.add(sbn.getTag()); 1393 } 1394 return result; 1395 } 1396 1397 @Override tearDown()1398 protected void tearDown() { 1399 mNm.cancelAll(); 1400 deleteChannels(); 1401 MockListener.getInstance().resetData(); 1402 } 1403 } 1404 1405 private class GetSnoozedNotificationTest extends InteractiveTestCase { 1406 final static int READY_TO_SNOOZE = 0; 1407 final static int SNOOZED = 1; 1408 final static int READY_TO_CHECK_FOR_GET_SNOOZE = 2; 1409 int state = -1; 1410 long snoozeTime = 30000; 1411 1412 @Override inflate(ViewGroup parent)1413 protected View inflate(ViewGroup parent) { 1414 return createAutoItem(parent, R.string.nls_get_snoozed); 1415 } 1416 1417 @Override setUp()1418 protected void setUp() { 1419 createChannels(); 1420 sendNotifications(); 1421 status = READY; 1422 state = READY_TO_SNOOZE; 1423 } 1424 1425 @Override test()1426 protected void test() { 1427 status = RETEST; 1428 if (state == READY_TO_SNOOZE) { 1429 MockListener.getInstance().snoozeNotification( 1430 MockListener.getInstance().getKeyForTag(mTag1), snoozeTime); 1431 MockListener.getInstance().snoozeNotification( 1432 MockListener.getInstance().getKeyForTag(mTag2), snoozeTime); 1433 state = SNOOZED; 1434 } else if (state == SNOOZED) { 1435 List<JSONObject> result = 1436 new ArrayList<>(MockListener.getInstance().mRemovedReason.values()); 1437 if (result.size() == 0) { 1438 status = FAIL; 1439 return; 1440 } 1441 boolean pass = false; 1442 for (JSONObject payload : result) { 1443 try { 1444 pass |= (checkEquals(mTag1, 1445 payload.getString(JSON_TAG), 1446 "data dismissal test: notification tag (%s, %s)") 1447 && checkEquals(MockListener.REASON_SNOOZED, 1448 payload.getInt(JSON_REASON), 1449 "data dismissal test: reason (%d, %d)")); 1450 if (pass) { 1451 break; 1452 } 1453 } catch (JSONException e) { 1454 e.printStackTrace(); 1455 } 1456 } 1457 if (!pass) { 1458 logFail(); 1459 status = FAIL; 1460 } else { 1461 state = READY_TO_CHECK_FOR_GET_SNOOZE; 1462 } 1463 } else { 1464 List<String> result = new ArrayList<>(); 1465 StatusBarNotification[] snoozed = 1466 MockListener.getInstance().getSnoozedNotifications(); 1467 for (StatusBarNotification sbn : snoozed) { 1468 result.add(sbn.getTag()); 1469 } 1470 if (result.size() >= 2 1471 && result.contains(mTag1) 1472 && result.contains(mTag2)) { 1473 status = PASS; 1474 } else { 1475 logFail(); 1476 status = FAIL; 1477 } 1478 } 1479 } 1480 1481 @Override tearDown()1482 protected void tearDown() { 1483 mNm.cancelAll(); 1484 deleteChannels(); 1485 MockListener.getInstance().resetData(); 1486 delay(); 1487 } 1488 } 1489 1490 /** Tests that the extras {@link Bundle} in a MessagingStyle#Message is preserved. */ 1491 private class MessageBundleTest extends InteractiveTestCase { 1492 private final String extrasKey1 = "extras_key_1"; 1493 private final CharSequence extrasValue1 = "extras_value_1"; 1494 private final String extrasKey2 = "extras_key_2"; 1495 private final CharSequence extrasValue2 = "extras_value_2"; 1496 1497 @Override inflate(ViewGroup parent)1498 protected View inflate(ViewGroup parent) { 1499 return createAutoItem(parent, R.string.msg_extras_preserved); 1500 } 1501 1502 @Override setUp()1503 protected void setUp() { 1504 createChannels(); 1505 sendMessagingNotification(); 1506 status = READY; 1507 } 1508 1509 @Override tearDown()1510 protected void tearDown() { 1511 mNm.cancelAll(); 1512 deleteChannels(); 1513 delay(); 1514 } 1515 sendMessagingNotification()1516 private void sendMessagingNotification() { 1517 mTag1 = UUID.randomUUID().toString(); 1518 mNm.cancelAll(); 1519 mWhen1 = System.currentTimeMillis() + 1; 1520 mIcon1 = R.drawable.ic_stat_alice; 1521 mId1 = NOTIFICATION_ID + 1; 1522 1523 Notification.MessagingStyle.Message msg1 = 1524 new Notification.MessagingStyle.Message("text1", 0 /* timestamp */, "sender1"); 1525 msg1.getExtras().putCharSequence(extrasKey1, extrasValue1); 1526 1527 Notification.MessagingStyle.Message msg2 = 1528 new Notification.MessagingStyle.Message("text2", 1 /* timestamp */, "sender2"); 1529 msg2.getExtras().putCharSequence(extrasKey2, extrasValue2); 1530 1531 Notification.MessagingStyle style = new Notification.MessagingStyle("display_name"); 1532 style.addMessage(msg1); 1533 style.addMessage(msg2); 1534 1535 Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 1536 .setContentTitle("ClearTest 1") 1537 .setContentText(mTag1.toString()) 1538 .setPriority(Notification.PRIORITY_LOW) 1539 .setSmallIcon(mIcon1) 1540 .setWhen(mWhen1) 1541 .setDeleteIntent(makeIntent(1, mTag1)) 1542 .setOnlyAlertOnce(true) 1543 .setStyle(style) 1544 .build(); 1545 mNm.notify(mTag1, mId1, n1); 1546 mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE; 1547 } 1548 1549 // Returns true on success. verifyMessage( NotificationCompat.MessagingStyle.Message message, String extrasKey, CharSequence extrasValue)1550 private boolean verifyMessage( 1551 NotificationCompat.MessagingStyle.Message message, 1552 String extrasKey, 1553 CharSequence extrasValue) { 1554 return message.getExtras() != null 1555 && message.getExtras().getCharSequence(extrasKey) != null 1556 && message.getExtras().getCharSequence(extrasKey).equals(extrasValue); 1557 } 1558 1559 @Override test()1560 protected void test() { 1561 List<Notification> result = 1562 new ArrayList<>(MockListener.getInstance().mPostedNotifications); 1563 if (result.size() != 1 || result.get(0) == null) { 1564 logFail(); 1565 status = FAIL; 1566 next(); 1567 return; 1568 } 1569 // Can only read in MessagingStyle using the compat class. 1570 NotificationCompat.MessagingStyle readStyle = 1571 NotificationCompat.MessagingStyle 1572 .extractMessagingStyleFromNotification( 1573 result.get(0)); 1574 if (readStyle == null || readStyle.getMessages().size() != 2) { 1575 status = FAIL; 1576 logFail(); 1577 next(); 1578 return; 1579 } 1580 1581 if (!verifyMessage(readStyle.getMessages().get(0), extrasKey1, 1582 extrasValue1) 1583 || !verifyMessage( 1584 readStyle.getMessages().get(1), extrasKey2, extrasValue2)) { 1585 status = FAIL; 1586 logFail(); 1587 next(); 1588 return; 1589 } 1590 1591 status = PASS; 1592 } 1593 } 1594 1595 /** 1596 * Tests that conversation notifications appear at the top of the shade, if the device supports 1597 * a separate conversation section 1598 */ 1599 private class ConversationOrderingTest extends InteractiveTestCase { 1600 private static final String SHARE_SHORTCUT_ID = "shareShortcut"; 1601 private static final String SHORTCUT_CATEGORY = 1602 "com.android.cts.verifier.notifications.SHORTCUT_CATEGORY"; 1603 1604 @Override setUp()1605 protected void setUp() { 1606 createChannels(); 1607 createDynamicShortcut(); 1608 sendNotifications(); 1609 status = READY; 1610 } 1611 1612 @Override tearDown()1613 protected void tearDown() { 1614 mNm.cancelAll(); 1615 deleteChannels(); 1616 delay(); 1617 } 1618 1619 @Override inflate(ViewGroup parent)1620 protected View inflate(ViewGroup parent) { 1621 return createPassFailItem(parent, R.string.conversation_section_ordering); 1622 } 1623 createDynamicShortcut()1624 private void createDynamicShortcut() { 1625 Person person = new Person.Builder() 1626 .setBot(false) 1627 .setIcon(Icon.createWithResource(mContext, R.drawable.ic_stat_alice)) 1628 .setName("Person A") 1629 .setImportant(true) 1630 .build(); 1631 1632 Set<String> categorySet = new ArraySet<>(); 1633 categorySet.add(SHORTCUT_CATEGORY); 1634 Intent shortcutIntent = 1635 new Intent(mContext, BubbleActivity.class); 1636 shortcutIntent.setAction(Intent.ACTION_VIEW); 1637 1638 ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, SHARE_SHORTCUT_ID) 1639 .setShortLabel(SHARE_SHORTCUT_ID) 1640 .setIcon(Icon.createWithResource(mContext, R.drawable.ic_stat_alice)) 1641 .setIntent(shortcutIntent) 1642 .setPerson(person) 1643 .setCategories(categorySet) 1644 .setLongLived(true) 1645 .build(); 1646 1647 ShortcutManager scManager = 1648 (ShortcutManager) mContext.getSystemService(Context.SHORTCUT_SERVICE); 1649 scManager.addDynamicShortcuts(Arrays.asList(shortcut)); 1650 } 1651 sendNotifications()1652 private void sendNotifications() { 1653 mTag1 = UUID.randomUUID().toString(); 1654 mId1 = NOTIFICATION_ID + 1; 1655 1656 Person person = new Person.Builder() 1657 .setName("Person A") 1658 .build(); 1659 1660 Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 1661 .setContentTitle("ConversationOrderingTest") 1662 .setContentText(mTag1) 1663 .setSmallIcon(R.drawable.ic_stat_alice) 1664 .setShortcutId(SHARE_SHORTCUT_ID) 1665 .setStyle(new Notification.MessagingStyle(person) 1666 .setConversationTitle("Bubble Chat") 1667 .addMessage("Hello?", 1668 SystemClock.currentThreadTimeMillis() - 300000, person) 1669 .addMessage("Is it me you're looking for?", 1670 SystemClock.currentThreadTimeMillis(), person) 1671 ) 1672 .build(); 1673 mNm.notify(mTag1, mId1, n1); 1674 1675 mTag2 = UUID.randomUUID().toString(); 1676 mId2 = mId1 + 1; 1677 Notification n2 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 1678 .setContentTitle("Non-Person Notification") 1679 .setContentText(mTag1) 1680 .setSmallIcon(R.drawable.ic_stat_alice) 1681 .build(); 1682 mNm.notify(mTag2, mId2, n2); 1683 } 1684 1685 @Override autoStart()1686 boolean autoStart() { 1687 return true; 1688 } 1689 1690 @Override test()1691 protected void test() { 1692 status = WAIT_FOR_USER; 1693 next(); 1694 } 1695 } 1696 1697 protected class SendUserToChangeFilter extends InteractiveTestCase { 1698 @Override inflate(ViewGroup parent)1699 protected View inflate(ViewGroup parent) { 1700 return createUserItem( 1701 parent, R.string.cp_start_settings, R.string.nls_change_type_filter); 1702 } 1703 1704 @Override setUp()1705 protected void setUp() { 1706 // note: it's expected that the '0' type will be ignored since we've specified a 1707 // type in the manifest 1708 ArrayList<String> pkgs = new ArrayList<>(); 1709 pkgs.add("com.android.settings"); 1710 MockListener.getInstance().migrateNotificationFilter(0, pkgs); 1711 status = READY; 1712 } 1713 1714 @Override autoStart()1715 boolean autoStart() { 1716 return true; 1717 } 1718 1719 @Override test()1720 protected void test() { 1721 if (getIntent().resolveActivity(mPackageManager) == null) { 1722 logFail("no settings activity"); 1723 status = FAIL; 1724 } else { 1725 if (buttonPressed) { 1726 status = PASS; 1727 } else { 1728 status = RETEST_AFTER_LONG_DELAY; 1729 } 1730 next(); 1731 } 1732 } 1733 tearDown()1734 protected void tearDown() { 1735 // wait for the service to start 1736 delay(); 1737 } 1738 1739 @Override getIntent()1740 protected Intent getIntent() { 1741 Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS); 1742 intent.putExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, 1743 MockListener.COMPONENT_NAME.flattenToString()); 1744 return intent; 1745 } 1746 } 1747 1748 protected class ResetChangeFilter extends SendUserToChangeFilter { 1749 @Override inflate(ViewGroup parent)1750 protected View inflate(ViewGroup parent) { 1751 return createUserItem( 1752 parent, R.string.cp_start_settings, R.string.nls_reset_type_filter); 1753 } 1754 } 1755 1756 protected class AskIfFilterChanged extends InteractiveTestCase { 1757 @Override inflate(ViewGroup parent)1758 protected View inflate(ViewGroup parent) { 1759 return createPassFailItem(parent, R.string.nls_original_filter_verification); 1760 } 1761 1762 @Override autoStart()1763 boolean autoStart() { 1764 return true; 1765 } 1766 1767 @Override test()1768 protected void test() { 1769 status = WAIT_FOR_USER; 1770 next(); 1771 } 1772 } 1773 } 1774