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