1 /*
2  * Copyright (C) 2022 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.controls.ui
18 
19 import android.app.PendingIntent
20 import android.content.ComponentName
21 import android.content.Context
22 import android.content.pm.ApplicationInfo
23 import android.content.pm.PackageManager
24 import android.content.pm.ServiceInfo
25 import android.graphics.drawable.Drawable
26 import android.os.UserHandle
27 import android.service.controls.ControlsProviderService
28 import android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM
29 import android.testing.TestableLooper
30 import android.util.AttributeSet
31 import android.view.LayoutInflater
32 import android.view.View
33 import android.view.ViewGroup
34 import android.widget.FrameLayout
35 import androidx.test.ext.junit.runners.AndroidJUnit4
36 import androidx.test.filters.SmallTest
37 import com.android.systemui.SysuiTestCase
38 import com.android.systemui.controls.ControlsMetricsLogger
39 import com.android.systemui.controls.ControlsServiceInfo
40 import com.android.systemui.controls.CustomIconCache
41 import com.android.systemui.controls.controller.ControlsController
42 import com.android.systemui.controls.controller.StructureInfo
43 import com.android.systemui.controls.management.ControlsListingController
44 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
45 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
46 import com.android.systemui.controls.panels.SelectedComponentRepository
47 import com.android.systemui.controls.panels.selectedComponentRepository
48 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
49 import com.android.systemui.dump.DumpManager
50 import com.android.systemui.flags.FeatureFlags
51 import com.android.systemui.plugins.ActivityStarter
52 import com.android.systemui.res.R
53 import com.android.systemui.settings.UserTracker
54 import com.android.systemui.statusbar.phone.SystemUIDialog
55 import com.android.systemui.statusbar.policy.KeyguardStateController
56 import com.android.systemui.testKosmos
57 import com.android.systemui.util.FakeSystemUIDialogController
58 import com.android.systemui.util.concurrency.FakeExecutor
59 import com.android.systemui.util.mockito.any
60 import com.android.systemui.util.mockito.argumentCaptor
61 import com.android.systemui.util.mockito.capture
62 import com.android.systemui.util.mockito.eq
63 import com.android.systemui.util.mockito.mock
64 import com.android.systemui.util.mockito.whenever
65 import com.android.systemui.util.time.FakeSystemClock
66 import com.android.wm.shell.taskview.TaskView
67 import com.android.wm.shell.taskview.TaskViewFactory
68 import com.google.common.truth.Truth.assertThat
69 import java.util.Optional
70 import java.util.function.Consumer
71 import org.junit.Before
72 import org.junit.Test
73 import org.junit.runner.RunWith
74 import org.mockito.Mock
75 import org.mockito.Mockito.clearInvocations
76 import org.mockito.Mockito.doAnswer
77 import org.mockito.Mockito.doReturn
78 import org.mockito.Mockito.isNull
79 import org.mockito.Mockito.never
80 import org.mockito.Mockito.spy
81 import org.mockito.Mockito.verify
82 import org.mockito.Mockito.`when`
83 import org.mockito.MockitoAnnotations
84 
85 @SmallTest
86 @RunWith(AndroidJUnit4::class)
87 @TestableLooper.RunWithLooper
88 class ControlsUiControllerImplTest : SysuiTestCase() {
89     private val kosmos = testKosmos()
90 
91     @Mock lateinit var controlsController: ControlsController
92     @Mock lateinit var controlsListingController: ControlsListingController
93     @Mock lateinit var controlActionCoordinator: ControlActionCoordinator
94     @Mock lateinit var activityStarter: ActivityStarter
95     @Mock lateinit var iconCache: CustomIconCache
96     @Mock lateinit var controlsMetricsLogger: ControlsMetricsLogger
97     @Mock lateinit var keyguardStateController: KeyguardStateController
98     @Mock lateinit var userTracker: UserTracker
99     @Mock lateinit var taskViewFactory: TaskViewFactory
100     @Mock lateinit var dumpManager: DumpManager
101     @Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
102     @Mock lateinit var featureFlags: FeatureFlags
103     @Mock lateinit var packageManager: PackageManager
104     @Mock lateinit var systemUIDialogFactory: SystemUIDialog.Factory
105 
106     private val preferredPanelRepository = kosmos.selectedComponentRepository
107     private lateinit var fakeDialogController: FakeSystemUIDialogController
108     private val uiExecutor = FakeExecutor(FakeSystemClock())
109     private val bgExecutor = FakeExecutor(FakeSystemClock())
110 
111     private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
112     private lateinit var parent: FrameLayout
113     private lateinit var underTest: ControlsUiControllerImpl
114 
115     private var isKeyguardDismissed: Boolean = true
116     private var isRemoveAppDialogCreated: Boolean = false
117 
118     @Before
setupnull119     fun setup() {
120         MockitoAnnotations.initMocks(this)
121 
122         fakeDialogController = FakeSystemUIDialogController(mContext)
123         whenever(systemUIDialogFactory.create(any(Context::class.java)))
124             .thenReturn(fakeDialogController.dialog)
125         controlsSettingsRepository = FakeControlsSettingsRepository()
126 
127         // This way, it won't be cloned every time `LayoutInflater.fromContext` is called, but we
128         // need to clone it once so we don't modify the original one.
129         mContext.addMockSystemService(
130             Context.LAYOUT_INFLATER_SERVICE,
131             mContext.baseContext
132                 .getSystemService(LayoutInflater::class.java)!!
133                 .cloneInContext(mContext)
134         )
135 
136         parent = FrameLayout(mContext)
137 
138         underTest =
139             ControlsUiControllerImpl(
140                 { controlsController },
141                 context,
142                 packageManager,
143                 uiExecutor,
144                 bgExecutor,
145                 { controlsListingController },
146                 controlActionCoordinator,
147                 activityStarter,
148                 iconCache,
149                 controlsMetricsLogger,
150                 keyguardStateController,
151                 userTracker,
152                 Optional.of(taskViewFactory),
153                 controlsSettingsRepository,
154                 authorizedPanelsRepository,
155                 preferredPanelRepository,
156                 featureFlags,
157                 ControlsDialogsFactory(systemUIDialogFactory),
158                 dumpManager,
159             )
160         `when`(userTracker.userId).thenReturn(0)
161         `when`(userTracker.userHandle).thenReturn(UserHandle.of(0))
162         doAnswer {
163                 if (isKeyguardDismissed) {
164                     it.getArgument<ActivityStarter.OnDismissAction>(0).onDismiss()
165                 } else {
166                     it.getArgument<Runnable?>(1)?.run()
167                 }
168             }
169             .whenever(activityStarter)
170             .dismissKeyguardThenExecute(any(), isNull(), any())
171     }
172 
173     @Test
testGetPreferredPanelnull174     fun testGetPreferredPanel() {
175         val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
176 
177         preferredPanelRepository.setSelectedComponent(
178             SelectedComponentRepository.SelectedComponent(
179                 name = panel.appName.toString(),
180                 componentName = panel.componentName,
181                 isPanel = true,
182             )
183         )
184 
185         val selected = underTest.getPreferredSelectedItem(emptyList())
186 
187         assertThat(selected).isEqualTo(panel)
188     }
189 
190     @Test
testPanelDoesNotRefreshControlsnull191     fun testPanelDoesNotRefreshControls() {
192         val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
193         setUpPanel(panel)
194 
195         underTest.show(parent, {}, context)
196         verify(controlsController, never()).refreshStatus(any(), any())
197     }
198 
199     @Test
testPanelBindsForPanelnull200     fun testPanelBindsForPanel() {
201         val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
202         setUpPanel(panel)
203 
204         underTest.show(parent, {}, context)
205         verify(controlsController).bindComponentForPanel(panel.componentName)
206     }
207 
208     @Test
testPanelCallsTaskViewFactoryCreatenull209     fun testPanelCallsTaskViewFactoryCreate() {
210         mockLayoutInflater()
211         val packageName = "pkg"
212         `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
213         val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
214         val serviceInfo = setUpPanel(panel)
215 
216         underTest.show(parent, {}, context)
217 
218         val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
219 
220         verify(controlsListingController).addCallback(capture(captor))
221 
222         captor.value.onServicesUpdated(listOf(serviceInfo))
223         FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
224 
225         verify(taskViewFactory).create(eq(context), eq(uiExecutor), any())
226     }
227 
228     @Test
testSingleAppHeaderIsNotClickablenull229     fun testSingleAppHeaderIsNotClickable() {
230         mockLayoutInflater()
231         val packageName = "pkg"
232         `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
233         val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
234         val serviceInfo = setUpPanel(panel)
235 
236         underTest.show(parent, {}, context)
237 
238         val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
239 
240         verify(controlsListingController).addCallback(capture(captor))
241 
242         captor.value.onServicesUpdated(listOf(serviceInfo))
243         FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
244 
245         val header: View = parent.requireViewById(R.id.controls_header)
246         assertThat(header.isClickable).isFalse()
247         assertThat(header.hasOnClickListeners()).isFalse()
248     }
249 
250     @Test
testMultipleAppHeaderIsClickablenull251     fun testMultipleAppHeaderIsClickable() {
252         mockLayoutInflater()
253         val packageName1 = "pkg"
254         val panel1 = SelectedItem.PanelItem("App name 1", ComponentName(packageName1, "cls"))
255         val serviceInfo1 = setUpPanel(panel1)
256 
257         val packageName2 = "pkg"
258         val panel2 = SelectedItem.PanelItem("App name 2", ComponentName(packageName2, "cls"))
259         val serviceInfo2 = setUpPanel(panel2)
260 
261         `when`(authorizedPanelsRepository.getAuthorizedPanels())
262             .thenReturn(setOf(packageName1, packageName2))
263 
264         underTest.show(parent, {}, context)
265 
266         val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
267 
268         verify(controlsListingController).addCallback(capture(captor))
269 
270         captor.value.onServicesUpdated(listOf(serviceInfo1, serviceInfo2))
271         FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
272 
273         val header: View = parent.requireViewById(R.id.app_or_structure_spinner)
274         assertThat(header.isClickable).isTrue()
275         assertThat(header.hasOnClickListeners()).isTrue()
276     }
277 
278     @Test
testPanelControllerStartActivityWithCorrectArgumentsnull279     fun testPanelControllerStartActivityWithCorrectArguments() {
280         mSetFlagsRule.disableFlags(FLAG_HOME_PANEL_DREAM)
281         mockLayoutInflater()
282         val packageName = "pkg"
283         `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
284         controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
285 
286         val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
287         val serviceInfo = setUpPanel(panel)
288 
289         underTest.show(parent, {}, context)
290 
291         val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
292 
293         verify(controlsListingController).addCallback(capture(captor))
294 
295         captor.value.onServicesUpdated(listOf(serviceInfo))
296         FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
297 
298         val pendingIntent = verifyPanelCreatedAndStartTaskView()
299 
300         with(pendingIntent) {
301             assertThat(isActivity).isTrue()
302             assertThat(intent.component).isEqualTo(serviceInfo.panelActivity)
303             assertThat(
304                     intent.getBooleanExtra(
305                         ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
306                         false
307                     )
308                 )
309                 .isTrue()
310             // We should not include controls surface extra if the home panel dream flag is off.
311             assertThat(intent.getIntExtra(ControlsProviderService.EXTRA_CONTROLS_SURFACE, -10))
312                 .isEqualTo(-10)
313         }
314     }
315 
316     @Test
testPanelControllerStartActivityWithHomePanelDreamEnablednull317     fun testPanelControllerStartActivityWithHomePanelDreamEnabled() {
318         mSetFlagsRule.enableFlags(FLAG_HOME_PANEL_DREAM)
319         mockLayoutInflater()
320         val packageName = "pkg"
321         `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
322         controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
323 
324         val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
325         val serviceInfo = setUpPanel(panel)
326 
327         underTest.show(parent, {}, context)
328 
329         val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
330 
331         verify(controlsListingController).addCallback(capture(captor))
332 
333         captor.value.onServicesUpdated(listOf(serviceInfo))
334         FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
335 
336         val pendingIntent = verifyPanelCreatedAndStartTaskView()
337 
338         with(pendingIntent) {
339             assertThat(isActivity).isTrue()
340             assertThat(intent.component).isEqualTo(serviceInfo.panelActivity)
341             assertThat(
342                     intent.getBooleanExtra(
343                         ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
344                         false
345                     )
346                 )
347                 .isTrue()
348             // We should not include controls surface extra if the home panel dream flag is off.
349             assertThat(intent.getIntExtra(ControlsProviderService.EXTRA_CONTROLS_SURFACE, -10))
350                 .isEqualTo(ControlsProviderService.CONTROLS_SURFACE_ACTIVITY_PANEL)
351         }
352     }
353 
354     @Test
testPendingIntentExtrasAreModifiednull355     fun testPendingIntentExtrasAreModified() {
356         mockLayoutInflater()
357         val packageName = "pkg"
358         `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
359         controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
360 
361         val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
362         val serviceInfo = setUpPanel(panel)
363 
364         underTest.show(parent, {}, context)
365 
366         val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
367 
368         verify(controlsListingController).addCallback(capture(captor))
369 
370         captor.value.onServicesUpdated(listOf(serviceInfo))
371         FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
372 
373         val pendingIntent = verifyPanelCreatedAndStartTaskView()
374         assertThat(
375                 pendingIntent.intent.getBooleanExtra(
376                     ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
377                     false
378                 )
379             )
380             .isTrue()
381 
382         underTest.hide(parent)
383 
384         clearInvocations(controlsListingController, taskViewFactory)
385         controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(false)
386         underTest.show(parent, {}, context)
387 
388         verify(controlsListingController).addCallback(capture(captor))
389         captor.value.onServicesUpdated(listOf(serviceInfo))
390         FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
391 
392         val newPendingIntent = verifyPanelCreatedAndStartTaskView()
393         assertThat(
394                 newPendingIntent.intent.getBooleanExtra(
395                     ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
396                     false
397                 )
398             )
399             .isFalse()
400     }
401 
402     @Test
testResolveActivityWhileSeeding_ControlsActivitynull403     fun testResolveActivityWhileSeeding_ControlsActivity() {
404         whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(true)
405         assertThat(underTest.resolveActivity()).isEqualTo(ControlsActivity::class.java)
406     }
407 
408     @Test
testResolveActivityNotSeedingNoFavoritesNoPanels_ControlsProviderSelectorActivitynull409     fun testResolveActivityNotSeedingNoFavoritesNoPanels_ControlsProviderSelectorActivity() {
410         whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(false)
411         whenever(controlsController.getFavorites()).thenReturn(emptyList())
412 
413         val selectedItems =
414             listOf(
415                 SelectedItem.StructureItem(
416                     StructureInfo(
417                         checkNotNull(ComponentName.unflattenFromString("pkg/.cls1")),
418                         "a",
419                         ArrayList()
420                     )
421                 ),
422             )
423         preferredPanelRepository.setSelectedComponent(
424             SelectedComponentRepository.SelectedComponent(selectedItems[0])
425         )
426 
427         assertThat(underTest.resolveActivity())
428             .isEqualTo(ControlsProviderSelectorActivity::class.java)
429     }
430 
431     @Test
testResolveActivityNotSeedingNoDefaultNoFavoritesPanel_ControlsActivitynull432     fun testResolveActivityNotSeedingNoDefaultNoFavoritesPanel_ControlsActivity() {
433         val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
434         val activity = ComponentName("pkg", "activity")
435         val csi = ControlsServiceInfo(panel.componentName, panel.appName, activity)
436         whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(true)
437         whenever(controlsController.getFavorites()).thenReturn(emptyList())
438         whenever(controlsListingController.getCurrentServices()).thenReturn(listOf(csi))
439 
440         assertThat(underTest.resolveActivity()).isEqualTo(ControlsActivity::class.java)
441     }
442 
443     @Test
testRemoveViewsOnlyForParentPassedInHidenull444     fun testRemoveViewsOnlyForParentPassedInHide() {
445         underTest.show(parent, {}, context)
446         parent.addView(View(context))
447 
448         val mockParent: ViewGroup = mock()
449 
450         underTest.hide(mockParent)
451 
452         verify(mockParent).removeAllViews()
453         assertThat(parent.childCount).isGreaterThan(0)
454     }
455 
456     @Test
testHideDifferentParentDoesntCancelListenersnull457     fun testHideDifferentParentDoesntCancelListeners() {
458         underTest.show(parent, {}, context)
459         underTest.hide(mock())
460 
461         verify(controlsController, never()).unsubscribe()
462         verify(controlsListingController, never()).removeCallback(any())
463     }
464 
465     @Test
testRemovingAppsRemovesFavoritenull466     fun testRemovingAppsRemovesFavorite() {
467         val componentName = ComponentName(context, "cls")
468         whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
469         val panel = SelectedItem.PanelItem("App name", componentName)
470         preferredPanelRepository.setSelectedComponent(
471             SelectedComponentRepository.SelectedComponent(panel)
472         )
473         underTest.show(parent, {}, context)
474         underTest.startRemovingApp(componentName, "Test App")
475 
476         fakeDialogController.clickPositive()
477 
478         verify(controlsController).removeFavorites(eq(componentName))
479         assertThat(underTest.getPreferredSelectedItem(emptyList()))
480             .isEqualTo(SelectedItem.EMPTY_SELECTION)
481         assertThat(preferredPanelRepository.shouldAddDefaultComponent()).isFalse()
482         assertThat(preferredPanelRepository.getSelectedComponent()).isNull()
483     }
484 
485     @Test
testKeyguardRemovingAppsNotShowingDialognull486     fun testKeyguardRemovingAppsNotShowingDialog() {
487         isKeyguardDismissed = false
488         val componentName = ComponentName(context, "cls")
489         whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
490         val panel = SelectedItem.PanelItem("App name", componentName)
491         preferredPanelRepository.setSelectedComponent(
492             SelectedComponentRepository.SelectedComponent(panel)
493         )
494         underTest.show(parent, {}, context)
495         underTest.startRemovingApp(componentName, "Test App")
496 
497         assertThat(isRemoveAppDialogCreated).isFalse()
498         verify(controlsController, never()).removeFavorites(eq(componentName))
499         assertThat(underTest.getPreferredSelectedItem(emptyList())).isEqualTo(panel)
500         assertThat(preferredPanelRepository.shouldAddDefaultComponent()).isTrue()
501         assertThat(preferredPanelRepository.getSelectedComponent())
502             .isEqualTo(SelectedComponentRepository.SelectedComponent(panel))
503     }
504 
505     @Test
testCancelRemovingAppsDoesntRemoveFavoritenull506     fun testCancelRemovingAppsDoesntRemoveFavorite() {
507         val componentName = ComponentName(context, "cls")
508         whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
509         val panel = SelectedItem.PanelItem("App name", componentName)
510         preferredPanelRepository.setSelectedComponent(
511             SelectedComponentRepository.SelectedComponent(panel)
512         )
513         underTest.show(parent, {}, context)
514         underTest.startRemovingApp(componentName, "Test App")
515 
516         fakeDialogController.clickNeutral()
517 
518         verify(controlsController, never()).removeFavorites(eq(componentName))
519         assertThat(underTest.getPreferredSelectedItem(emptyList())).isEqualTo(panel)
520         assertThat(preferredPanelRepository.shouldAddDefaultComponent()).isTrue()
521         assertThat(preferredPanelRepository.getSelectedComponent())
522             .isEqualTo(SelectedComponentRepository.SelectedComponent(panel))
523     }
524 
525     @Test
testHideCancelsTheRemoveAppDialognull526     fun testHideCancelsTheRemoveAppDialog() {
527         val componentName = ComponentName(context, "cls")
528         underTest.show(parent, {}, context)
529         underTest.startRemovingApp(componentName, "Test App")
530 
531         underTest.hide(parent)
532 
533         verify(fakeDialogController.dialog).cancel()
534     }
535 
536     @Test
testOnRotationWithPanelUpdateBoundsCallednull537     fun testOnRotationWithPanelUpdateBoundsCalled() {
538         mockLayoutInflater()
539         val packageName = "pkg"
540         `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
541         val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
542         val serviceInfo = setUpPanel(panel)
543 
544         underTest.show(parent, {}, context)
545 
546         val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
547 
548         verify(controlsListingController).addCallback(capture(captor))
549         captor.value.onServicesUpdated(listOf(serviceInfo))
550         FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
551 
552         val taskViewConsumerCaptor = argumentCaptor<Consumer<TaskView>>()
553         verify(taskViewFactory).create(eq(context), eq(uiExecutor), capture(taskViewConsumerCaptor))
554 
555         val taskView: TaskView = mock {
556             `when`(this.post(any())).thenAnswer {
557                 uiExecutor.execute(it.arguments[0] as Runnable)
558                 true
559             }
560         }
561 
562         taskViewConsumerCaptor.value.accept(taskView)
563 
564         underTest.onSizeChange()
565         verify(taskView).onLocationChanged()
566     }
567 
setUpPanelnull568     private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
569         val activity = ComponentName(context, "activity")
570         preferredPanelRepository.setSelectedComponent(
571             SelectedComponentRepository.SelectedComponent(panel)
572         )
573         return ControlsServiceInfo(panel.componentName, panel.appName, activity)
574     }
575 
verifyPanelCreatedAndStartTaskViewnull576     private fun verifyPanelCreatedAndStartTaskView(): PendingIntent {
577         val taskViewConsumerCaptor = argumentCaptor<Consumer<TaskView>>()
578         verify(taskViewFactory).create(eq(context), eq(uiExecutor), capture(taskViewConsumerCaptor))
579 
580         val taskView: TaskView = mock {
581             `when`(this.post(any())).thenAnswer {
582                 uiExecutor.execute(it.arguments[0] as Runnable)
583                 true
584             }
585         }
586         // calls PanelTaskViewController#launchTaskView
587         taskViewConsumerCaptor.value.accept(taskView)
588         val listenerCaptor = argumentCaptor<TaskView.Listener>()
589         verify(taskView).setListener(any(), capture(listenerCaptor))
590         listenerCaptor.value.onInitialized()
591         FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
592 
593         val pendingIntentCaptor = argumentCaptor<PendingIntent>()
594         verify(taskView).startActivity(capture(pendingIntentCaptor), any(), any(), any())
595         return pendingIntentCaptor.value
596     }
597 
ControlsServiceInfonull598     private fun ControlsServiceInfo(
599         componentName: ComponentName,
600         label: CharSequence,
601         panelComponentName: ComponentName? = null
602     ): ControlsServiceInfo {
603         val serviceInfo =
604             ServiceInfo().apply {
605                 applicationInfo = ApplicationInfo()
606                 packageName = componentName.packageName
607                 name = componentName.className
608             }
609         return spy(ControlsServiceInfo(mContext, serviceInfo)).apply {
610             doReturn(label).whenever(this).loadLabel()
611             doReturn(mock<Drawable>()).whenever(this).loadIcon()
612             doReturn(panelComponentName).whenever(this).panelActivity
613         }
614     }
615 
mockLayoutInflaternull616     private fun mockLayoutInflater() {
617         LayoutInflater.from(context)
618             .setPrivateFactory(
619                 object : LayoutInflater.Factory2 {
620                     override fun onCreateView(
621                         view: View?,
622                         name: String,
623                         context: Context,
624                         attrs: AttributeSet
625                     ): View? {
626                         return onCreateView(name, context, attrs)
627                     }
628 
629                     override fun onCreateView(
630                         name: String,
631                         context: Context,
632                         attrs: AttributeSet
633                     ): View? {
634                         if (FrameLayout::class.java.simpleName.equals(name)) {
635                             val mock: FrameLayout = mock {
636                                 `when`(this.context).thenReturn(context)
637                                 `when`(this.id).thenReturn(R.id.controls_panel)
638                                 `when`(this.requireViewById<View>(any())).thenCallRealMethod()
639                                 `when`(this.findViewById<View>(R.id.controls_panel))
640                                     .thenReturn(this)
641                                 `when`(this.post(any())).thenAnswer {
642                                     uiExecutor.execute(it.arguments[0] as Runnable)
643                                     true
644                                 }
645                             }
646                             return mock
647                         } else {
648                             return null
649                         }
650                     }
651                 }
652             )
653     }
654 }
655