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