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