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