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