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