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