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