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