1 /* <lambda>null2 * Copyright (C) 2024 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 package com.android.systemui.statusbar.notification.row 17 18 import android.R 19 import android.app.AppOpsManager 20 import android.app.INotificationManager 21 import android.app.Notification 22 import android.app.NotificationChannel 23 import android.app.NotificationManager 24 import android.content.Intent 25 import android.content.pm.PackageManager 26 import android.content.pm.ShortcutManager 27 import android.content.pm.launcherApps 28 import android.graphics.Color 29 import android.os.Binder 30 import android.os.fakeExecutorHandler 31 import android.os.userManager 32 import android.provider.Settings 33 import android.service.notification.NotificationListenerService.Ranking 34 import android.testing.TestableLooper.RunWithLooper 35 import android.util.ArraySet 36 import android.view.View 37 import android.view.accessibility.accessibilityManager 38 import androidx.test.ext.junit.runners.AndroidJUnit4 39 import androidx.test.filters.SmallTest 40 import com.android.compose.animation.scene.ObservableTransitionState 41 import com.android.internal.logging.MetricsLogger 42 import com.android.internal.logging.UiEventLogger 43 import com.android.internal.logging.metricsLogger 44 import com.android.internal.logging.testing.UiEventLoggerFake 45 import com.android.internal.statusbar.statusBarService 46 import com.android.systemui.SysuiTestCase 47 import com.android.systemui.concurrency.fakeExecutor 48 import com.android.systemui.flags.EnableSceneContainer 49 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository 50 import com.android.systemui.kosmos.testScope 51 import com.android.systemui.people.widget.PeopleSpaceWidgetManager 52 import com.android.systemui.plugins.activityStarter 53 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin 54 import com.android.systemui.plugins.statusbar.statusBarStateController 55 import com.android.systemui.power.domain.interactor.PowerInteractorFactory.create 56 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository 57 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor 58 import com.android.systemui.scene.domain.interactor.sceneInteractor 59 import com.android.systemui.scene.shared.model.Scenes 60 import com.android.systemui.settings.UserContextProvider 61 import com.android.systemui.shade.shadeControllerSceneImpl 62 import com.android.systemui.statusbar.NotificationEntryHelper 63 import com.android.systemui.statusbar.NotificationPresenter 64 import com.android.systemui.statusbar.notification.AssistantFeedbackController 65 import com.android.systemui.statusbar.notification.NotificationActivityStarter 66 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider 67 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor 68 import com.android.systemui.statusbar.notification.headsup.headsUpManager 69 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier 70 import com.android.systemui.statusbar.notification.stack.NotificationListContainer 71 import com.android.systemui.statusbar.notificationLockscreenUserManager 72 import com.android.systemui.statusbar.policy.deviceProvisionedController 73 import com.android.systemui.testKosmos 74 import com.android.systemui.util.kotlin.JavaAdapter 75 import com.android.systemui.wmshell.BubblesManager 76 import java.util.Optional 77 import kotlin.test.assertEquals 78 import kotlinx.coroutines.ExperimentalCoroutinesApi 79 import kotlinx.coroutines.flow.MutableStateFlow 80 import kotlinx.coroutines.test.runCurrent 81 import org.junit.Assert 82 import org.junit.Before 83 import org.junit.Test 84 import org.junit.runner.RunWith 85 import org.mockito.Mock 86 import org.mockito.MockitoAnnotations 87 import org.mockito.invocation.InvocationOnMock 88 import org.mockito.kotlin.any 89 import org.mockito.kotlin.anyOrNull 90 import org.mockito.kotlin.argumentCaptor 91 import org.mockito.kotlin.doNothing 92 import org.mockito.kotlin.eq 93 import org.mockito.kotlin.mock 94 import org.mockito.kotlin.never 95 import org.mockito.kotlin.spy 96 import org.mockito.kotlin.times 97 import org.mockito.kotlin.verify 98 import org.mockito.kotlin.whenever 99 100 /** Tests for [NotificationGutsManager] with the scene container enabled. */ 101 @OptIn(ExperimentalCoroutinesApi::class) 102 @SmallTest 103 @RunWith(AndroidJUnit4::class) 104 @RunWithLooper 105 @EnableSceneContainer 106 class NotificationGutsManagerWithScenesTest : SysuiTestCase() { 107 private val testNotificationChannel = 108 NotificationChannel( 109 TEST_CHANNEL_ID, 110 TEST_CHANNEL_ID, 111 NotificationManager.IMPORTANCE_DEFAULT, 112 ) 113 114 private val kosmos = testKosmos() 115 private val testScope = kosmos.testScope 116 private val javaAdapter = JavaAdapter(testScope.backgroundScope) 117 private val executor = kosmos.fakeExecutor 118 private val handler = kosmos.fakeExecutorHandler 119 private lateinit var helper: NotificationTestHelper 120 private lateinit var gutsManager: NotificationGutsManager 121 private lateinit var windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor 122 123 private val metricsLogger = kosmos.metricsLogger 124 private val deviceProvisionedController = kosmos.deviceProvisionedController 125 private val accessibilityManager = kosmos.accessibilityManager 126 private val mBarService = kosmos.statusBarService 127 private val launcherApps = kosmos.launcherApps 128 private val shadeController = kosmos.shadeControllerSceneImpl 129 private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager 130 private val statusBarStateController = kosmos.statusBarStateController 131 private val headsUpManager = kosmos.headsUpManager 132 private val activityStarter = kosmos.activityStarter 133 private val userManager = kosmos.userManager 134 private val activeNotificationsInteractor = kosmos.activeNotificationsInteractor 135 private val sceneInteractor = kosmos.sceneInteractor 136 137 @Mock private lateinit var onUserInteractionCallback: OnUserInteractionCallback 138 @Mock private lateinit var presenter: NotificationPresenter 139 @Mock private lateinit var notificationActivityStarter: NotificationActivityStarter 140 @Mock private lateinit var notificationListContainer: NotificationListContainer 141 @Mock 142 private lateinit var onSettingsClickListener: NotificationGutsManager.OnSettingsClickListener 143 @Mock private lateinit var highPriorityProvider: HighPriorityProvider 144 @Mock private lateinit var notificationManager: INotificationManager 145 @Mock private lateinit var shortcutManager: ShortcutManager 146 @Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController 147 @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier 148 @Mock private lateinit var contextTracker: UserContextProvider 149 @Mock private lateinit var bubblesManager: BubblesManager 150 @Mock private lateinit var peopleSpaceWidgetManager: PeopleSpaceWidgetManager 151 @Mock private lateinit var assistantFeedbackController: AssistantFeedbackController 152 153 @Before 154 fun setUp() { 155 MockitoAnnotations.initMocks(this) 156 allowTestableLooperAsMainThread() 157 helper = NotificationTestHelper(mContext, mDependency) 158 whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(false) 159 windowRootViewVisibilityInteractor = 160 WindowRootViewVisibilityInteractor( 161 testScope.backgroundScope, 162 WindowRootViewVisibilityRepository(mBarService, executor), 163 FakeKeyguardRepository(), 164 headsUpManager, 165 create().powerInteractor, 166 activeNotificationsInteractor, 167 ) { 168 sceneInteractor 169 } 170 gutsManager = 171 NotificationGutsManager( 172 mContext, 173 handler, 174 handler, 175 javaAdapter, 176 accessibilityManager, 177 highPriorityProvider, 178 notificationManager, 179 userManager, 180 peopleSpaceWidgetManager, 181 launcherApps, 182 shortcutManager, 183 channelEditorDialogController, 184 contextTracker, 185 assistantFeedbackController, 186 Optional.of(bubblesManager), 187 UiEventLoggerFake(), 188 onUserInteractionCallback, 189 shadeController, 190 windowRootViewVisibilityInteractor, 191 notificationLockscreenUserManager, 192 statusBarStateController, 193 mBarService, 194 deviceProvisionedController, 195 metricsLogger, 196 headsUpManager, 197 activityStarter, 198 ) 199 gutsManager.setUpWithPresenter( 200 presenter, 201 notificationListContainer, 202 onSettingsClickListener, 203 ) 204 gutsManager.setNotificationActivityStarter(notificationActivityStarter) 205 gutsManager.start() 206 } 207 208 @Test 209 fun testOpenAndCloseGuts() { 210 val guts = spy(NotificationGuts(mContext)) 211 whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock -> 212 handler.post((invocation.arguments[0] as Runnable)) 213 null 214 } 215 216 // Test doesn't support animation since the guts view is not attached. 217 doNothing() 218 .whenever(guts) 219 .openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>()) 220 val realRow = createTestNotificationRow() 221 val menuItem = createTestMenuItem(realRow) 222 val row = spy(realRow) 223 whenever(row!!.windowToken).thenReturn(Binder()) 224 whenever(row.guts).thenReturn(guts) 225 Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem)) 226 assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong()) 227 executor.runAllReady() 228 verify(guts).openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>()) 229 verify(headsUpManager).setGutsShown(realRow!!.entry, true) 230 assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong()) 231 gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false) 232 verify(guts) 233 .closeControls(any<Boolean>(), any<Boolean>(), any<Int>(), any<Int>(), any<Boolean>()) 234 verify(row, times(1)).setGutsView(any()) 235 executor.runAllReady() 236 verify(headsUpManager).setGutsShown(realRow.entry, false) 237 } 238 239 @Test 240 fun testLockscreenShadeVisible_visible_gutsNotClosed() { 241 // First, start out lockscreen or shade as not visible 242 setIsLockscreenOrShadeVisible(false) 243 testScope.testScheduler.runCurrent() 244 val guts = mock<NotificationGuts>() 245 gutsManager.exposedGuts = guts 246 247 // WHEN the lockscreen or shade becomes visible 248 setIsLockscreenOrShadeVisible(true) 249 testScope.testScheduler.runCurrent() 250 251 // THEN the guts are not closed 252 verify(guts, never()).removeCallbacks(any()) 253 verify(guts, never()) 254 .closeControls(any<Boolean>(), any<Boolean>(), any<Int>(), any<Int>(), any<Boolean>()) 255 } 256 257 @Test 258 fun testLockscreenShadeVisible_notVisible_gutsClosed() { 259 // First, start out lockscreen or shade as visible 260 setIsLockscreenOrShadeVisible(true) 261 testScope.testScheduler.runCurrent() 262 val guts = mock<NotificationGuts>() 263 gutsManager.exposedGuts = guts 264 265 // WHEN the lockscreen or shade is no longer visible 266 setIsLockscreenOrShadeVisible(false) 267 testScope.testScheduler.runCurrent() 268 269 // THEN the guts are closed 270 verify(guts).removeCallbacks(anyOrNull()) 271 verify(guts) 272 .closeControls( 273 /* leavebehinds= */ eq(true), 274 /* controls= */ eq(true), 275 /* x= */ any<Int>(), 276 /* y= */ any<Int>(), 277 /* force= */ eq(true), 278 ) 279 } 280 281 @Test 282 fun testLockscreenShadeVisible_notVisible_listContainerReset() { 283 // First, start out lockscreen or shade as visible 284 setIsLockscreenOrShadeVisible(true) 285 testScope.testScheduler.runCurrent() 286 287 // WHEN the lockscreen or shade is no longer visible 288 setIsLockscreenOrShadeVisible(false) 289 testScope.testScheduler.runCurrent() 290 291 // THEN the list container is reset 292 verify(notificationListContainer).resetExposedMenuView(any<Boolean>(), any<Boolean>()) 293 } 294 295 @Test 296 fun testChangeDensityOrFontScale() { 297 val guts = spy(NotificationGuts(mContext)) 298 whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock -> 299 handler.post((invocation.arguments[0] as Runnable)) 300 null 301 } 302 303 // Test doesn't support animation since the guts view is not attached. 304 doNothing() 305 .whenever(guts) 306 .openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>()) 307 val realRow = createTestNotificationRow() 308 val menuItem = createTestMenuItem(realRow) 309 val row = spy(realRow) 310 whenever(row!!.windowToken).thenReturn(Binder()) 311 whenever(row.guts).thenReturn(guts) 312 doNothing().whenever(row).ensureGutsInflated() 313 val realEntry = realRow!!.entry 314 val entry = spy(realEntry) 315 whenever(entry.row).thenReturn(row) 316 whenever(entry.getGuts()).thenReturn(guts) 317 Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem)) 318 executor.runAllReady() 319 verify(guts).openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>()) 320 321 // called once by mGutsManager.bindGuts() in mGutsManager.openGuts() 322 verify(row).setGutsView(any()) 323 row.onDensityOrFontScaleChanged() 324 gutsManager.onDensityOrFontScaleChanged(entry) 325 executor.runAllReady() 326 gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false) 327 verify(guts) 328 .closeControls(any<Boolean>(), any<Boolean>(), any<Int>(), any<Int>(), any<Boolean>()) 329 330 // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged() 331 verify(row, times(2)).setGutsView(any()) 332 } 333 334 @Test 335 fun testAppOpsSettingsIntent_camera() { 336 val ops = ArraySet<Int>() 337 ops.add(AppOpsManager.OP_CAMERA) 338 gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>()) 339 val captor = argumentCaptor<Intent>() 340 verify(notificationActivityStarter, times(1)) 341 .startNotificationGutsIntent(captor.capture(), any<Int>(), any()) 342 assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action) 343 } 344 345 @Test 346 fun testAppOpsSettingsIntent_mic() { 347 val ops = ArraySet<Int>() 348 ops.add(AppOpsManager.OP_RECORD_AUDIO) 349 gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>()) 350 val captor = argumentCaptor<Intent>() 351 verify(notificationActivityStarter, times(1)) 352 .startNotificationGutsIntent(captor.capture(), any<Int>(), any()) 353 assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action) 354 } 355 356 @Test 357 fun testAppOpsSettingsIntent_camera_mic() { 358 val ops = ArraySet<Int>() 359 ops.add(AppOpsManager.OP_CAMERA) 360 ops.add(AppOpsManager.OP_RECORD_AUDIO) 361 gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>()) 362 val captor = argumentCaptor<Intent>() 363 verify(notificationActivityStarter, times(1)) 364 .startNotificationGutsIntent(captor.capture(), any<Int>(), any()) 365 assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action) 366 } 367 368 @Test 369 fun testAppOpsSettingsIntent_overlay() { 370 val ops = ArraySet<Int>() 371 ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) 372 gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>()) 373 val captor = argumentCaptor<Intent>() 374 verify(notificationActivityStarter, times(1)) 375 .startNotificationGutsIntent(captor.capture(), any<Int>(), any()) 376 assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.lastValue.action) 377 } 378 379 @Test 380 fun testAppOpsSettingsIntent_camera_mic_overlay() { 381 val ops = ArraySet<Int>() 382 ops.add(AppOpsManager.OP_CAMERA) 383 ops.add(AppOpsManager.OP_RECORD_AUDIO) 384 ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) 385 gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>()) 386 val captor = argumentCaptor<Intent>() 387 verify(notificationActivityStarter, times(1)) 388 .startNotificationGutsIntent(captor.capture(), any<Int>(), any()) 389 assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action) 390 } 391 392 @Test 393 fun testAppOpsSettingsIntent_camera_overlay() { 394 val ops = ArraySet<Int>() 395 ops.add(AppOpsManager.OP_CAMERA) 396 ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) 397 gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>()) 398 val captor = argumentCaptor<Intent>() 399 verify(notificationActivityStarter, times(1)) 400 .startNotificationGutsIntent(captor.capture(), any<Int>(), any()) 401 assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action) 402 } 403 404 @Test 405 fun testAppOpsSettingsIntent_mic_overlay() { 406 val ops = ArraySet<Int>() 407 ops.add(AppOpsManager.OP_RECORD_AUDIO) 408 ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) 409 gutsManager.startAppOpsSettingsActivity("", 0, ops, mock<ExpandableNotificationRow>()) 410 val captor = argumentCaptor<Intent>() 411 verify(notificationActivityStarter, times(1)) 412 .startNotificationGutsIntent(captor.capture(), any<Int>(), any()) 413 assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action) 414 } 415 416 @Test 417 @Throws(Exception::class) 418 fun testInitializeNotificationInfoView_highPriority() { 419 val notificationInfoView = mock<NotificationInfo>() 420 val row = spy(helper.createRow()) 421 val entry = row.entry 422 NotificationEntryHelper.modifyRanking(entry) 423 .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) 424 .setImportance(NotificationManager.IMPORTANCE_HIGH) 425 .build() 426 whenever(row.getIsNonblockable()).thenReturn(false) 427 whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true) 428 val statusBarNotification = entry.sbn 429 gutsManager.initializeNotificationInfo(row, notificationInfoView) 430 verify(notificationInfoView) 431 .bindNotification( 432 any<PackageManager>(), 433 any<INotificationManager>(), 434 eq(onUserInteractionCallback), 435 eq(channelEditorDialogController), 436 eq(statusBarNotification.packageName), 437 any<NotificationChannel>(), 438 eq(entry), 439 any<NotificationInfo.OnSettingsClickListener>(), 440 any<NotificationInfo.OnAppSettingsClickListener>(), 441 any<UiEventLogger>(), 442 eq(true), 443 eq(false), 444 eq(true), /* wasShownHighPriority */ 445 eq(assistantFeedbackController), 446 any<MetricsLogger>(), 447 ) 448 } 449 450 @Test 451 @Throws(Exception::class) 452 fun testInitializeNotificationInfoView_PassesAlongProvisionedState() { 453 val notificationInfoView = mock<NotificationInfo>() 454 val row = spy(helper.createRow()) 455 NotificationEntryHelper.modifyRanking(row.entry) 456 .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) 457 .build() 458 whenever(row.getIsNonblockable()).thenReturn(false) 459 val statusBarNotification = row.entry.sbn 460 val entry = row.entry 461 gutsManager.initializeNotificationInfo(row, notificationInfoView) 462 verify(notificationInfoView) 463 .bindNotification( 464 any<PackageManager>(), 465 any<INotificationManager>(), 466 eq(onUserInteractionCallback), 467 eq(channelEditorDialogController), 468 eq(statusBarNotification.packageName), 469 any<NotificationChannel>(), 470 eq(entry), 471 any<NotificationInfo.OnSettingsClickListener>(), 472 any<NotificationInfo.OnAppSettingsClickListener>(), 473 any<UiEventLogger>(), 474 eq(true), 475 eq(false), 476 eq(false), /* wasShownHighPriority */ 477 eq(assistantFeedbackController), 478 any<MetricsLogger>(), 479 ) 480 } 481 482 @Test 483 @Throws(Exception::class) 484 fun testInitializeNotificationInfoView_withInitialAction() { 485 val notificationInfoView = mock<NotificationInfo>() 486 val row = spy(helper.createRow()) 487 NotificationEntryHelper.modifyRanking(row.entry) 488 .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) 489 .build() 490 whenever(row.getIsNonblockable()).thenReturn(false) 491 val statusBarNotification = row.entry.sbn 492 val entry = row.entry 493 gutsManager.initializeNotificationInfo(row, notificationInfoView) 494 verify(notificationInfoView) 495 .bindNotification( 496 any<PackageManager>(), 497 any<INotificationManager>(), 498 eq(onUserInteractionCallback), 499 eq(channelEditorDialogController), 500 eq(statusBarNotification.packageName), 501 any<NotificationChannel>(), 502 eq(entry), 503 any<NotificationInfo.OnSettingsClickListener>(), 504 any<NotificationInfo.OnAppSettingsClickListener>(), 505 any<UiEventLogger>(), 506 eq(true), 507 eq(false), 508 eq(false), /* wasShownHighPriority */ 509 eq(assistantFeedbackController), 510 any<MetricsLogger>(), 511 ) 512 } 513 514 private fun createTestNotificationRow(): ExpandableNotificationRow? { 515 val nb = 516 Notification.Builder(mContext, testNotificationChannel.id) 517 .setContentTitle("foo") 518 .setColorized(true) 519 .setColor(Color.RED) 520 .setFlag(Notification.FLAG_CAN_COLORIZE, true) 521 .setSmallIcon(R.drawable.sym_def_app_icon) 522 return try { 523 val row = helper.createRow(nb.build()) 524 NotificationEntryHelper.modifyRanking(row.entry) 525 .setChannel(testNotificationChannel) 526 .build() 527 row 528 } catch (e: Exception) { 529 org.junit.Assert.fail() 530 null 531 } 532 } 533 534 private fun setIsLockscreenOrShadeVisible(isVisible: Boolean) { 535 val key = 536 if (isVisible) { 537 Scenes.Lockscreen 538 } else { 539 Scenes.Bouncer 540 } 541 sceneInteractor.changeScene(key, "test") 542 sceneInteractor.setTransitionState( 543 MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) 544 ) 545 testScope.runCurrent() 546 } 547 548 private fun createTestMenuItem( 549 row: ExpandableNotificationRow? 550 ): NotificationMenuRowPlugin.MenuItem { 551 val menuRow: NotificationMenuRowPlugin = 552 NotificationMenuRow(mContext, peopleNotificationIdentifier) 553 menuRow.createMenu(row, row!!.entry.sbn) 554 val menuItem = menuRow.getLongpressMenuItem(mContext) 555 Assert.assertNotNull(menuItem) 556 return menuItem 557 } 558 559 companion object { 560 private const val TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId" 561 } 562 } 563