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