• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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