1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.notification.row;
18 
19 import static android.app.Notification.FLAG_BUBBLE;
20 import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
21 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
22 import static android.app.NotificationManager.IMPORTANCE_HIGH;
23 
24 import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
25 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
26 
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertTrue;
29 import static org.mockito.ArgumentMatchers.any;
30 import static org.mockito.ArgumentMatchers.anyInt;
31 import static org.mockito.Mockito.mock;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.when;
34 
35 import android.annotation.Nullable;
36 import android.app.ActivityManager;
37 import android.app.Notification;
38 import android.app.Notification.BubbleMetadata;
39 import android.app.NotificationChannel;
40 import android.app.PendingIntent;
41 import android.content.Context;
42 import android.content.Intent;
43 import android.content.pm.LauncherApps;
44 import android.graphics.drawable.Icon;
45 import android.os.UserHandle;
46 import android.service.notification.StatusBarNotification;
47 import android.testing.TestableLooper;
48 import android.text.TextUtils;
49 import android.view.LayoutInflater;
50 import android.widget.RemoteViews;
51 
52 import androidx.annotation.NonNull;
53 
54 import com.android.internal.logging.MetricsLogger;
55 import com.android.internal.statusbar.IStatusBarService;
56 import com.android.systemui.TestableDependency;
57 import com.android.systemui.classifier.FalsingCollectorFake;
58 import com.android.systemui.classifier.FalsingManagerFake;
59 import com.android.systemui.flags.FakeFeatureFlags;
60 import com.android.systemui.flags.FeatureFlags;
61 import com.android.systemui.flags.Flags;
62 import com.android.systemui.media.controls.util.MediaFeatureFlag;
63 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
64 import com.android.systemui.plugins.statusbar.StatusBarStateController;
65 import com.android.systemui.statusbar.NotificationMediaManager;
66 import com.android.systemui.statusbar.NotificationRemoteInputManager;
67 import com.android.systemui.statusbar.NotificationShadeWindowController;
68 import com.android.systemui.statusbar.SmartReplyController;
69 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
70 import com.android.systemui.statusbar.notification.SourceType;
71 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
72 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
73 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
74 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
75 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
76 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
77 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
78 import com.android.systemui.statusbar.notification.icon.IconBuilder;
79 import com.android.systemui.statusbar.notification.icon.IconManager;
80 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
81 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
82 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
83 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
84 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
85 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
86 import com.android.systemui.statusbar.phone.KeyguardBypassController;
87 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
88 import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
89 import com.android.systemui.statusbar.policy.SmartReplyConstants;
90 import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
91 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
92 import com.android.systemui.tests.R;
93 import com.android.systemui.wmshell.BubblesManager;
94 import com.android.systemui.wmshell.BubblesTestActivity;
95 
96 import org.mockito.ArgumentCaptor;
97 
98 import java.util.Objects;
99 import java.util.Optional;
100 import java.util.concurrent.CountDownLatch;
101 import java.util.concurrent.Executor;
102 import java.util.concurrent.TimeUnit;
103 
104 /**
105  * A helper class to create {@link ExpandableNotificationRow} (for both individual and group
106  * notifications).
107  */
108 public class NotificationTestHelper {
109 
110     /** Package name for testing purposes. */
111     public static final String PKG = "com.android.systemui";
112     /** System UI id for testing purposes. */
113     public static final int UID = 1000;
114     /** Current {@link UserHandle} of the system. */
115     public static final UserHandle USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser());
116 
117     private static final String GROUP_KEY = "gruKey";
118     private static final String APP_NAME = "appName";
119 
120     private final Context mContext;
121     private final TestableLooper mTestLooper;
122     private int mId;
123     private final ExpandableNotificationRowLogger mMockLogger;
124     private final GroupMembershipManager mGroupMembershipManager;
125     private final GroupExpansionManager mGroupExpansionManager;
126     private ExpandableNotificationRow mRow;
127     private final HeadsUpManagerPhone mHeadsUpManager;
128     private final NotifBindPipeline mBindPipeline;
129     private final NotifCollectionListener mBindPipelineEntryListener;
130     private final RowContentBindStage mBindStage;
131     private final IconManager mIconManager;
132     private final StatusBarStateController mStatusBarStateController;
133     private final KeyguardBypassController mKeyguardBypassController;
134     private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
135     private final OnUserInteractionCallback mOnUserInteractionCallback;
136     private final NotificationDismissibilityProvider mDismissibilityProvider;
137     public final Runnable mFutureDismissalRunnable;
138     private @InflationFlag int mDefaultInflationFlags;
139     private final FakeFeatureFlags mFeatureFlags;
140 
NotificationTestHelper( Context context, TestableDependency dependency, TestableLooper testLooper)141     public NotificationTestHelper(
142             Context context,
143             TestableDependency dependency,
144             TestableLooper testLooper) {
145         this(context, dependency, testLooper, new FakeFeatureFlags());
146     }
147 
NotificationTestHelper( Context context, TestableDependency dependency, TestableLooper testLooper, @NonNull FakeFeatureFlags featureFlags)148     public NotificationTestHelper(
149             Context context,
150             TestableDependency dependency,
151             TestableLooper testLooper,
152             @NonNull FakeFeatureFlags featureFlags) {
153         mContext = context;
154         mTestLooper = testLooper;
155         mFeatureFlags = Objects.requireNonNull(featureFlags);
156         dependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
157         dependency.injectMockDependency(NotificationMediaManager.class);
158         dependency.injectMockDependency(NotificationShadeWindowController.class);
159         dependency.injectMockDependency(MediaOutputDialogFactory.class);
160         mMockLogger = mock(ExpandableNotificationRowLogger.class);
161         mStatusBarStateController = mock(StatusBarStateController.class);
162         mKeyguardBypassController = mock(KeyguardBypassController.class);
163         mGroupMembershipManager = mock(GroupMembershipManager.class);
164         mGroupExpansionManager = mock(GroupExpansionManager.class);
165         mHeadsUpManager = mock(HeadsUpManagerPhone.class);
166         mIconManager = new IconManager(
167                 mock(CommonNotifCollection.class),
168                 mock(LauncherApps.class),
169                 new IconBuilder(mContext));
170 
171         NotificationContentInflater contentBinder = new NotificationContentInflater(
172                 mock(NotifRemoteViewCache.class),
173                 mock(NotificationRemoteInputManager.class),
174                 mock(ConversationNotificationProcessor.class),
175                 mock(MediaFeatureFlag.class),
176                 mock(Executor.class),
177                 new MockSmartReplyInflater(),
178                 mock(NotifLayoutInflaterFactory.class),
179                 mock(NotificationContentInflaterLogger.class));
180         contentBinder.setInflateSynchronously(true);
181         mBindStage = new RowContentBindStage(contentBinder,
182                 mock(NotifInflationErrorManager.class),
183                 new RowContentBindStageLogger(logcatLogBuffer()));
184 
185         CommonNotifCollection collection = mock(CommonNotifCollection.class);
186 
187         mBindPipeline = new NotifBindPipeline(
188                 collection,
189                 new NotifBindPipelineLogger(logcatLogBuffer()),
190                 mTestLooper.getLooper());
191         mBindPipeline.setStage(mBindStage);
192 
193         ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor =
194                 ArgumentCaptor.forClass(NotifCollectionListener.class);
195         verify(collection).addCollectionListener(collectionListenerCaptor.capture());
196         mBindPipelineEntryListener = collectionListenerCaptor.getValue();
197         mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class);
198         mOnUserInteractionCallback = mock(OnUserInteractionCallback.class);
199         mDismissibilityProvider = mock(NotificationDismissibilityProvider.class);
200         mFutureDismissalRunnable = mock(Runnable.class);
201         when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt()))
202                 .thenReturn(mFutureDismissalRunnable);
203     }
204 
setDefaultInflationFlags(@nflationFlag int defaultInflationFlags)205     public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) {
206         mDefaultInflationFlags = defaultInflationFlags;
207     }
208 
getMockLogger()209     public ExpandableNotificationRowLogger getMockLogger() {
210         return mMockLogger;
211     }
212 
getOnUserInteractionCallback()213     public OnUserInteractionCallback getOnUserInteractionCallback() {
214         return mOnUserInteractionCallback;
215     }
216 
getDismissibilityProvider()217     public NotificationDismissibilityProvider getDismissibilityProvider() {
218         return mDismissibilityProvider;
219     }
220 
221     /**
222      * Creates a generic row with rounded border.
223      *
224      * @return a generic row with the set roundness.
225      * @throws Exception
226      */
createRowWithRoundness( float topRoundness, float bottomRoundness, SourceType sourceType )227     public ExpandableNotificationRow createRowWithRoundness(
228             float topRoundness,
229             float bottomRoundness,
230             SourceType sourceType
231     ) throws Exception {
232         ExpandableNotificationRow row = createRow();
233         row.requestRoundness(topRoundness, bottomRoundness, sourceType, /*animate = */ false);
234         assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f);
235         assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f);
236         return row;
237     }
238 
239     /**
240      * Creates a generic row.
241      *
242      * @return a generic row with no special properties.
243      * @throws Exception
244      */
createRow()245     public ExpandableNotificationRow createRow() throws Exception {
246         return createRow(PKG, UID, USER_HANDLE);
247     }
248 
249     /**
250      * Create a row with the package and user id specified.
251      *
252      * @param pkg package
253      * @param uid user id
254      * @return a row with a notification using the package and user id
255      * @throws Exception
256      */
createRow(String pkg, int uid, UserHandle userHandle)257     public ExpandableNotificationRow createRow(String pkg, int uid, UserHandle userHandle)
258             throws Exception {
259         return createRow(pkg, uid, userHandle, false /* isGroupSummary */, null /* groupKey */);
260     }
261 
262     /**
263      * Creates a row based off the notification given.
264      *
265      * @param notification the notification
266      * @return a row built off the notification
267      * @throws Exception
268      */
createRow(Notification notification)269     public ExpandableNotificationRow createRow(Notification notification) throws Exception {
270         return generateRow(notification, PKG, UID, USER_HANDLE, mDefaultInflationFlags);
271     }
272 
createRow(NotificationEntry entry)273     public ExpandableNotificationRow createRow(NotificationEntry entry) throws Exception {
274         return generateRow(entry, mDefaultInflationFlags);
275     }
276 
277     /**
278      * Create a row with the specified content views inflated in addition to the default.
279      *
280      * @param extraInflationFlags the flags corresponding to the additional content views that
281      *                            should be inflated
282      * @return a row with the specified content views inflated in addition to the default
283      * @throws Exception
284      */
createRow(@nflationFlag int extraInflationFlags)285     public ExpandableNotificationRow createRow(@InflationFlag int extraInflationFlags)
286             throws Exception {
287         return generateRow(createNotification(), PKG, UID, USER_HANDLE, extraInflationFlags);
288     }
289 
290     /**
291      * Returns an {@link ExpandableNotificationRow} group with the given number of child
292      * notifications.
293      */
createGroup(int numChildren)294     public ExpandableNotificationRow createGroup(int numChildren) throws Exception {
295         ExpandableNotificationRow row = createGroupSummary(GROUP_KEY);
296         for (int i = 0; i < numChildren; i++) {
297             ExpandableNotificationRow childRow = createGroupChild(GROUP_KEY);
298             row.addChildNotification(childRow);
299         }
300         return row;
301     }
302 
303     /** Returns a group notification with 2 child notifications. */
createGroup()304     public ExpandableNotificationRow createGroup() throws Exception {
305         return createGroup(2);
306     }
307 
createGroupSummary(String groupkey)308     private ExpandableNotificationRow createGroupSummary(String groupkey) throws Exception {
309         return createRow(PKG, UID, USER_HANDLE, true /* isGroupSummary */, groupkey);
310     }
311 
createGroupChild(String groupkey)312     private ExpandableNotificationRow createGroupChild(String groupkey) throws Exception {
313         return createRow(PKG, UID, USER_HANDLE, false /* isGroupSummary */, groupkey);
314     }
315 
316     /**
317      * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
318      */
createBubble()319     public ExpandableNotificationRow createBubble()
320             throws Exception {
321         Notification n = createNotification(false /* isGroupSummary */,
322                 null /* groupKey */,
323                 makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */));
324         n.flags |= FLAG_BUBBLE;
325         ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE,
326                 mDefaultInflationFlags, IMPORTANCE_HIGH);
327         modifyRanking(row.getEntry())
328                 .setCanBubble(true)
329                 .build();
330         return row;
331     }
332 
333     /**
334      * Returns an {@link ExpandableNotificationRow} that shows as a sticky FSI HUN.
335      */
createStickyRow()336     public ExpandableNotificationRow createStickyRow()
337             throws Exception {
338         Notification n = createNotification(false /* isGroupSummary */,
339                 null /* groupKey */,
340                 makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */));
341         n.flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
342         ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE,
343                 mDefaultInflationFlags, IMPORTANCE_HIGH);
344         return row;
345     }
346 
347 
348     /**
349      * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
350      */
createShortcutBubble(String shortcutId)351     public ExpandableNotificationRow createShortcutBubble(String shortcutId)
352             throws Exception {
353         Notification n = createNotification(false /* isGroupSummary */,
354                 null /* groupKey */, makeShortcutBubbleMetadata(shortcutId));
355         n.flags |= FLAG_BUBBLE;
356         ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE,
357                 mDefaultInflationFlags, IMPORTANCE_HIGH);
358         modifyRanking(row.getEntry())
359                 .setCanBubble(true)
360                 .build();
361         return row;
362     }
363 
364     /**
365      * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble and is part
366      * of a group of notifications.
367      */
createBubbleInGroup()368     public ExpandableNotificationRow createBubbleInGroup()
369             throws Exception {
370         Notification n = createNotification(false /* isGroupSummary */,
371                 GROUP_KEY /* groupKey */,
372                 makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */));
373         n.flags |= FLAG_BUBBLE;
374         ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE,
375                 mDefaultInflationFlags, IMPORTANCE_HIGH);
376         modifyRanking(row.getEntry())
377                 .setCanBubble(true)
378                 .build();
379         return row;
380     }
381 
382     /**
383      * Returns an {@link NotificationEntry} that should be shown as a bubble.
384      *
385      * @param deleteIntent the intent to assign to {@link BubbleMetadata#deleteIntent}
386      */
createBubble(@ullable PendingIntent deleteIntent)387     public NotificationEntry createBubble(@Nullable PendingIntent deleteIntent) {
388         return createBubble(makeBubbleMetadata(deleteIntent, false /* autoExpand */), USER_HANDLE);
389     }
390 
391     /**
392      * Returns an {@link NotificationEntry} that should be shown as a bubble.
393      *
394      * @param handle the user to associate with this bubble.
395      */
createBubble(UserHandle handle)396     public NotificationEntry createBubble(UserHandle handle) {
397         return createBubble(makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */),
398                 handle);
399     }
400 
401     /**
402      * Returns an {@link NotificationEntry} that should be shown as a auto-expanded bubble.
403      */
createAutoExpandedBubble()404     public NotificationEntry createAutoExpandedBubble() {
405         return createBubble(makeBubbleMetadata(null /* deleteIntent */, true /* autoExpand */),
406                 USER_HANDLE);
407     }
408 
409     /**
410      * Returns an {@link NotificationEntry} that should be shown as a bubble.
411      *
412      * @param userHandle the user to associate with this notification.
413      */
createBubble(BubbleMetadata metadata, UserHandle userHandle)414     private NotificationEntry createBubble(BubbleMetadata metadata, UserHandle userHandle) {
415         Notification n = createNotification(false /* isGroupSummary */, null /* groupKey */,
416                 metadata);
417         n.flags |= FLAG_BUBBLE;
418 
419         final NotificationChannel channel =
420                 new NotificationChannel(
421                         n.getChannelId(),
422                         n.getChannelId(),
423                         IMPORTANCE_HIGH);
424         channel.setBlockable(true);
425 
426         NotificationEntry entry = new NotificationEntryBuilder()
427                 .setPkg(PKG)
428                 .setOpPkg(PKG)
429                 .setId(mId++)
430                 .setUid(UID)
431                 .setInitialPid(2000)
432                 .setNotification(n)
433                 .setUser(userHandle)
434                 .setPostTime(System.currentTimeMillis())
435                 .setChannel(channel)
436                 .build();
437 
438         modifyRanking(entry)
439                 .setCanBubble(true)
440                 .build();
441         return entry;
442     }
443 
444     /**
445      * Creates a notification row with the given details.
446      *
447      * @param pkg package used for creating a {@link StatusBarNotification}
448      * @param uid uid used for creating a {@link StatusBarNotification}
449      * @param isGroupSummary whether the notification row is a group summary
450      * @param groupKey the group key for the notification group used across notifications
451      * @return a row with that's either a standalone notification or a group notification if the
452      *         groupKey is non-null
453      * @throws Exception
454      */
createRow( String pkg, int uid, UserHandle userHandle, boolean isGroupSummary, @Nullable String groupKey)455     private ExpandableNotificationRow createRow(
456             String pkg,
457             int uid,
458             UserHandle userHandle,
459             boolean isGroupSummary,
460             @Nullable String groupKey)
461             throws Exception {
462         Notification notif = createNotification(isGroupSummary, groupKey);
463         return generateRow(notif, pkg, uid, userHandle, mDefaultInflationFlags);
464     }
465 
466     /**
467      * Creates a generic notification.
468      *
469      * @return a notification with no special properties
470      */
createNotification()471     public Notification createNotification() {
472         return createNotification(false /* isGroupSummary */, null /* groupKey */);
473     }
474 
475     /**
476      * Creates a notification with the given parameters.
477      *
478      * @param isGroupSummary whether the notification is a group summary
479      * @param groupKey the group key for the notification group used across notifications
480      * @return a notification that is in the group specified or standalone if unspecified
481      */
createNotification(boolean isGroupSummary, @Nullable String groupKey)482     private Notification createNotification(boolean isGroupSummary, @Nullable String groupKey) {
483         return createNotification(isGroupSummary, groupKey, null /* bubble metadata */);
484     }
485 
486     /**
487      * Creates a notification with the given parameters.
488      *
489      * @param isGroupSummary whether the notification is a group summary
490      * @param groupKey the group key for the notification group used across notifications
491      * @param bubbleMetadata the bubble metadata to use for this notification if it exists.
492      * @return a notification that is in the group specified or standalone if unspecified
493      */
createNotification(boolean isGroupSummary, @Nullable String groupKey, @Nullable BubbleMetadata bubbleMetadata)494     public Notification createNotification(boolean isGroupSummary,
495             @Nullable String groupKey, @Nullable BubbleMetadata bubbleMetadata) {
496         Notification publicVersion = new Notification.Builder(mContext).setSmallIcon(
497                 R.drawable.ic_person)
498                 .setCustomContentView(new RemoteViews(mContext.getPackageName(),
499                         R.layout.custom_view_dark))
500                 .build();
501         Notification.Builder notificationBuilder = new Notification.Builder(mContext, "channelId")
502                 .setSmallIcon(R.drawable.ic_person)
503                 .setContentTitle("Title")
504                 .setContentText("Text")
505                 .setPublicVersion(publicVersion)
506                 .setStyle(new Notification.BigTextStyle().bigText("Big Text"));
507         if (isGroupSummary) {
508             notificationBuilder.setGroupSummary(true);
509         }
510         if (!TextUtils.isEmpty(groupKey)) {
511             notificationBuilder.setGroup(groupKey);
512         }
513         if (bubbleMetadata != null) {
514             notificationBuilder.setBubbleMetadata(bubbleMetadata);
515         }
516         return notificationBuilder.build();
517     }
518 
getStatusBarStateController()519     public StatusBarStateController getStatusBarStateController() {
520         return mStatusBarStateController;
521     }
522 
getKeyguardBypassController()523     public KeyguardBypassController getKeyguardBypassController() {
524         return mKeyguardBypassController;
525     }
526 
generateRow( Notification notification, String pkg, int uid, UserHandle userHandle, @InflationFlag int extraInflationFlags)527     private ExpandableNotificationRow generateRow(
528             Notification notification,
529             String pkg,
530             int uid,
531             UserHandle userHandle,
532             @InflationFlag int extraInflationFlags)
533             throws Exception {
534         return generateRow(notification, pkg, uid, userHandle, extraInflationFlags,
535                 IMPORTANCE_DEFAULT);
536     }
537 
generateRow( Notification notification, String pkg, int uid, UserHandle userHandle, @InflationFlag int extraInflationFlags, int importance)538     private ExpandableNotificationRow generateRow(
539             Notification notification,
540             String pkg,
541             int uid,
542             UserHandle userHandle,
543             @InflationFlag int extraInflationFlags,
544             int importance)
545             throws Exception {
546         final NotificationChannel channel =
547                 new NotificationChannel(
548                         notification.getChannelId(),
549                         notification.getChannelId(),
550                         importance);
551         channel.setBlockable(true);
552 
553         NotificationEntry entry = new NotificationEntryBuilder()
554                 .setPkg(pkg)
555                 .setOpPkg(pkg)
556                 .setId(mId++)
557                 .setUid(uid)
558                 .setInitialPid(2000)
559                 .setNotification(notification)
560                 .setUser(userHandle)
561                 .setPostTime(System.currentTimeMillis())
562                 .setChannel(channel)
563                 .build();
564 
565         return generateRow(entry, extraInflationFlags);
566     }
567 
generateRow( NotificationEntry entry, @InflationFlag int extraInflationFlags)568     private ExpandableNotificationRow generateRow(
569             NotificationEntry entry,
570             @InflationFlag int extraInflationFlags)
571             throws Exception {
572         // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be
573         //  set, but we do not want to override an existing value that is needed by a specific test.
574         mFeatureFlags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS);
575 
576         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
577                 mContext.LAYOUT_INFLATER_SERVICE);
578         mRow = (ExpandableNotificationRow) inflater.inflate(
579                 R.layout.status_bar_notification_row,
580                 null /* root */,
581                 false /* attachToRoot */);
582         ExpandableNotificationRow row = mRow;
583 
584         entry.setRow(row);
585         mIconManager.createIcons(entry);
586 
587         mBindPipelineEntryListener.onEntryInit(entry);
588         mBindPipeline.manageRow(entry, row);
589 
590         row.initialize(
591                 entry,
592                 mock(RemoteInputViewSubcomponent.Factory.class),
593                 APP_NAME,
594                 entry.getKey(),
595                 mMockLogger,
596                 mKeyguardBypassController,
597                 mGroupMembershipManager,
598                 mGroupExpansionManager,
599                 mHeadsUpManager,
600                 mBindStage,
601                 mock(OnExpandClickListener.class),
602                 mock(ExpandableNotificationRow.CoordinateOnClickListener.class),
603                 new FalsingManagerFake(),
604                 new FalsingCollectorFake(),
605                 mStatusBarStateController,
606                 mPeopleNotificationIdentifier,
607                 mOnUserInteractionCallback,
608                 Optional.of(mock(BubblesManager.class)),
609                 mock(NotificationGutsManager.class),
610                 mDismissibilityProvider,
611                 mock(MetricsLogger.class),
612                 new NotificationChildrenContainerLogger(logcatLogBuffer()),
613                 mock(SmartReplyConstants.class),
614                 mock(SmartReplyController.class),
615                 mFeatureFlags,
616                 mock(IStatusBarService.class));
617 
618         row.setAboveShelfChangedListener(aboveShelf -> { });
619         mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
620         inflateAndWait(entry);
621 
622         return row;
623     }
624 
inflateAndWait(NotificationEntry entry)625     private void inflateAndWait(NotificationEntry entry) throws Exception {
626         CountDownLatch countDownLatch = new CountDownLatch(1);
627         mBindStage.requestRebind(entry, en -> countDownLatch.countDown());
628         mTestLooper.processAllMessages();
629         assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
630     }
631 
makeBubbleMetadata(PendingIntent deleteIntent, boolean autoExpand)632     private BubbleMetadata makeBubbleMetadata(PendingIntent deleteIntent, boolean autoExpand) {
633         Intent target = new Intent(mContext, BubblesTestActivity.class);
634         PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target,
635                 PendingIntent.FLAG_MUTABLE);
636 
637         return new BubbleMetadata.Builder(bubbleIntent,
638                         Icon.createWithResource(mContext, R.drawable.android))
639                 .setDeleteIntent(deleteIntent)
640                 .setDesiredHeight(314)
641                 .setAutoExpandBubble(autoExpand)
642                 .build();
643     }
644 
makeShortcutBubbleMetadata(String shortcutId)645     private BubbleMetadata makeShortcutBubbleMetadata(String shortcutId) {
646         return new BubbleMetadata.Builder(shortcutId)
647                 .setDesiredHeight(314)
648                 .build();
649     }
650 
651     private static class MockSmartReplyInflater implements SmartReplyStateInflater {
652         @Override
inflateSmartReplyState(NotificationEntry entry)653         public InflatedSmartReplyState inflateSmartReplyState(NotificationEntry entry) {
654             return mock(InflatedSmartReplyState.class);
655         }
656 
657         @Override
inflateSmartReplyViewHolder(Context sysuiContext, Context notifPackageContext, NotificationEntry entry, InflatedSmartReplyState existingSmartReplyState, InflatedSmartReplyState newSmartReplyState)658         public InflatedSmartReplyViewHolder inflateSmartReplyViewHolder(Context sysuiContext,
659                 Context notifPackageContext, NotificationEntry entry,
660                 InflatedSmartReplyState existingSmartReplyState,
661                 InflatedSmartReplyState newSmartReplyState) {
662             return mock(InflatedSmartReplyViewHolder.class);
663         }
664     }
665 }
666