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