1 /* 2 * Copyright (C) 2024 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 android.platform.systemui_tapl.controller; 18 19 import static android.app.Flags.FLAG_API_RICH_ONGOING; 20 import static android.app.Notification.CATEGORY_SYSTEM; 21 import static android.app.NotificationManager.IMPORTANCE_DEFAULT; 22 import static android.app.NotificationManager.IMPORTANCE_HIGH; 23 import static android.app.NotificationManager.IMPORTANCE_LOW; 24 import static android.app.NotificationManager.IMPORTANCE_MIN; 25 import static android.app.PendingIntent.FLAG_IMMUTABLE; 26 import static android.platform.systemui_tapl.ui.Notification.NOTIFICATION_BIG_TEXT; 27 import static android.platform.test.util.HealthTestingUtils.waitForCondition; 28 import static android.platform.uiautomatorhelpers.DeviceHelpers.getContext; 29 import static android.platform.uiautomatorhelpers.DeviceHelpers.getUiDevice; 30 31 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 32 33 import android.R; 34 import android.annotation.FlaggedApi; 35 import android.app.Notification; 36 import android.app.Notification.Builder; 37 import android.app.Notification.MessagingStyle; 38 import android.app.NotificationChannel; 39 import android.app.NotificationManager; 40 import android.app.PendingIntent; 41 import android.app.Person; 42 import android.content.Context; 43 import android.content.Intent; 44 import android.content.pm.ShortcutInfo; 45 import android.content.pm.ShortcutManager; 46 import android.graphics.Bitmap; 47 import android.graphics.Canvas; 48 import android.graphics.Color; 49 import android.graphics.drawable.Icon; 50 import android.os.SystemClock; 51 import android.service.notification.StatusBarNotification; 52 import android.util.Log; 53 import android.widget.RemoteViews; 54 55 import androidx.annotation.NonNull; 56 import androidx.annotation.Nullable; 57 58 import java.io.IOException; 59 import java.util.ArrayList; 60 import java.util.List; 61 import java.util.Locale; 62 63 /** Controller for manipulating notifications. */ 64 public class NotificationController { 65 private static final String LOG_TAG = "NotificationController"; 66 67 private static final String NOTIFICATION_TITLE_TEXT = "TEST NOTIFICATION"; 68 69 private static final String INCOMING_CALL_TEXT = "Incoming call"; 70 71 private static final String NOTIFICATION_GROUP = "Test group"; 72 private static final String CUSTOM_TEXT = "Example text"; 73 private static final String NOTIFICATION_CONTENT_TEXT_FORMAT = "Test notification %d"; 74 75 /** Id of the high importance channel created by the controller. */ 76 public static final String NOTIFICATION_CHANNEL_HIGH_IMPORTANCE_ID = 77 "test_channel_id_high_importance"; 78 79 public static final String NOTIFICATION_CHANNEL_DEFAULT_IMPORTANCE_ID = 80 "test_channel_id_default_importance"; 81 82 public static final String NOTIFICATION_CHANNEL_LOW_IMPORTANCE_ID = 83 "test_channel_id_low_importance"; 84 85 public static final String NOTIFICATION_CHANNEL_MIN_IMPORTANCE_ID = 86 "test_channel_id_min_importance"; 87 88 private static final String NOTIFICATION_CONTENT_TEXT = "Test notification content"; 89 private static final String NOTIFICATION_CHANNEL_HIGH_IMPORTANCE_NAME = 90 "Test Channel HIGH_IMPORTANCE"; 91 private static final String NOTIFICATION_CHANNEL_DEFAULT_IMPORTANCE_NAME = 92 "Test Channel DEFAULT_IMPORTANCE"; 93 94 private static final String NOTIFICATION_CHANNEL_LOW_IMPORTANCE_NAME = 95 "Test Channel LOW_IMPORTANCE"; 96 private static final String NOTIFICATION_CHANNEL_MIN_IMPORTANCE_NAME = 97 "Test Channel MIN_IMPORTANCE"; 98 private static final String NOTIFICATION_GROUP_KEY_FORMAT = "Test group %d"; 99 private static final String PACKAGE_NAME = 100 getInstrumentation().getTargetContext().getPackageName(); 101 private static final String EXTRA_NAME_MESSAGE = "message"; 102 private static final String DEFAULT_TEST_SHORTCUT_ID = "test_shortcut_id"; 103 104 private static final android.app.NotificationManager NOTIFICATION_MANAGER = 105 getInstrumentation().getTargetContext().getSystemService(NotificationManager.class); 106 107 private static int nextNotificationId = 0; 108 109 private static final String DEFAULT_ACTION_TEXT = "action"; 110 111 static { NOTIFICATION_MANAGER.createNotificationChannel( new NotificationChannel( NOTIFICATION_CHANNEL_HIGH_IMPORTANCE_ID, NOTIFICATION_CHANNEL_HIGH_IMPORTANCE_NAME, IMPORTANCE_HIGH))112 NOTIFICATION_MANAGER.createNotificationChannel( 113 new NotificationChannel( 114 NOTIFICATION_CHANNEL_HIGH_IMPORTANCE_ID, 115 NOTIFICATION_CHANNEL_HIGH_IMPORTANCE_NAME, 116 IMPORTANCE_HIGH)); 117 NOTIFICATION_MANAGER.createNotificationChannel( new NotificationChannel( NOTIFICATION_CHANNEL_DEFAULT_IMPORTANCE_ID, NOTIFICATION_CHANNEL_DEFAULT_IMPORTANCE_NAME, IMPORTANCE_DEFAULT))118 NOTIFICATION_MANAGER.createNotificationChannel( 119 new NotificationChannel( 120 NOTIFICATION_CHANNEL_DEFAULT_IMPORTANCE_ID, 121 NOTIFICATION_CHANNEL_DEFAULT_IMPORTANCE_NAME, 122 IMPORTANCE_DEFAULT)); 123 NOTIFICATION_MANAGER.createNotificationChannel( new NotificationChannel( NOTIFICATION_CHANNEL_LOW_IMPORTANCE_ID, NOTIFICATION_CHANNEL_LOW_IMPORTANCE_NAME, IMPORTANCE_LOW))124 NOTIFICATION_MANAGER.createNotificationChannel( 125 new NotificationChannel( 126 NOTIFICATION_CHANNEL_LOW_IMPORTANCE_ID, 127 NOTIFICATION_CHANNEL_LOW_IMPORTANCE_NAME, 128 IMPORTANCE_LOW)); 129 NOTIFICATION_MANAGER.createNotificationChannel( new NotificationChannel( NOTIFICATION_CHANNEL_MIN_IMPORTANCE_ID, NOTIFICATION_CHANNEL_MIN_IMPORTANCE_NAME, IMPORTANCE_MIN))130 NOTIFICATION_MANAGER.createNotificationChannel( 131 new NotificationChannel( 132 NOTIFICATION_CHANNEL_MIN_IMPORTANCE_ID, 133 NOTIFICATION_CHANNEL_MIN_IMPORTANCE_NAME, 134 IMPORTANCE_MIN)); 135 } 136 137 /** Returns an instance of NotificationController. */ get()138 public static NotificationController get() { 139 return new NotificationController(); 140 } 141 NotificationController()142 private NotificationController() {} 143 getNextNotificationId()144 private static int getNextNotificationId() { 145 return nextNotificationId++; 146 } 147 148 /** 149 * Posts notification. 150 * 151 * @param builder Builder for notification to post. 152 */ postNotification(Builder builder)153 public void postNotification(Builder builder) { 154 postNotificationSync(getNextNotificationId(), builder); 155 } 156 157 /** 158 * Posts notification without setting group ID. 159 * 160 * @param builder Builder for notification to post. 161 */ postNotificationNoGroup(Builder builder)162 public void postNotificationNoGroup(Builder builder) { 163 postNotificationSync(/* id= */ getNextNotificationId(), builder, /* groupKey= */ null); 164 } 165 166 /** 167 * Posts notification. 168 * 169 * @param id notification id. 170 * @param builder Builder for notification to post. 171 */ postNotification(int id, Builder builder)172 public void postNotification(int id, Builder builder) { 173 Notification notification = builder.setGroup(getGroupKey(id)).build(); 174 NOTIFICATION_MANAGER.notify(id, notification); 175 waitUntilNotificationPosted(id); 176 } 177 178 /** 179 * Cancels a notification. 180 * 181 * @param id notification id. 182 */ cancelNotification(int id)183 public void cancelNotification(int id) { 184 NOTIFICATION_MANAGER.cancel(id); 185 waitUntilNotificationCancelled(id); 186 } 187 188 /** 189 * Checks the notification has the LIFETIME_EXTENDED_BY_DIRECT_REPLY flag, then sends a 190 * cancellation for given notification, and checks that the cancellation is refused because of 191 * the flag, which prevents non-user originated cancellations from occurring. 192 * 193 * @param id notification id. 194 */ cancelNotificationLifetimeExtended(int id)195 public void cancelNotificationLifetimeExtended(int id) { 196 // Checks the notification has the lifetime extended flag. 197 waitUntilNotificationUpdatedWithFlag( 198 id, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY); 199 // Sends the cancelation signal. 200 NOTIFICATION_MANAGER.cancel(id); 201 // The cancelation should be refused. 202 waitForCondition( 203 () -> "Notification is gone when cancelation should have been prevented", 204 () -> hasNotification(id)); 205 } 206 207 /** 208 * Checks that a notification has been cancelled. 209 * 210 * @param id notification id. 211 */ notificationCancelled(int id)212 public void notificationCancelled(int id) { 213 waitUntilNotificationCancelled(id); 214 } 215 216 /** 217 * Sends a cancellation signal; does not confirm the notification is canceled. 218 * 219 * @param id notification id 220 */ sendCancellation(int id)221 public void sendCancellation(int id) { 222 NOTIFICATION_MANAGER.cancel(id); 223 } 224 225 /** 226 * Posts a number of notifications to the device with a package to launch. Successive calls to 227 * this should post new notifications in addition to those previously posted. Note that this may 228 * fail if the helper has surpassed the system-defined limit for per-package notifications. 229 * 230 * @param count The number of notifications to post. 231 * @param isMessaging If notification should be a messagingstyle notification 232 */ postNotifications(int count, boolean isMessaging)233 public void postNotifications(int count, boolean isMessaging) { 234 postNotifications(count, null, isMessaging); 235 } 236 237 /** 238 * Posts a number of notifications to the device. Successive calls to this should post new 239 * notifications to those previously posted. Note that this may fail if the helper has surpassed 240 * the system-defined limit for per-package notifications. 241 * 242 * @param count The number of notifications to post. 243 */ postNotifications(int count)244 public NotificationIdentity postNotifications(int count) { 245 return postNotifications(count, /* pkg */ null); 246 } 247 248 /** 249 * Setup Expectations: None 250 * 251 * <p>Posts a number of notifications to the device with a package to launch. Successive calls 252 * to this should post new notifications in addition to those previously posted. Note that this 253 * may fail if the helper has surpassed the system-defined limit for per-package notifications. 254 * 255 * @param count The number of notifications to post. 256 * @param pkg The application that will be launched by notifications. 257 */ postNotifications(int count, String pkg)258 public NotificationIdentity postNotifications(int count, String pkg) { 259 postNotifications(count, pkg, /* isMessaging= */ false); 260 return new NotificationIdentity( 261 NotificationIdentity.Type.BY_TITLE, 262 NOTIFICATION_TITLE_TEXT, 263 null, 264 null, 265 null, 266 false, 267 pkg); 268 } 269 270 /** 271 * Posts a notification using {@link android.app.Notification.CallStyle}. 272 * 273 * @param pkg App to launch, when clicking on notification. 274 */ 275 @NonNull postCallStyleNotification(@ullable String pkg)276 public NotificationIdentity postCallStyleNotification(@Nullable String pkg) { 277 Person namedPerson = new Person.Builder().setName("Named Person").build(); 278 postNotificationSync( 279 getNextNotificationId(), 280 getBuilder(pkg) 281 .setStyle( 282 Notification.CallStyle.forOngoingCall( 283 namedPerson, getLaunchIntent(pkg))) 284 .setFullScreenIntent(getLaunchIntent(pkg), true) 285 .setContentText(INCOMING_CALL_TEXT)); 286 return new NotificationIdentity( 287 NotificationIdentity.Type.CALL, null, INCOMING_CALL_TEXT, null, null, true, null); 288 } 289 290 /** 291 * Posts a notification using {@link android.app.Notification.InboxStyle}. 292 * 293 * @param pkg App to launch, when clicking on notification. 294 */ 295 @NonNull postInboxStyleNotification( @ullable String pkg, @Nullable String rowText)296 public NotificationIdentity postInboxStyleNotification( 297 @Nullable String pkg, @Nullable String rowText) { 298 postNotificationSync( 299 getNextNotificationId(), 300 getBuilder(pkg) 301 .setStyle(new Notification.InboxStyle().addLine(rowText)) 302 .setContentText(NOTIFICATION_CONTENT_TEXT)); 303 return new NotificationIdentity( 304 NotificationIdentity.Type.INBOX, 305 null, 306 NOTIFICATION_TITLE_TEXT, 307 null, 308 null, 309 true, 310 null); 311 } 312 313 /** 314 * Posts a notification using {@link android.app.Notification.MediaStyle}. 315 * 316 * @param pkg App to launch, when clicking on notification. 317 */ 318 @NonNull postMediaStyleNotification( @ullable String pkg, boolean decorated)319 public NotificationIdentity postMediaStyleNotification( 320 @Nullable String pkg, boolean decorated) { 321 postNotificationSync( 322 getNextNotificationId(), 323 getBuilder(pkg) 324 .setStyle( 325 decorated 326 ? new Notification.DecoratedMediaCustomViewStyle() 327 : new Notification.MediaStyle()) 328 .setContentText(NOTIFICATION_CONTENT_TEXT)); 329 return new NotificationIdentity( 330 NotificationIdentity.Type.MEDIA, 331 null, 332 NOTIFICATION_CONTENT_TEXT, 333 null, 334 null, 335 true, 336 null); 337 } 338 339 /** 340 * Posts a notification with a custom layout. 341 * 342 * @param pkg App to launch, when clicking on notification. 343 * @param decorated whether the custom notification should have the standard view wrapper 344 */ 345 @NonNull postCustomNotification(@ullable String pkg, boolean decorated)346 public NotificationIdentity postCustomNotification(@Nullable String pkg, boolean decorated) { 347 postNotificationSync( 348 getNextNotificationId(), 349 getBuilder(pkg) 350 .setCustomContentView(makeCustomContent()) 351 .setStyle(decorated ? new Notification.DecoratedCustomViewStyle() : null) 352 .setContentText(CUSTOM_TEXT)); 353 return new NotificationIdentity( 354 NotificationIdentity.Type.CUSTOM, null, CUSTOM_TEXT, null, null, true, null); 355 } 356 makeCustomContent()357 protected RemoteViews makeCustomContent() { 358 RemoteViews customContent = 359 new RemoteViews(PACKAGE_NAME, android.R.layout.simple_list_item_1); 360 int textId = android.R.id.text1; 361 customContent.setTextViewText(textId, "Example Text"); 362 return customContent; 363 } 364 365 /** 366 * Posts a notification using {@link android.app.Notification.BigPictureStyle}. 367 * 368 * @param pkg App to launch, when clicking on notification. 369 */ 370 @NonNull postBigPictureNotification(@ullable String pkg)371 public NotificationIdentity postBigPictureNotification(@Nullable String pkg) { 372 Bitmap bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888); 373 new Canvas(bitmap).drawColor(Color.BLUE); 374 postNotificationSync( 375 getNextNotificationId(), 376 getBuilder(pkg) 377 .setStyle(new android.app.Notification.BigPictureStyle().bigPicture(bitmap)) 378 .setContentText(NOTIFICATION_CONTENT_TEXT)); 379 return new NotificationIdentity( 380 /* type= */ NotificationIdentity.Type.BIG_PICTURE, 381 /* title= */ null, 382 /* text= */ NOTIFICATION_TITLE_TEXT, 383 /* summary= */ null, 384 /* textWhenExpanded= */ null, 385 /* contentIsVisibleInCollapsedState= */ true, 386 /* pkg= */ null); 387 } 388 389 /** 390 * Posts a notification using {@link android.app.Notification.ProgressStyle}. 391 * 392 * @param pkg App to launch, when clicking on notification. 393 */ 394 @NonNull 395 @FlaggedApi(FLAG_API_RICH_ONGOING) postProgressStyleNotification(@ullable String pkg)396 public NotificationIdentity postProgressStyleNotification(@Nullable String pkg) { 397 final Bitmap bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888); 398 new Canvas(bitmap).drawColor(Color.BLUE); 399 postNotificationSync( 400 getNextNotificationId(), 401 getBuilder(pkg) 402 .setStyle( 403 new Notification.ProgressStyle() 404 .setProgress(50) 405 .setProgressStartIcon( 406 Icon.createWithResource("", R.drawable.btn_star)) 407 .setProgressEndIcon( 408 Icon.createWithResource("", R.drawable.btn_minus)) 409 .addProgressPoint( 410 new Notification.ProgressStyle.Point(10) 411 .setColor(Color.RED)) 412 .addProgressPoint( 413 new Notification.ProgressStyle.Point(50) 414 .setColor(Color.BLUE)) 415 .addProgressPoint( 416 new Notification.ProgressStyle.Point(90) 417 .setColor(Color.GREEN)) 418 .addProgressSegment( 419 new Notification.ProgressStyle.Segment(20) 420 .setColor(Color.RED)) 421 .addProgressSegment( 422 new Notification.ProgressStyle.Segment(30) 423 .setColor(Color.YELLOW)) 424 .addProgressSegment( 425 new Notification.ProgressStyle.Segment(50) 426 .setColor(Color.BLUE)) 427 .setProgressTrackerIcon( 428 Icon.createWithResource( 429 "", R.drawable.ic_menu_send))) 430 .setContentText(NOTIFICATION_CONTENT_TEXT) 431 .setLargeIcon(bitmap)); 432 433 return new NotificationIdentity( 434 /* type= */ NotificationIdentity.Type.BIG_PICTURE, 435 /* title= */ null, 436 /* text= */ NOTIFICATION_TITLE_TEXT, 437 /* summary= */ null, 438 /* textWhenExpanded= */ null, 439 /* contentIsVisibleInCollapsedState= */ true, 440 /* pkg= */ null); 441 } 442 443 /** 444 * Posts a notification using {@link android.app.Notification.BigPictureStyle}. 445 * 446 * @param pkg App to launch, when clicking on notification. 447 * @param picture The picture to include as the content of the BigPicture Notification. 448 */ 449 @NonNull postBigPictureNotification( @ullable String pkg, String title, @NonNull Icon picture, boolean lowImportance)450 public NotificationIdentity postBigPictureNotification( 451 @Nullable String pkg, String title, @NonNull Icon picture, boolean lowImportance) { 452 postNotificationSync( 453 getNextNotificationId(), 454 getBuilder(pkg, lowImportance ? Importance.LOW : Importance.DEFAULT) 455 .setContentTitle(title) 456 .setStyle( 457 new android.app.Notification.BigPictureStyle().bigPicture(picture)) 458 .setContentText(NOTIFICATION_CONTENT_TEXT)); 459 return new NotificationIdentity( 460 /* type= */ NotificationIdentity.Type.BIG_PICTURE, 461 /* title= */ null, 462 /* text= */ title, 463 /* summary= */ null, 464 /* textWhenExpanded= */ null, 465 /* contentIsVisibleInCollapsedState= */ true, 466 "Scenario"); 467 } 468 469 /** 470 * Posts a number of notifications while the shade is closed. Successive calls to this should 471 * post new notifications in addition to those previously posted. Note that this may fail if the 472 * helper has surpassed the system-defined limit for per-package notifications. 473 * 474 * @param count The number of notifications to post. 475 * @param pkg The application that will be launched by notifications. 476 * @param summary Summary text for this group notification 477 */ 478 @NonNull postGroupNotifications( int count, @Nullable String pkg, @NonNull String summary)479 public GroupNotificationIdentities postGroupNotifications( 480 int count, @Nullable String pkg, @NonNull String summary) { 481 return postGroupNotifications(count, pkg, summary, /* highImportance= */ false); 482 } 483 484 /** 485 * Posts a number of notifications while the shade is closed. Successive calls to this should 486 * post new notifications in addition to those previously posted. Note that this may fail if the 487 * helper has surpassed the system-defined limit for per-package notifications. 488 * 489 * @param count The number of notifications to post. 490 * @param pkg The application that will be launched by notifications. 491 * @param summary Summary text for this group notification 492 * @param highImportance Whether to post the notification with high importance 493 */ 494 @NonNull postGroupNotifications( int count, @Nullable String pkg, @NonNull String summary, boolean highImportance)495 public GroupNotificationIdentities postGroupNotifications( 496 int count, @Nullable String pkg, @NonNull String summary, boolean highImportance) { 497 return postGroupNotificationsImpl(count, pkg, summary, highImportance); 498 } 499 500 /** 501 * Posts a number of notifications while the shade is closed with custom prioruty. Successive 502 * calls to this should post new notifications in addition to those previously posted. Note that 503 * this may fail if the helper has surpassed the system-defined limit for per-package 504 * notifications. 505 * 506 * @param count The number of notifications to post. 507 * @param pkg The application that will be launched by notifications. 508 * @param summary Summary text for this group notification 509 * @param priority The priority of the group notification 510 */ 511 @NonNull postGroupNotifications( int count, @Nullable String pkg, @NonNull String summary, Importance priority)512 public GroupNotificationIdentities postGroupNotifications( 513 int count, @Nullable String pkg, @NonNull String summary, Importance priority) { 514 return postGroupNotificationsImpl(count, pkg, summary, priority); 515 } 516 517 /*** 518 * Posts a number of MessagingStyle Notifications and group them. Note that this may fail if the 519 * helper has surpassed the system-defined limit for per-package notifications. 520 * @param pkg The application that will be launched by notifications. 521 * @param count The number of notifications to post. 522 * @param summary Summary text for this group notification 523 * @param personName Name of the person 524 * @param messages Message Content to be posted for each MessingStyle Notification 525 */ postGroupNotificationWithMessagingStyle( String pkg, String summary, int count, String groupName, String personName, List<MessagingStyle.Message> messages)526 public NotificationIdentity postGroupNotificationWithMessagingStyle( 527 String pkg, 528 String summary, 529 int count, 530 String groupName, 531 String personName, 532 List<MessagingStyle.Message> messages) { 533 Builder builder = 534 getBuilder(pkg) 535 .setGroupAlertBehavior(android.app.Notification.GROUP_ALERT_SUMMARY) 536 .setGroup(groupName); 537 538 for (int i = 0; i < count; i++) { 539 final Person person = new Person.Builder().setName(personName + "_" + i).build(); 540 final MessagingStyle messagingStyle = 541 new MessagingStyle(person).setConversationTitle(NOTIFICATION_TITLE_TEXT); 542 for (MessagingStyle.Message message : messages) { 543 messagingStyle.addMessage(message); 544 } 545 builder.setStyle(messagingStyle); 546 postNotificationSync(getNextNotificationId(), builder, groupName); 547 } 548 builder.setStyle(new android.app.Notification.InboxStyle().setSummaryText(summary)) 549 .setGroupSummary(true); 550 postNotificationSync(getNextNotificationId(), builder, groupName); 551 return new NotificationIdentity( 552 NotificationIdentity.Type.GROUP, 553 null, 554 NOTIFICATION_TITLE_TEXT, 555 summary, 556 null, 557 true, 558 null); 559 } 560 561 /*** 562 * Posts a number of ConversationStyle Notifications and group them. 563 * Note that this may fail if the helper has surpassed the system-defined limit 564 * for per-package notifications. 565 * 566 * @param pkg The application that will be launched by notifications. 567 * @param count The number of notifications to post. 568 * @param summary Summary text for this group notification 569 * @param personName Name of the person 570 * @param messages Message Content to be posted for each MessingStyle Notification 571 */ postGroupNotificationWithConversationStyle( String pkg, String summary, int count, String groupName, String personName, List<MessagingStyle.Message> messages)572 public NotificationIdentity postGroupNotificationWithConversationStyle( 573 String pkg, 574 String summary, 575 int count, 576 String groupName, 577 String personName, 578 List<MessagingStyle.Message> messages) { 579 Context context = getInstrumentation().getTargetContext(); 580 Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888); 581 new Canvas(bitmap).drawColor(Color.BLUE); 582 Intent intent = new android.content.Intent(Intent.ACTION_VIEW); 583 584 Builder builder = 585 getBuilder(pkg) 586 .setGroupAlertBehavior(android.app.Notification.GROUP_ALERT_SUMMARY) 587 .setGroup(groupName); 588 final ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class); 589 590 for (int i = 0; i < count; i++) { 591 final Person person = new Person.Builder().setName(personName + "_" + i).build(); 592 593 String shortCutId = "short_cut" + i; 594 ShortcutInfo shortcutInfo = 595 new ShortcutInfo.Builder(context, shortCutId) 596 .setShortLabel(personName) 597 .setLongLabel(personName) 598 .setIntent(intent) 599 .setIcon(Icon.createWithAdaptiveBitmap(bitmap)) 600 .setPerson(person) 601 .setLongLived(true) 602 .build(); 603 shortcutManager.pushDynamicShortcut(shortcutInfo); 604 final MessagingStyle messagingStyle = 605 new MessagingStyle(person).setConversationTitle(NOTIFICATION_TITLE_TEXT); 606 for (MessagingStyle.Message message : messages) { 607 messagingStyle.addMessage(message); 608 } 609 builder.setStyle(messagingStyle).setShortcutId(shortCutId); 610 postNotificationSync(getNextNotificationId(), builder, groupName); 611 } 612 builder.setStyle(new android.app.Notification.InboxStyle().setSummaryText(summary)) 613 .setGroupSummary(true); 614 postNotificationSync(getNextNotificationId(), builder, groupName); 615 return new NotificationIdentity( 616 /* type= */ NotificationIdentity.Type.GROUP, 617 /* title= */ null, 618 /* text= */ NOTIFICATION_TITLE_TEXT, 619 /* summary= */ summary, 620 /* textWhenExpanded= */ null, 621 /* contentIsVisibleInCollapsedState= */ true, 622 /* pkg= */ null); 623 } 624 625 /*** 626 * Posts a number of BigTextStyle Notifications and group them. Note that this may fail if the 627 * helper has surpassed the system-defined limit for per-package notifications. 628 * @param pkg The application that will be launched by notifications. 629 * @param summary Summary text for this group notification 630 * @param groupName Name of the group 631 * @param bigTextContents List of Text to be mapped BigTextStyle Notifications 632 */ postGroupNotificationWithBigTextStyle( String pkg, String summary, String groupName, List<String> bigTextContents)633 public NotificationIdentity postGroupNotificationWithBigTextStyle( 634 String pkg, String summary, String groupName, List<String> bigTextContents) { 635 Builder builder = 636 getBuilder(pkg).setGroupAlertBehavior(android.app.Notification.GROUP_ALERT_SUMMARY); 637 638 final Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle(); 639 for (String bigText : bigTextContents) { 640 bigTextStyle.setBigContentTitle(bigText); 641 builder.setStyle(bigTextStyle).setGroup(groupName); 642 postNotificationSync(getNextNotificationId(), builder, groupName); 643 } 644 builder.setStyle(new android.app.Notification.InboxStyle().setSummaryText(summary)) 645 .setGroupSummary(true); 646 postNotificationSync(getNextNotificationId(), builder, groupName); 647 return new NotificationIdentity( 648 NotificationIdentity.Type.GROUP, 649 null, 650 NOTIFICATION_TITLE_TEXT, 651 summary, 652 null, 653 true, 654 null); 655 } 656 657 /** 658 * Posts a notification using {@link android.app.Notification.BigTextStyle}. 659 * 660 * @param pkg App to launch, when clicking on notification. 661 */ 662 @NonNull postBigTextNotification(@ullable String pkg)663 public NotificationIdentity postBigTextNotification(@Nullable String pkg) { 664 return postBigTextNotification(pkg, false); 665 } 666 667 /** 668 * Posts a notification using {@link android.app.Notification.BigTextStyle}. 669 * 670 * @param pkg App to launch, when clicking on notification. 671 * @param highImportance Whether to post the notification with high importance. 672 */ 673 @NonNull postBigTextNotification( @ullable String pkg, boolean highImportance)674 public NotificationIdentity postBigTextNotification( 675 @Nullable String pkg, boolean highImportance) { 676 return BigTextNotificationController.postBigTextNotification( 677 /* pkg= */ pkg, /* highImportance= */ highImportance); 678 } 679 680 /** 681 * Posts a notification using {@link android.app.Notification.BigTextStyle}. 682 * 683 * @param pkg App to launch, when clicking on notification. 684 * @param title title of a notification 685 * @param collapsedText collapsed text of a notification 686 * @param expandedText expanded text of a notification 687 */ 688 @NonNull postBigTextNotification( @ullable String pkg, boolean highImportance, String title, String collapsedText, String expandedText)689 public NotificationIdentity postBigTextNotification( 690 @Nullable String pkg, 691 boolean highImportance, 692 String title, 693 String collapsedText, 694 String expandedText) { 695 return BigTextNotificationController.postBigTextNotification( 696 /* pkg= */ pkg, 697 /* highImportance= */ highImportance, 698 /* collapsedText= */ collapsedText, 699 /* expandedText= */ expandedText, 700 /* title= */ title); 701 } 702 703 /** 704 * Posts a heads-up notification using {@link android.app.Notification.BigTextStyle} with a 705 * default action button. The action button is useful to distinguish if the notification is in 706 * the HUN form (We can tell a notification is in the HUN form if its expand button is at the 707 * "expand" state, and an action button is showing). 708 * 709 * @param pkg App to launch, when clicking on notification. 710 */ postBigTextHeadsUpNotification(@ullable String pkg)711 public NotificationIdentity postBigTextHeadsUpNotification(@Nullable String pkg) { 712 return BigTextNotificationController.postBigTextNotification( 713 /* pkg= */ pkg, 714 /* highImportance= */ true, 715 /* actions= */ getDefaultActionBuilder().build()); 716 } 717 getDefaultActionBuilder()718 public Notification.Action.Builder getDefaultActionBuilder() { 719 return new Notification.Action.Builder( 720 Icon.createWithResource("", R.drawable.btn_star), 721 DEFAULT_ACTION_TEXT, 722 PendingIntent.getActivity( 723 getContext(), 724 0, 725 new android.content.Intent(Intent.ACTION_VIEW), 726 PendingIntent.FLAG_IMMUTABLE)); 727 } 728 729 /** 730 * Posts a Full Screen Intent Notification. 731 * 732 * @param pkg App to launch, when clicking on notification. 733 * @param fsiPendingIntent Full Screen Intent 734 * @param actions actions Action to be shown in the Notification 735 */ postFullScreenIntentNotification( @ullable final String pkg, final PendingIntent fsiPendingIntent, final Notification.Action... actions)736 public NotificationIdentity postFullScreenIntentNotification( 737 @Nullable final String pkg, 738 final PendingIntent fsiPendingIntent, 739 final Notification.Action... actions) { 740 postNotificationSync( 741 getNextNotificationId(), 742 getBuilder(pkg, Importance.HIGH) 743 .setSmallIcon(android.R.drawable.stat_notify_chat) 744 .setContentText(NOTIFICATION_CONTENT_TEXT) 745 .setFullScreenIntent(fsiPendingIntent, true) 746 .setActions(actions)); 747 return new NotificationIdentity( 748 /* title= */ NotificationIdentity.Type.BY_TITLE, 749 /* type= */ NOTIFICATION_TITLE_TEXT, 750 /* text= */ null, 751 /* summary= */ null, 752 /* textWhenExpanded= */ null, 753 /* contentIsVisibleInCollapsedState= */ true, 754 /* pkg= */ pkg); 755 } 756 757 /** 758 * Posts an ongoing Notification. 759 * 760 * @param pkg App to launch, when clicking on notification. 761 */ postOngoingNotification(@ullable final String pkg)762 public NotificationIdentity postOngoingNotification(@Nullable final String pkg) { 763 postNotificationSync( 764 getNextNotificationId(), 765 getBuilder(pkg, Importance.HIGH) 766 .setContentText(NOTIFICATION_CONTENT_TEXT) 767 .setOngoing(true)); 768 return new NotificationIdentity( 769 /* title= */ NotificationIdentity.Type.BY_TITLE, 770 /* type= */ NOTIFICATION_TITLE_TEXT, 771 /* text= */ null, 772 /* summary= */ null, 773 /* textWhenExpanded= */ null, 774 /* contentIsVisibleInCollapsedState= */ true, 775 /* pkg= */ pkg); 776 } 777 postGroupNotificationsImpl( int count, @Nullable String pkg, @NonNull String summary, boolean highImportance)778 private static GroupNotificationIdentities postGroupNotificationsImpl( 779 int count, @Nullable String pkg, @NonNull String summary, boolean highImportance) { 780 return postGroupNotificationsImpl( 781 count, pkg, summary, highImportance ? Importance.HIGH : Importance.DEFAULT); 782 } 783 postGroupNotificationsImpl( int count, @Nullable String pkg, @NonNull String summary, Importance priority)784 private static GroupNotificationIdentities postGroupNotificationsImpl( 785 int count, @Nullable String pkg, @NonNull String summary, Importance priority) { 786 GroupNotificationIdentities identities = new GroupNotificationIdentities(); 787 Builder builder = 788 getBuilder(pkg, priority) 789 .setGroupAlertBehavior(android.app.Notification.GROUP_ALERT_SUMMARY); 790 791 for (int i = 0; i < count; i++) { 792 final String childText = String.format(Locale.US, NOTIFICATION_CONTENT_TEXT_FORMAT, i); 793 builder.setContentText(childText); 794 postNotificationSync(getNextNotificationId(), builder, NOTIFICATION_GROUP); 795 identities.children.add( 796 new NotificationIdentity( 797 /* type= */ NotificationIdentity.Type.BY_TEXT, 798 /* title= */ null, 799 /* text= */ childText, 800 /* summary= */ null, 801 /* textWhenExpanded= */ null, 802 /* contentIsVisibleInCollapsedState= */ true, 803 /* pkg= */ pkg)); 804 } 805 806 builder.setStyle(new android.app.Notification.InboxStyle().setSummaryText(summary)) 807 .setGroupSummary(true); 808 postNotificationSync(getNextNotificationId(), builder, NOTIFICATION_GROUP); 809 identities.summary = 810 new NotificationIdentity( 811 /* type= */ priority == Importance.MIN 812 ? NotificationIdentity.Type.GROUP_MINIMIZED 813 : NotificationIdentity.Type.GROUP, 814 /* title= */ NOTIFICATION_TITLE_TEXT, 815 /* text= */ NOTIFICATION_TITLE_TEXT, 816 /* summary= */ summary, 817 /* textWhenExpanded= */ null, 818 /* contentIsVisibleInCollapsedState= */ true, 819 /* pkg= */ pkg); 820 821 return identities; 822 } 823 824 /** 825 * Posts Standard Silent Notification 826 * 827 * @param pkg 828 */ postStandardSilentNotification(String pkg)829 public NotificationIdentity postStandardSilentNotification(String pkg) { 830 postNotificationSync( 831 getNextNotificationId(), 832 getBuilder(pkg, Importance.LOW).setContentText(NOTIFICATION_CONTENT_TEXT)); 833 return new NotificationIdentity( 834 /* title= */ NotificationIdentity.Type.BY_TITLE, 835 /* type= */ NOTIFICATION_TITLE_TEXT, 836 /* text= */ null, 837 /* summary= */ null, 838 /* textWhenExpanded= */ null, 839 /* contentIsVisibleInCollapsedState= */ true, 840 /* pkg= */ pkg); 841 } 842 843 /** 844 * Posts a Standard Notification. 845 * 846 * @param pkg App to launch, when clicking on notification. 847 */ postStandardStyleNotification(String pkg)848 public NotificationIdentity postStandardStyleNotification(String pkg) { 849 postNotificationSync(getNextNotificationId(), getBuilder(pkg)); 850 851 return new NotificationIdentity( 852 /* type= */ NotificationIdentity.Type.BY_TITLE, 853 /* title= */ NOTIFICATION_TITLE_TEXT, 854 /* text= */ null, 855 /* summary= */ null, 856 /* textWhenExpanded= */ null, 857 /* contentIsVisibleInCollapsedState= */ false, 858 /* pkg= */ pkg); 859 } 860 861 /** 862 * Posts a Standard Notification. 863 * 864 * @param pkg App to launch, when clicking on notification. 865 * @param title title of the notification 866 * @param content content of the notification 867 */ postStandardStyleNotification( String pkg, String title, String content)868 public NotificationIdentity postStandardStyleNotification( 869 String pkg, String title, String content) { 870 postNotificationSync( 871 getNextNotificationId(), 872 getBuilder(pkg).setContentTitle(title).setContentText(content)); 873 874 return new NotificationIdentity( 875 /* type= */ NotificationIdentity.Type.BY_TITLE, 876 /* title= */ title, 877 /* text= */ null, 878 /* summary= */ null, 879 /* textWhenExpanded= */ null, 880 /* contentIsVisibleInCollapsedState= */ false, 881 /* pkg= */ pkg); 882 } 883 884 /** 885 * Posts a notification using {@link android.app.Notification.MessagingStyle}. 886 * 887 * @param pkg App to launch, when clicking on notification. 888 */ postMessagingStyleNotification(String pkg)889 public NotificationIdentity postMessagingStyleNotification(String pkg) { 890 String personName = "Person Name"; 891 android.app.Person person = new android.app.Person.Builder().setName(personName).build(); 892 postNotificationSync( 893 getNextNotificationId(), 894 getBuilder(pkg) 895 .setStyle( 896 new android.app.Notification.MessagingStyle(person) 897 .setConversationTitle(NOTIFICATION_TITLE_TEXT) 898 .addMessage( 899 new android.app.Notification.MessagingStyle.Message( 900 "Message 4", 901 SystemClock.currentThreadTimeMillis(), 902 person)) 903 .addMessage( 904 new android.app.Notification.MessagingStyle.Message( 905 "Message 3", 906 SystemClock.currentThreadTimeMillis(), 907 person)) 908 .addMessage( 909 new android.app.Notification.MessagingStyle.Message( 910 "Message 2", 911 SystemClock.currentThreadTimeMillis(), 912 person)) 913 .addMessage( 914 new android.app.Notification.MessagingStyle.Message( 915 "Message 1", 916 SystemClock.currentThreadTimeMillis(), 917 person)))); 918 return new NotificationIdentity( 919 NotificationIdentity.Type.MESSAGING_STYLE, 920 null, 921 personName, 922 null, 923 null, 924 false, 925 null); 926 } 927 928 /** 929 * Posts a notification using {@link android.app.Notification.MessagingStyle}. 930 * 931 * @param pkg App to launch, when clicking on notification. 932 * @param personName name of the person who sends message 933 * @param messages message list. 934 */ postMessagingStyleNotification( String pkg, String personName, List<MessagingStyle.Message> messages)935 public NotificationIdentity postMessagingStyleNotification( 936 String pkg, String personName, List<MessagingStyle.Message> messages) { 937 final Person person = new Person.Builder().setName(personName).build(); 938 final MessagingStyle messagingStyle = 939 new MessagingStyle(person).setConversationTitle(NOTIFICATION_TITLE_TEXT); 940 for (MessagingStyle.Message message : messages) { 941 messagingStyle.addMessage(message); 942 } 943 postNotificationSync(getNextNotificationId(), getBuilder(pkg).setStyle(messagingStyle)); 944 return new NotificationIdentity( 945 /* type= */ NotificationIdentity.Type.MESSAGING_STYLE, 946 /* title= */ null, 947 /* text= */ personName, 948 /* summary= */ null, 949 /* textWhenExpanded= */ null, 950 /* contentIsVisibleInCollapsedState= */ false, 951 /* pkg= */ null); 952 } 953 954 /** 955 * Posts a conversation notification. This notification is associated with a conversation 956 * shortcut and in {@link android.app.Notification.MessagingStyle}. 957 * 958 * @param pkg App to launch, when clicking on notification. 959 */ postConversationNotification(String pkg)960 public NotificationIdentity postConversationNotification(String pkg) { 961 return postConversationNotification(pkg, "test_shortcut_id", "Person Name"); 962 } 963 964 /** 965 * Posts a sensitive big text style notification. 966 * 967 * @param pkg App to launch, when clicking on notification. 968 */ postBigTextNotificationWithPublicVersion(String pkg)969 public NotificationIdentity postBigTextNotificationWithPublicVersion(String pkg) { 970 return BigTextNotificationController.postBigTextNotification( 971 /* pkg= */ pkg, 972 /* highImportance= */ false, 973 /* collapsedText= */ NOTIFICATION_CONTENT_TEXT, 974 /* expandedText= */ NOTIFICATION_BIG_TEXT, 975 /* title: String = */ NOTIFICATION_TITLE_TEXT, 976 /* contentIntent= */ null, 977 /* publicVersion= */ getBuilder(pkg).build()); 978 } 979 980 /** 981 * Posts a conversation notification. This notification is associated with a conversation 982 * shortcut and in {@link android.app.Notification.MessagingStyle}. 983 * 984 * @param pkg App to launch, when clicking on notification. 985 * @param shortcutId The shortcut ID of the associated conversation. 986 * @param personName The name of the person of the associated conversation. 987 */ postConversationNotification( String pkg, String shortcutId, String personName)988 public NotificationIdentity postConversationNotification( 989 String pkg, String shortcutId, String personName) { 990 Context context = getInstrumentation().getTargetContext(); 991 Person person = new Person.Builder().setName(personName).build(); 992 long currentTimeMillis = SystemClock.currentThreadTimeMillis(); 993 Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888); 994 new Canvas(bitmap).drawColor(Color.BLUE); 995 Intent intent = new android.content.Intent(Intent.ACTION_VIEW); 996 997 ShortcutInfo shortcutInfo = 998 new ShortcutInfo.Builder(context, shortcutId) 999 .setShortLabel(personName) 1000 .setLongLabel(personName) 1001 .setIntent(intent) 1002 .setIcon(Icon.createWithAdaptiveBitmap(bitmap)) 1003 .setPerson(person) 1004 .setLongLived(true) 1005 .build(); 1006 ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class); 1007 shortcutManager.pushDynamicShortcut(shortcutInfo); 1008 1009 Builder builder = 1010 getBuilder(pkg) 1011 .setStyle( 1012 new MessagingStyle(person) 1013 .addMessage( 1014 new MessagingStyle.Message( 1015 "Message " + personName, 1016 currentTimeMillis, 1017 person))) 1018 .setShortcutId(shortcutId); 1019 1020 postNotificationSync(getNextNotificationId(), builder); 1021 1022 return new NotificationIdentity( 1023 NotificationIdentity.Type.CONVERSATION, null, personName, null, null, false, null); 1024 } 1025 1026 /** 1027 * Posts a conversation notification. This notification is associated with a conversation 1028 * shortcut and in {@link android.app.Notification.MessagingStyle}. 1029 * 1030 * @param pkg App to launch, when clicking on notification. 1031 * @param shortcutId The shortcut ID of the associated conversation. 1032 * @param personName The name of the person of the associated conversation. 1033 * @param messages messages of the conversation 1034 */ postConversationNotification( String pkg, String shortcutId, String personName, List<MessagingStyle.Message> messages)1035 public NotificationIdentity postConversationNotification( 1036 String pkg, 1037 String shortcutId, 1038 String personName, 1039 List<MessagingStyle.Message> messages) { 1040 final Context context = getInstrumentation().getTargetContext(); 1041 final Person person = new Person.Builder().setName(personName).build(); 1042 final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888); 1043 new Canvas(bitmap).drawColor(Color.BLUE); 1044 final Intent intent = new android.content.Intent(Intent.ACTION_VIEW); 1045 1046 final ShortcutInfo shortcutInfo = 1047 new ShortcutInfo.Builder(context, shortcutId) 1048 .setShortLabel(personName) 1049 .setLongLabel(personName) 1050 .setIntent(intent) 1051 .setIcon(Icon.createWithAdaptiveBitmap(bitmap)) 1052 .setPerson(person) 1053 .setLongLived(true) 1054 .build(); 1055 final ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class); 1056 shortcutManager.pushDynamicShortcut(shortcutInfo); 1057 1058 final MessagingStyle messagingStyle = new MessagingStyle(person); 1059 for (MessagingStyle.Message message : messages) { 1060 messagingStyle.addMessage(message); 1061 } 1062 1063 final Builder builder = getBuilder(pkg).setStyle(messagingStyle).setShortcutId(shortcutId); 1064 1065 postNotificationSync(getNextNotificationId(), builder); 1066 1067 return new NotificationIdentity( 1068 /* type= */ NotificationIdentity.Type.CONVERSATION, 1069 /* title= */ null, 1070 /* text= */ personName, 1071 /* summary= */ null, 1072 /* textWhenExpanded= */ null, 1073 /* contentIsVisibleInCollapsedState= */ false, 1074 /* pkg= */ null); 1075 } 1076 createBubbleNotificationPostBuilder( String senderName, String text, String shortcutId, String messageToActivity)1077 private Builder createBubbleNotificationPostBuilder( 1078 String senderName, String text, String shortcutId, String messageToActivity) { 1079 final String pkg = getInstrumentation().getTargetContext().getPackageName(); 1080 1081 Context context = getInstrumentation().getTargetContext(); 1082 Person person = new Person.Builder().setName(senderName).build(); 1083 long currentTimeMillis = SystemClock.currentThreadTimeMillis(); 1084 Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888); 1085 new Canvas(bitmap).drawColor(Color.BLUE); 1086 Intent intent = new Intent(Intent.ACTION_SENDTO); 1087 if (messageToActivity != null) { 1088 intent.putExtra(EXTRA_NAME_MESSAGE, messageToActivity); 1089 } 1090 1091 ShortcutInfo shortcutInfo = 1092 new ShortcutInfo.Builder(context, shortcutId) 1093 .setShortLabel(senderName) 1094 .setLongLabel(senderName) 1095 .setIntent(intent) 1096 .setIcon(Icon.createWithAdaptiveBitmap(bitmap)) 1097 .setPerson(person) 1098 .setLongLived(true) 1099 .build(); 1100 ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class); 1101 shortcutManager.pushDynamicShortcut(shortcutInfo); 1102 1103 Notification.BubbleMetadata bubbleMetadata = 1104 new Notification.BubbleMetadata.Builder(shortcutInfo.getId()) 1105 .setAutoExpandBubble(false /* autoExpand */) 1106 .setSuppressNotification(false /* suppressNotif */) 1107 .build(); 1108 1109 return getBuilder(pkg) 1110 .setStyle( 1111 new MessagingStyle(person) 1112 .addMessage( 1113 new MessagingStyle.Message( 1114 text, currentTimeMillis, person))) 1115 .setShortcutId(shortcutId) 1116 .setBubbleMetadata(bubbleMetadata); 1117 } 1118 1119 /** 1120 * Posts multiple bubble notifications. 1121 * 1122 * @param senderName Name of notification sender. 1123 * @param count How many bubble notifications to send. 1124 */ 1125 @NonNull postBubbleNotifications(String senderName, int count)1126 public NotificationIdentity postBubbleNotifications(String senderName, int count) { 1127 final Builder builder = 1128 createBubbleNotificationPostBuilder( 1129 senderName, "Bubble message", DEFAULT_TEST_SHORTCUT_ID, null); 1130 1131 for (int i = 0; i < count; i++) { 1132 postNotificationSync(getNextNotificationId(), builder); 1133 } 1134 1135 return new NotificationIdentity( 1136 NotificationIdentity.Type.CONVERSATION, 1137 null, 1138 "Bubble message", 1139 null, 1140 null, 1141 false, 1142 null); 1143 } 1144 1145 /** 1146 * Posts a bubble notification. 1147 * 1148 * @param id An identifier of the notification to be posted. 1149 * @param senderName Name of notification sender. 1150 * @param text Notification message content. 1151 * @param shortcutId id of the shortcut used in the notification. 1152 * @param messageToActivity message to send to bubble test activity. 1153 */ postBubbleNotification( int id, String senderName, String text, String shortcutId, String messageToActivity)1154 public void postBubbleNotification( 1155 int id, String senderName, String text, String shortcutId, String messageToActivity) { 1156 Builder builder = 1157 createBubbleNotificationPostBuilder( 1158 senderName, text, shortcutId, messageToActivity); 1159 1160 postNotificationSync(id, builder); 1161 } 1162 1163 /** 1164 * Posts a bubble notification. 1165 * 1166 * @param id An identifier of the notification to be posted. 1167 * @param senderName Name of notification sender. 1168 * @param text Notification message content. 1169 */ postBubbleNotification(int id, String senderName, String text)1170 public void postBubbleNotification(int id, String senderName, String text) { 1171 postBubbleNotification( 1172 id, senderName, text, DEFAULT_TEST_SHORTCUT_ID, /* messageToActivity= */ null); 1173 } 1174 1175 /** 1176 * Updates an existing bubble notification. 1177 * 1178 * @param id An identifier of the notification to be updated. 1179 * @param senderName Name of notification sender. 1180 * @param text Update message content. 1181 */ updateBubbleNotification(int id, String senderName, String text)1182 public void updateBubbleNotification(int id, String senderName, String text) { 1183 Person person = new Person.Builder().setName(senderName).build(); 1184 long currentTimeMillis = SystemClock.currentThreadTimeMillis(); 1185 1186 Notification.BubbleMetadata bubbleMetadata = 1187 new Notification.BubbleMetadata.Builder(DEFAULT_TEST_SHORTCUT_ID) 1188 .setAutoExpandBubble(false /* autoExpand */) 1189 .setSuppressNotification(false /* suppressNotif */) 1190 .build(); 1191 1192 Builder builder = 1193 getBuilder(PACKAGE_NAME) 1194 .setStyle( 1195 new Notification.MessagingStyle(person) 1196 .addMessage( 1197 new MessagingStyle.Message( 1198 text, currentTimeMillis, person))) 1199 .setShortcutId(DEFAULT_TEST_SHORTCUT_ID) 1200 .setBubbleMetadata(bubbleMetadata); 1201 1202 NOTIFICATION_MANAGER.notify(id, builder.build()); 1203 } 1204 postNotificationSync(int id, Builder builder)1205 private static void postNotificationSync(int id, Builder builder) { 1206 // By default, we add a group key with the same id as the notification so that it is not 1207 // grouped with other notifications, making sure that the notification count is incremented 1208 // only by 1 when we already posted another notifications, and not by 2 which will happen 1209 // if a new group is formed (as a group also counts as 1 notification). This avoids race 1210 // conditions when adding a lot of consecutive notifications. 1211 postNotificationSync(id, builder, getGroupKey(id)); 1212 } 1213 getGroupKey(int id)1214 private static String getGroupKey(int id) { 1215 return String.format(NOTIFICATION_GROUP_KEY_FORMAT, id); 1216 } 1217 postNotificationSync(int id, Builder builder, String groupKey)1218 private static void postNotificationSync(int id, Builder builder, String groupKey) { 1219 final int initialCount = getNotificationCount(); 1220 final Notification notification = builder.setGroup(groupKey).build(); 1221 NOTIFICATION_MANAGER.notify(id, notification); 1222 waitUntilPostedNotificationsCountMatches(initialCount + 1); 1223 } 1224 1225 /** 1226 * Returns the current total number of posted notifications. If you've just posted a 1227 * notification via NOTIFICATION_MANAGER.notify, this count isn't guaranteed to be correct 1228 * unless you've waited for it to arrive. If the notification is posted by postNotificationSync, 1229 * the count will be correct after posting. Use only postNotificationSync to post notifications. 1230 * Filter out autogroup notifications created by the NotificationManager. 1231 */ getNotificationCount()1232 private static int getNotificationCount() { 1233 StatusBarNotification[] notifications = NOTIFICATION_MANAGER.getActiveNotifications(); 1234 int filteredNotificationCount = 0; 1235 for (StatusBarNotification notification : notifications) { 1236 1237 // ignore the auto-group notifications created by the NotificationManager. 1238 if ((notification.getNotification().flags & Notification.FLAG_AUTOGROUP_SUMMARY) 1239 == 0x0) { 1240 filteredNotificationCount++; 1241 } 1242 } 1243 return filteredNotificationCount; 1244 } 1245 hasNotification(int id)1246 private static boolean hasNotification(int id) { 1247 StatusBarNotification[] activeNotifications = NOTIFICATION_MANAGER.getActiveNotifications(); 1248 for (StatusBarNotification notification : activeNotifications) { 1249 if (notification.getId() == id) { 1250 return true; 1251 } 1252 } 1253 return false; 1254 } 1255 hasNotificationWithFlag(int id, int flag)1256 private static boolean hasNotificationWithFlag(int id, int flag) { 1257 StatusBarNotification[] activeNotifications = NOTIFICATION_MANAGER.getActiveNotifications(); 1258 for (StatusBarNotification notification : activeNotifications) { 1259 if (notification.getId() == id && (notification.getNotification().flags & flag) != 0) { 1260 return true; 1261 } 1262 } 1263 return false; 1264 } 1265 waitUntilNotificationPosted(int id)1266 private static void waitUntilNotificationPosted(int id) { 1267 waitForCondition(() -> "Notification was not posted.", () -> hasNotification(id)); 1268 } 1269 waitUntilNotificationCancelled(int id)1270 private static void waitUntilNotificationCancelled(int id) { 1271 waitForCondition(() -> "Notification is still present.", () -> !hasNotification(id)); 1272 } 1273 waitUntilNotificationUpdatedWithFlag(int id, int flag)1274 private static void waitUntilNotificationUpdatedWithFlag(int id, int flag) { 1275 waitForCondition( 1276 () -> "Notification was not posted with flag.", 1277 () -> (hasNotificationWithFlag(id, flag))); 1278 } 1279 waitUntilPostedNotificationsCountMatches(int count)1280 private static void waitUntilPostedNotificationsCountMatches(int count) { 1281 waitForCondition( 1282 () -> 1283 "Notification count didn't become " 1284 + count 1285 + ". It is currently equal to " 1286 + getNotificationCount(), 1287 () -> getNotificationCount() == count); 1288 } 1289 getBuilder(String pkg)1290 private static Builder getBuilder(String pkg) { 1291 return getBuilder(pkg, Importance.DEFAULT); 1292 } 1293 getBuilder(String pkg, Importance importance)1294 private static Builder getBuilder(String pkg, Importance importance) { 1295 Context context = getInstrumentation().getTargetContext(); 1296 1297 final String channelId = 1298 switch (importance) { 1299 case HIGH -> NOTIFICATION_CHANNEL_HIGH_IMPORTANCE_ID; 1300 case DEFAULT -> NOTIFICATION_CHANNEL_DEFAULT_IMPORTANCE_ID; 1301 case LOW -> NOTIFICATION_CHANNEL_LOW_IMPORTANCE_ID; 1302 case MIN -> NOTIFICATION_CHANNEL_MIN_IMPORTANCE_ID; 1303 }; 1304 Builder builder = 1305 new Builder(context, channelId) 1306 .setContentTitle(NOTIFICATION_TITLE_TEXT) 1307 .setCategory(CATEGORY_SYSTEM) 1308 .setGroupSummary(false) 1309 .setContentText(NOTIFICATION_CONTENT_TEXT) 1310 .setShowWhen(false) 1311 .setSmallIcon(android.R.drawable.stat_notify_chat); 1312 if (pkg != null) { 1313 builder.setContentIntent(getLaunchIntent(pkg)); 1314 } 1315 return builder; 1316 } 1317 getLaunchIntent(String pkg)1318 private static PendingIntent getLaunchIntent(String pkg) { 1319 Context context = getInstrumentation().getTargetContext(); 1320 return PendingIntent.getActivity( 1321 context, 1322 0, 1323 context.getPackageManager().getLaunchIntentForPackage(pkg), 1324 Intent.FLAG_ACTIVITY_NEW_TASK | FLAG_IMMUTABLE); 1325 } 1326 postNotifications(int count, String pkg, boolean isMessaging)1327 private static void postNotifications(int count, String pkg, boolean isMessaging) { 1328 Builder builder = getBuilder(pkg); 1329 if (isMessaging) { 1330 Person person = new Person.Builder().setName("Marvelous user").build(); 1331 builder.setStyle( 1332 new MessagingStyle(person) 1333 .addMessage( 1334 new MessagingStyle.Message( 1335 "Hello", 1336 SystemClock.currentThreadTimeMillis(), 1337 person))); 1338 } 1339 1340 for (int i = (count - 1); i >= 0; i--) { 1341 builder.setContentText(String.format(NOTIFICATION_CONTENT_TEXT_FORMAT, i)); 1342 postNotificationSync(getNextNotificationId(), builder); 1343 } 1344 } 1345 1346 /** Cancels all notifications posted by this object. */ cancelNotifications()1347 public void cancelNotifications() { 1348 NOTIFICATION_MANAGER.cancelAll(); 1349 waitUntilPostedNotificationsCountMatches(0); 1350 } 1351 1352 /** 1353 * Set or reset avalanche suppression. 1354 * 1355 * @param disabledForTest If true, disable; otherwise reset to default. 1356 */ setCooldownSettingDisabled(boolean disabledForTest)1357 public void setCooldownSettingDisabled(boolean disabledForTest) { 1358 StringBuilder sb = new StringBuilder(); 1359 StringBuilder command = new StringBuilder(""); 1360 if (disabledForTest) { 1361 command.append("settings put system notification_cooldown_enabled 0"); 1362 } else { 1363 command.append("settings reset system notification_cooldown_enabled"); 1364 } 1365 runCommandAndCollectResult("set avalanche suppression", command.toString(), sb); 1366 Log.d(LOG_TAG, sb.toString()); 1367 } 1368 runCommandAndCollectResult( String description, String cmd, StringBuilder sb)1369 private static String runCommandAndCollectResult( 1370 String description, String cmd, StringBuilder sb) { 1371 if (cmd == null || sb == null) { 1372 return null; 1373 } 1374 String result = null; 1375 try { 1376 Log.d(LOG_TAG, "Before command: " + cmd); 1377 result = getUiDevice().executeShellCommand(cmd); 1378 String msg = String.format("%s command: %s\nResult: %s\n", description, cmd, result); 1379 Log.d(LOG_TAG, msg); 1380 sb.append(msg); 1381 } catch (IOException ioe) { 1382 Log.e(LOG_TAG, "Failed to run command: " + cmd, ioe); 1383 } 1384 return result; 1385 } 1386 1387 /** 1388 * Set up or clear the debug filter; restricting notifications to the provided packages, or 1389 * resetting if none are provided. 1390 * 1391 * @param allowedPackages package names allowed to show notifications 1392 */ setDebugNotificationFilter(@ullable List<String> allowedPackages)1393 private void setDebugNotificationFilter(@Nullable List<String> allowedPackages) { 1394 StringBuilder sb = new StringBuilder(); 1395 StringBuilder command = new StringBuilder("cmd statusbar notif-filter "); 1396 if (allowedPackages == null || allowedPackages.isEmpty()) { 1397 command.append("reset"); 1398 } else { 1399 command.append("allowed-pkgs"); 1400 for (String pkg : allowedPackages) { 1401 command.append(" "); 1402 command.append(pkg); 1403 } 1404 } 1405 runCommandAndCollectResult("set debug filter", command.toString(), sb); 1406 Log.d(LOG_TAG, sb.toString()); 1407 } 1408 1409 /** 1410 * Set up or clear the debug filter; restricting notifications to the test package, or resetting 1411 * if false is provided. 1412 * 1413 * @param enabled whether to enable the debug filter 1414 */ setDebugNotificationFilter(boolean enabled)1415 public void setDebugNotificationFilter(boolean enabled) { 1416 setDebugNotificationFilter(enabled ? List.of(PACKAGE_NAME) : null); 1417 } 1418 1419 /** 1420 * Holds the identities of a group summary and children as posted by {@link 1421 * NotificationController#postGroupNotifications(int, String, String, boolean)}. 1422 */ 1423 public static class GroupNotificationIdentities { 1424 public NotificationIdentity summary = null; 1425 public List<NotificationIdentity> children = new ArrayList<NotificationIdentity>(); 1426 } 1427 1428 /** 1429 * The importance of the Notification to be posted. 1430 * 1431 * @see NotificationChannel#setImportance(int) 1432 */ 1433 public enum Importance { 1434 HIGH, 1435 DEFAULT, 1436 LOW, 1437 MIN 1438 } 1439 } 1440