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