1 /* 2 * Copyright (C) 2023 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 18 package com.android.systemui.controls.start 19 20 import android.content.BroadcastReceiver 21 import android.content.ComponentName 22 import android.content.Context 23 import android.content.Intent 24 import android.content.IntentFilter 25 import android.content.pm.ApplicationInfo 26 import android.content.pm.ServiceInfo 27 import android.os.UserHandle 28 import android.os.UserManager 29 import androidx.test.ext.junit.runners.AndroidJUnit4 30 import androidx.test.filters.SmallTest 31 import com.android.systemui.SysuiTestCase 32 import com.android.systemui.broadcast.BroadcastDispatcher 33 import com.android.systemui.common.data.repository.fakePackageChangeRepository 34 import com.android.systemui.common.domain.interactor.packageChangeInteractor 35 import com.android.systemui.common.shared.model.PackageChangeModel 36 import com.android.systemui.controls.ControlsServiceInfo 37 import com.android.systemui.controls.controller.ControlsController 38 import com.android.systemui.controls.dagger.ControlsComponent 39 import com.android.systemui.controls.management.ControlsListingController 40 import com.android.systemui.controls.panels.AuthorizedPanelsRepository 41 import com.android.systemui.controls.panels.SelectedComponentRepository 42 import com.android.systemui.controls.panels.selectedComponentRepository 43 import com.android.systemui.controls.ui.SelectedItem 44 import com.android.systemui.kosmos.applicationCoroutineScope 45 import com.android.systemui.kosmos.testDispatcher 46 import com.android.systemui.kosmos.testScope 47 import com.android.systemui.settings.UserTracker 48 import com.android.systemui.testKosmos 49 import com.android.systemui.util.concurrency.FakeExecutor 50 import com.android.systemui.util.mockito.any 51 import com.android.systemui.util.mockito.argumentCaptor 52 import com.android.systemui.util.mockito.capture 53 import com.android.systemui.util.mockito.eq 54 import com.android.systemui.util.mockito.mock 55 import com.android.systemui.util.mockito.nullable 56 import com.android.systemui.util.mockito.whenever 57 import com.android.systemui.util.time.FakeSystemClock 58 import com.google.common.truth.Truth.assertThat 59 import java.util.Optional 60 import kotlinx.coroutines.ExperimentalCoroutinesApi 61 import kotlinx.coroutines.test.runCurrent 62 import kotlinx.coroutines.test.runTest 63 import org.junit.Before 64 import org.junit.Test 65 import org.junit.runner.RunWith 66 import org.mockito.Mock 67 import org.mockito.Mockito.anyInt 68 import org.mockito.Mockito.doAnswer 69 import org.mockito.Mockito.doReturn 70 import org.mockito.Mockito.never 71 import org.mockito.Mockito.verify 72 import org.mockito.Mockito.verifyZeroInteractions 73 import org.mockito.Mockito.`when` 74 import org.mockito.MockitoAnnotations 75 76 @OptIn(ExperimentalCoroutinesApi::class) 77 @SmallTest 78 @RunWith(AndroidJUnit4::class) 79 class ControlsStartableTest : SysuiTestCase() { 80 81 private val kosmos = testKosmos() 82 83 @Mock private lateinit var controlsController: ControlsController 84 @Mock private lateinit var controlsListingController: ControlsListingController 85 @Mock private lateinit var userTracker: UserTracker 86 @Mock private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository 87 @Mock private lateinit var userManager: UserManager 88 @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher 89 90 private lateinit var preferredPanelsRepository: SelectedComponentRepository 91 92 private lateinit var fakeExecutor: FakeExecutor 93 94 @Before setUpnull95 fun setUp() { 96 MockitoAnnotations.initMocks(this) 97 whenever(authorizedPanelsRepository.getPreferredPackages()).thenReturn(setOf()) 98 whenever(userManager.isUserUnlocked(anyInt())).thenReturn(true) 99 whenever(userTracker.userHandle).thenReturn(UserHandle.of(1)) 100 101 fakeExecutor = FakeExecutor(FakeSystemClock()) 102 preferredPanelsRepository = kosmos.selectedComponentRepository 103 } 104 105 @Test testDisabledNothingIsCallednull106 fun testDisabledNothingIsCalled() { 107 createStartable(enabled = false).apply { 108 start() 109 onBootCompleted() 110 } 111 112 verifyZeroInteractions(controlsController, controlsListingController, userTracker) 113 } 114 115 @Test testNothingCalledOnStartnull116 fun testNothingCalledOnStart() { 117 createStartable(enabled = true).start() 118 119 fakeExecutor.advanceClockToLast() 120 fakeExecutor.runAllReady() 121 122 verifyZeroInteractions(controlsController, controlsListingController, userTracker) 123 } 124 125 @Test testNoPreferredPackagesNoDefaultSelected_noNewSelectionnull126 fun testNoPreferredPackagesNoDefaultSelected_noNewSelection() { 127 `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION) 128 val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)) 129 setUpControlsListingControls(listings) 130 131 createStartable(enabled = true).onBootCompleted() 132 fakeExecutor.runAllReady() 133 134 verify(controlsController, never()).setPreferredSelection(any()) 135 } 136 137 @Test testPreferredPackagesNotInstalled_noNewSelectionnull138 fun testPreferredPackagesNotInstalled_noNewSelection() { 139 whenever(authorizedPanelsRepository.getPreferredPackages()) 140 .thenReturn(setOf(TEST_PACKAGE_PANEL)) 141 `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION) 142 setUpControlsListingControls(emptyList()) 143 144 createStartable(enabled = true).onBootCompleted() 145 fakeExecutor.runAllReady() 146 147 verify(controlsController, never()).setPreferredSelection(any()) 148 } 149 150 @Test testPreferredPackageNotPanel_noNewSelectionnull151 fun testPreferredPackageNotPanel_noNewSelection() { 152 whenever(authorizedPanelsRepository.getPreferredPackages()) 153 .thenReturn(setOf(TEST_PACKAGE_PANEL)) 154 `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION) 155 val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "not panel", hasPanel = false)) 156 setUpControlsListingControls(listings) 157 158 createStartable(enabled = true).onBootCompleted() 159 fakeExecutor.runAllReady() 160 161 verify(controlsController, never()).setPreferredSelection(any()) 162 } 163 164 @Test testExistingSelection_noNewSelectionnull165 fun testExistingSelection_noNewSelection() { 166 whenever(authorizedPanelsRepository.getPreferredPackages()) 167 .thenReturn(setOf(TEST_PACKAGE_PANEL)) 168 `when`(controlsController.getPreferredSelection()) 169 .thenReturn(mock<SelectedItem.PanelItem>()) 170 val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)) 171 setUpControlsListingControls(listings) 172 173 createStartable(enabled = true).onBootCompleted() 174 fakeExecutor.runAllReady() 175 176 verify(controlsController, never()).setPreferredSelection(any()) 177 } 178 179 @Test testPanelAddednull180 fun testPanelAdded() { 181 whenever(authorizedPanelsRepository.getPreferredPackages()) 182 .thenReturn(setOf(TEST_PACKAGE_PANEL)) 183 `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION) 184 val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)) 185 setUpControlsListingControls(listings) 186 187 createStartable(enabled = true).onBootCompleted() 188 fakeExecutor.runAllReady() 189 190 verify(controlsController).setPreferredSelection(listings[0].toPanelItem()) 191 } 192 193 @Test testMultiplePreferredOnlyOnePanel_panelAddednull194 fun testMultiplePreferredOnlyOnePanel_panelAdded() { 195 whenever(authorizedPanelsRepository.getPreferredPackages()) 196 .thenReturn(setOf(TEST_PACKAGE_PANEL)) 197 `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION) 198 val listings = 199 listOf( 200 ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true), 201 ControlsServiceInfo(ComponentName("other_package", "cls"), "non panel", false) 202 ) 203 setUpControlsListingControls(listings) 204 205 createStartable(enabled = true).onBootCompleted() 206 fakeExecutor.runAllReady() 207 208 verify(controlsController).setPreferredSelection(listings[0].toPanelItem()) 209 } 210 211 @Test testMultiplePreferredMultiplePanels_firstPreferredAddednull212 fun testMultiplePreferredMultiplePanels_firstPreferredAdded() { 213 whenever(authorizedPanelsRepository.getPreferredPackages()) 214 .thenReturn(setOf(TEST_PACKAGE_PANEL)) 215 `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION) 216 val listings = 217 listOf( 218 ControlsServiceInfo(ComponentName("other_package", "cls"), "panel", true), 219 ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true) 220 ) 221 setUpControlsListingControls(listings) 222 223 createStartable(enabled = true).onBootCompleted() 224 fakeExecutor.runAllReady() 225 226 verify(controlsController).setPreferredSelection(listings[1].toPanelItem()) 227 } 228 229 @Test testPreferredSelectionIsPanel_bindOnBootnull230 fun testPreferredSelectionIsPanel_bindOnBoot() { 231 val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)) 232 setUpControlsListingControls(listings) 233 `when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem()) 234 235 createStartable(enabled = true).onBootCompleted() 236 fakeExecutor.runAllReady() 237 238 verify(controlsController).bindComponentForPanel(TEST_COMPONENT_PANEL) 239 } 240 241 @Test testPreferredSelectionIsPanel_userNotUnlocked_notBindnull242 fun testPreferredSelectionIsPanel_userNotUnlocked_notBind() { 243 whenever(userManager.isUserUnlocked(anyInt())).thenReturn(false) 244 245 val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)) 246 setUpControlsListingControls(listings) 247 `when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem()) 248 249 createStartable(enabled = true).onBootCompleted() 250 fakeExecutor.runAllReady() 251 252 verify(controlsController, never()).bindComponentForPanel(TEST_COMPONENT_PANEL) 253 } 254 255 @Test testPreferredSelectionIsPanel_userNotUnlocked_broadcastRegistered_broadcastSentBindsnull256 fun testPreferredSelectionIsPanel_userNotUnlocked_broadcastRegistered_broadcastSentBinds() { 257 whenever(userManager.isUserUnlocked(anyInt())).thenReturn(false) 258 259 val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)) 260 setUpControlsListingControls(listings) 261 `when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem()) 262 263 createStartable(enabled = true).onBootCompleted() 264 fakeExecutor.runAllReady() 265 266 val intentFilterCaptor = argumentCaptor<IntentFilter>() 267 val receiverCaptor = argumentCaptor<BroadcastReceiver>() 268 269 verify(broadcastDispatcher) 270 .registerReceiver( 271 capture(receiverCaptor), 272 capture(intentFilterCaptor), 273 eq(fakeExecutor), 274 nullable(), 275 anyInt(), 276 nullable() 277 ) 278 assertThat(intentFilterCaptor.value.matchAction(Intent.ACTION_USER_UNLOCKED)).isTrue() 279 280 // User is unlocked 281 whenever(userManager.isUserUnlocked(anyInt())).thenReturn(true) 282 receiverCaptor.value.onReceive(mock(), Intent(Intent.ACTION_USER_UNLOCKED)) 283 284 verify(controlsController).bindComponentForPanel(TEST_COMPONENT_PANEL) 285 } 286 287 @Test testPreferredSelectionPanel_listingNoPanel_notBindnull288 fun testPreferredSelectionPanel_listingNoPanel_notBind() { 289 val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false)) 290 setUpControlsListingControls(listings) 291 `when`(controlsController.getPreferredSelection()) 292 .thenReturn(SelectedItem.PanelItem("panel", TEST_COMPONENT_PANEL)) 293 294 createStartable(enabled = true).onBootCompleted() 295 fakeExecutor.runAllReady() 296 297 verify(controlsController, never()).bindComponentForPanel(any()) 298 } 299 300 @Test testNotPanelSelection_noBindnull301 fun testNotPanelSelection_noBind() { 302 val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false)) 303 setUpControlsListingControls(listings) 304 `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION) 305 306 createStartable(enabled = true).onBootCompleted() 307 fakeExecutor.runAllReady() 308 309 verify(controlsController, never()).bindComponentForPanel(any()) 310 } 311 312 @Test testAlreadyAddedPanel_noNewSelectionnull313 fun testAlreadyAddedPanel_noNewSelection() { 314 preferredPanelsRepository.setShouldAddDefaultComponent(false) 315 whenever(authorizedPanelsRepository.getPreferredPackages()) 316 .thenReturn(setOf(TEST_PACKAGE_PANEL)) 317 `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION) 318 val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)) 319 `when`(controlsListingController.getCurrentServices()).thenReturn(listings) 320 321 createStartable(enabled = true).onBootCompleted() 322 323 verify(controlsController, never()).setPreferredSelection(any()) 324 } 325 326 @Test testSelectedComponentIsUninstallednull327 fun testSelectedComponentIsUninstalled() = 328 with(kosmos) { 329 testScope.runTest { 330 val selectedComponent = 331 SelectedComponentRepository.SelectedComponent( 332 "panel", 333 TEST_COMPONENT_PANEL, 334 isPanel = true 335 ) 336 preferredPanelsRepository.setSelectedComponent(selectedComponent) 337 val activeUser = UserHandle.of(100) 338 whenever(userTracker.userHandle).thenReturn(activeUser) 339 340 createStartable(enabled = true).onBootCompleted() 341 fakeExecutor.runAllReady() 342 runCurrent() 343 344 assertThat(preferredPanelsRepository.getSelectedComponent()) 345 .isEqualTo(selectedComponent) 346 fakePackageChangeRepository.notifyChange( 347 PackageChangeModel.Uninstalled( 348 packageName = TEST_PACKAGE_PANEL, 349 packageUid = UserHandle.getUid(100, 1) 350 ) 351 ) 352 runCurrent() 353 354 assertThat(preferredPanelsRepository.getSelectedComponent()).isNull() 355 } 356 } 357 358 @Test testSelectedComponentIsChangednull359 fun testSelectedComponentIsChanged() = 360 with(kosmos) { 361 testScope.runTest { 362 val selectedComponent = 363 SelectedComponentRepository.SelectedComponent( 364 "panel", 365 TEST_COMPONENT_PANEL, 366 isPanel = true 367 ) 368 preferredPanelsRepository.setSelectedComponent(selectedComponent) 369 val activeUser = UserHandle.of(100) 370 whenever(userTracker.userHandle).thenReturn(activeUser) 371 372 createStartable(enabled = true).onBootCompleted() 373 fakeExecutor.runAllReady() 374 runCurrent() 375 376 fakePackageChangeRepository.notifyChange( 377 PackageChangeModel.Changed( 378 packageName = TEST_PACKAGE_PANEL, 379 packageUid = UserHandle.getUid(100, 1) 380 ) 381 ) 382 runCurrent() 383 384 assertThat(preferredPanelsRepository.getSelectedComponent()) 385 .isEqualTo(selectedComponent) 386 } 387 } 388 389 @Test testOtherPackageIsUninstallednull390 fun testOtherPackageIsUninstalled() = 391 with(kosmos) { 392 testScope.runTest { 393 val selectedComponent = 394 SelectedComponentRepository.SelectedComponent( 395 "panel", 396 TEST_COMPONENT_PANEL, 397 isPanel = true 398 ) 399 preferredPanelsRepository.setSelectedComponent(selectedComponent) 400 val activeUser = UserHandle.of(100) 401 whenever(userTracker.userHandle).thenReturn(activeUser) 402 403 createStartable(enabled = true).onBootCompleted() 404 fakeExecutor.runAllReady() 405 runCurrent() 406 407 fakePackageChangeRepository.notifyChange( 408 PackageChangeModel.Uninstalled( 409 packageName = TEST_PACKAGE, 410 packageUid = UserHandle.getUid(100, 1) 411 ) 412 ) 413 runCurrent() 414 415 assertThat(preferredPanelsRepository.getSelectedComponent()) 416 .isEqualTo(selectedComponent) 417 } 418 } 419 setUpControlsListingControlsnull420 private fun setUpControlsListingControls(listings: List<ControlsServiceInfo>) { 421 doAnswer { doReturn(listings).`when`(controlsListingController).getCurrentServices() } 422 .`when`(controlsListingController) 423 .forceReload() 424 } 425 createStartablenull426 private fun createStartable(enabled: Boolean): ControlsStartable { 427 val component: ControlsComponent = 428 mock() { 429 `when`(isEnabled()).thenReturn(enabled) 430 if (enabled) { 431 `when`(getControlsController()).thenReturn(Optional.of(controlsController)) 432 `when`(getControlsListingController()) 433 .thenReturn(Optional.of(controlsListingController)) 434 } else { 435 `when`(getControlsController()).thenReturn(Optional.empty()) 436 `when`(getControlsListingController()).thenReturn(Optional.empty()) 437 } 438 } 439 return ControlsStartable( 440 kosmos.applicationCoroutineScope, 441 kosmos.testDispatcher, 442 fakeExecutor, 443 component, 444 userTracker, 445 authorizedPanelsRepository, 446 preferredPanelsRepository, 447 kosmos.packageChangeInteractor, 448 userManager, 449 broadcastDispatcher, 450 ) 451 } 452 ControlsServiceInfonull453 private fun ControlsServiceInfo( 454 componentName: ComponentName, 455 label: CharSequence, 456 hasPanel: Boolean 457 ): ControlsServiceInfo { 458 val serviceInfo = 459 ServiceInfo().apply { 460 applicationInfo = ApplicationInfo() 461 packageName = componentName.packageName 462 name = componentName.className 463 } 464 return FakeControlsServiceInfo(context, serviceInfo, label, hasPanel) 465 } 466 467 private class FakeControlsServiceInfo( 468 context: Context, 469 serviceInfo: ServiceInfo, 470 private val label: CharSequence, 471 hasPanel: Boolean 472 ) : ControlsServiceInfo(context, serviceInfo) { 473 474 init { 475 if (hasPanel) { 476 panelActivity = serviceInfo.componentName 477 } 478 } 479 loadLabelnull480 override fun loadLabel(): CharSequence { 481 return label 482 } 483 } 484 485 companion object { toPanelItemnull486 private fun ControlsServiceInfo.toPanelItem(): SelectedItem.PanelItem { 487 if (panelActivity == null) { 488 throw IllegalArgumentException("$this is not a panel") 489 } 490 return SelectedItem.PanelItem(loadLabel(), componentName) 491 } 492 493 private const val TEST_PACKAGE = "pkg" 494 private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service") 495 private const val TEST_PACKAGE_PANEL = "pkg.panel" 496 private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service") 497 } 498 } 499