1 /* 2 * Copyright (C) 2020 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.management 18 19 import android.Manifest 20 import android.content.ComponentName 21 import android.content.Context 22 import android.content.ContextWrapper 23 import android.content.Intent 24 import android.content.pm.ActivityInfo 25 import android.content.pm.PackageManager 26 import android.content.pm.ResolveInfo 27 import android.content.pm.ServiceInfo 28 import android.os.Bundle 29 import android.os.UserHandle 30 import android.service.controls.ControlsProviderService 31 import android.testing.AndroidTestingRunner 32 import androidx.test.filters.SmallTest 33 import com.android.settingslib.applications.ServiceListing 34 import com.android.systemui.R 35 import com.android.systemui.SysuiTestCase 36 import com.android.systemui.controls.ControlsServiceInfo 37 import com.android.systemui.dump.DumpManager 38 import com.android.systemui.flags.FeatureFlags 39 import com.android.systemui.flags.Flags.APP_PANELS_ALL_APPS_ALLOWED 40 import com.android.systemui.settings.UserTracker 41 import com.android.systemui.util.ActivityTaskManagerProxy 42 import com.android.systemui.util.concurrency.FakeExecutor 43 import com.android.systemui.util.mockito.any 44 import com.android.systemui.util.mockito.argThat 45 import com.android.systemui.util.mockito.capture 46 import com.android.systemui.util.mockito.eq 47 import com.android.systemui.util.time.FakeSystemClock 48 import com.google.common.truth.Truth.assertThat 49 import java.util.concurrent.Executor 50 import org.junit.After 51 import org.junit.Assert.assertEquals 52 import org.junit.Assert.assertNull 53 import org.junit.Assert.assertTrue 54 import org.junit.Before 55 import org.junit.Test 56 import org.junit.runner.RunWith 57 import org.mockito.ArgumentCaptor 58 import org.mockito.ArgumentMatcher 59 import org.mockito.Mock 60 import org.mockito.Mockito.`when` 61 import org.mockito.Mockito.inOrder 62 import org.mockito.Mockito.mock 63 import org.mockito.Mockito.never 64 import org.mockito.Mockito.reset 65 import org.mockito.Mockito.verify 66 import org.mockito.MockitoAnnotations 67 68 @SmallTest 69 @RunWith(AndroidTestingRunner::class) 70 class ControlsListingControllerImplTest : SysuiTestCase() { 71 72 companion object { 73 private const val FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or 74 PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong() 75 } 76 77 @Mock 78 private lateinit var mockSL: ServiceListing 79 @Mock 80 private lateinit var mockCallback: ControlsListingController.ControlsListingCallback 81 @Mock 82 private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback 83 @Mock(stubOnly = true) 84 private lateinit var userTracker: UserTracker 85 @Mock(stubOnly = true) 86 private lateinit var dumpManager: DumpManager 87 @Mock 88 private lateinit var packageManager: PackageManager 89 @Mock 90 private lateinit var featureFlags: FeatureFlags 91 @Mock 92 private lateinit var activityTaskManagerProxy: ActivityTaskManagerProxy 93 94 private var componentName = ComponentName("pkg", "class1") 95 private var activityName = ComponentName("pkg", "activity") 96 97 private val executor = FakeExecutor(FakeSystemClock()) 98 99 private lateinit var controller: ControlsListingControllerImpl 100 101 private var serviceListingCallbackCaptor = 102 ArgumentCaptor.forClass(ServiceListing.Callback::class.java) 103 104 private val user = mContext.userId 105 private val otherUser = user + 1 106 107 @Before 108 fun setUp() { 109 MockitoAnnotations.initMocks(this) 110 111 `when`(userTracker.userId).thenReturn(user) 112 `when`(userTracker.userHandle).thenReturn(UserHandle.of(user)) 113 `when`(userTracker.userContext).thenReturn(context) 114 // Return disabled by default 115 `when`(packageManager.getComponentEnabledSetting(any())) 116 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED) 117 `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(true) 118 mContext.setMockPackageManager(packageManager) 119 120 mContext.orCreateTestableResources 121 .addOverride( 122 R.array.config_controlsPreferredPackages, 123 arrayOf(componentName.packageName) 124 ) 125 126 // Return false by default, we'll test the true path 127 `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(false) 128 129 val wrapper = object : ContextWrapper(mContext) { 130 override fun createContextAsUser(user: UserHandle, flags: Int): Context { 131 return baseContext 132 } 133 } 134 135 controller = ControlsListingControllerImpl( 136 wrapper, 137 executor, 138 { mockSL }, 139 userTracker, 140 activityTaskManagerProxy, 141 dumpManager, 142 featureFlags 143 ) 144 verify(mockSL).addCallback(capture(serviceListingCallbackCaptor)) 145 } 146 147 @After 148 fun tearDown() { 149 executor.advanceClockToLast() 150 executor.runAllReady() 151 } 152 153 @Test 154 fun testInitialStateListening() { 155 verify(mockSL).setListening(true) 156 verify(mockSL).reload() 157 } 158 159 @Test 160 fun testImmediateListingReload_doesNotCrash() { 161 val exec = Executor { it.run() } 162 val mockServiceListing = mock(ServiceListing::class.java) 163 var callback: ServiceListing.Callback? = null 164 `when`(mockServiceListing.addCallback(any<ServiceListing.Callback>())).then { 165 callback = it.getArgument(0) 166 Unit 167 } 168 `when`(mockServiceListing.reload()).then { 169 callback?.onServicesReloaded(listOf(ServiceInfo(componentName))) 170 } 171 ControlsListingControllerImpl( 172 mContext, 173 exec, 174 { mockServiceListing }, 175 userTracker, 176 activityTaskManagerProxy, 177 dumpManager, 178 featureFlags 179 ) 180 } 181 182 @Test 183 fun testStartsOnUser() { 184 assertEquals(user, controller.currentUserId) 185 } 186 187 @Test 188 fun testCallbackCalledWhenAdded() { 189 controller.addCallback(mockCallback) 190 executor.runAllReady() 191 verify(mockCallback).onServicesUpdated(any()) 192 reset(mockCallback) 193 194 controller.addCallback(mockCallbackOther) 195 executor.runAllReady() 196 verify(mockCallbackOther).onServicesUpdated(any()) 197 verify(mockCallback, never()).onServicesUpdated(any()) 198 } 199 200 @Test 201 fun testCallbackGetsList() { 202 val list = listOf(ServiceInfo(componentName)) 203 controller.addCallback(mockCallback) 204 controller.addCallback(mockCallbackOther) 205 206 @Suppress("unchecked_cast") 207 val captor: ArgumentCaptor<List<ControlsServiceInfo>> = 208 ArgumentCaptor.forClass(List::class.java) 209 as ArgumentCaptor<List<ControlsServiceInfo>> 210 211 executor.runAllReady() 212 reset(mockCallback) 213 reset(mockCallbackOther) 214 215 serviceListingCallbackCaptor.value.onServicesReloaded(list) 216 217 executor.runAllReady() 218 verify(mockCallback).onServicesUpdated(capture(captor)) 219 assertEquals(1, captor.value.size) 220 assertEquals(componentName.flattenToString(), captor.value[0].key) 221 222 verify(mockCallbackOther).onServicesUpdated(capture(captor)) 223 assertEquals(1, captor.value.size) 224 assertEquals(componentName.flattenToString(), captor.value[0].key) 225 } 226 227 @Test 228 fun testChangeUser() { 229 controller.changeUser(UserHandle.of(otherUser)) 230 executor.runAllReady() 231 assertEquals(otherUser, controller.currentUserId) 232 233 val inOrder = inOrder(mockSL) 234 inOrder.verify(mockSL).setListening(false) 235 inOrder.verify(mockSL).addCallback(any()) // We add a callback because we replaced the SL 236 inOrder.verify(mockSL).setListening(true) 237 inOrder.verify(mockSL).reload() 238 } 239 240 @Test 241 fun testChangeUserSendsCorrectServiceUpdate() { 242 val serviceInfo = ServiceInfo(componentName) 243 244 val list = listOf(serviceInfo) 245 controller.addCallback(mockCallback) 246 247 @Suppress("unchecked_cast") 248 val captor: ArgumentCaptor<List<ControlsServiceInfo>> = 249 ArgumentCaptor.forClass(List::class.java) 250 as ArgumentCaptor<List<ControlsServiceInfo>> 251 executor.runAllReady() 252 reset(mockCallback) 253 254 serviceListingCallbackCaptor.value.onServicesReloaded(list) 255 256 executor.runAllReady() 257 verify(mockCallback).onServicesUpdated(capture(captor)) 258 assertEquals(1, captor.value.size) 259 260 reset(mockCallback) 261 reset(mockSL) 262 263 val updatedList = listOf(serviceInfo) 264 serviceListingCallbackCaptor.value.onServicesReloaded(updatedList) 265 controller.changeUser(UserHandle.of(otherUser)) 266 executor.runAllReady() 267 assertEquals(otherUser, controller.currentUserId) 268 269 // this event should was triggered just before the user change, and should 270 // be ignored 271 verify(mockCallback, never()).onServicesUpdated(any()) 272 273 serviceListingCallbackCaptor.value.onServicesReloaded(emptyList<ServiceInfo>()) 274 executor.runAllReady() 275 276 verify(mockCallback).onServicesUpdated(capture(captor)) 277 assertEquals(0, captor.value.size) 278 } 279 280 @Test 281 fun test_nullPanelActivity() { 282 val list = listOf(ServiceInfo(componentName)) 283 serviceListingCallbackCaptor.value.onServicesReloaded(list) 284 285 executor.runAllReady() 286 287 assertNull(controller.getCurrentServices()[0].panelActivity) 288 } 289 290 @Test 291 fun testNoActivity_nullPanel() { 292 val serviceInfo = ServiceInfo( 293 componentName, 294 activityName 295 ) 296 297 val list = listOf(serviceInfo) 298 serviceListingCallbackCaptor.value.onServicesReloaded(list) 299 300 executor.runAllReady() 301 302 assertNull(controller.getCurrentServices()[0].panelActivity) 303 } 304 305 @Test 306 fun testActivityWithoutPermission_nullPanel() { 307 val serviceInfo = ServiceInfo( 308 componentName, 309 activityName 310 ) 311 312 setUpQueryResult(listOf(ActivityInfo(activityName))) 313 314 val list = listOf(serviceInfo) 315 serviceListingCallbackCaptor.value.onServicesReloaded(list) 316 317 executor.runAllReady() 318 319 assertNull(controller.getCurrentServices()[0].panelActivity) 320 } 321 322 @Test 323 fun testActivityPermissionNotExported_nullPanel() { 324 val serviceInfo = ServiceInfo( 325 componentName, 326 activityName 327 ) 328 329 setUpQueryResult(listOf( 330 ActivityInfo(activityName, permission = Manifest.permission.BIND_CONTROLS) 331 )) 332 333 val list = listOf(serviceInfo) 334 serviceListingCallbackCaptor.value.onServicesReloaded(list) 335 336 executor.runAllReady() 337 338 assertNull(controller.getCurrentServices()[0].panelActivity) 339 } 340 341 @Test 342 fun testActivityDisabled_nullPanel() { 343 val serviceInfo = ServiceInfo( 344 componentName, 345 activityName 346 ) 347 348 setUpQueryResult(listOf( 349 ActivityInfo( 350 activityName, 351 exported = true, 352 permission = Manifest.permission.BIND_CONTROLS 353 ) 354 )) 355 356 val list = listOf(serviceInfo) 357 serviceListingCallbackCaptor.value.onServicesReloaded(list) 358 359 executor.runAllReady() 360 361 assertNull(controller.getCurrentServices()[0].panelActivity) 362 } 363 364 @Test 365 fun testActivityEnabled_correctPanel() { 366 val serviceInfo = ServiceInfo( 367 componentName, 368 activityName 369 ) 370 371 `when`(packageManager.getComponentEnabledSetting(eq(activityName))) 372 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED) 373 374 setUpQueryResult(listOf( 375 ActivityInfo( 376 activityName, 377 exported = true, 378 permission = Manifest.permission.BIND_CONTROLS 379 ) 380 )) 381 382 val list = listOf(serviceInfo) 383 serviceListingCallbackCaptor.value.onServicesReloaded(list) 384 385 executor.runAllReady() 386 387 assertEquals(activityName, controller.getCurrentServices()[0].panelActivity) 388 } 389 390 @Test 391 fun testActivityDefaultEnabled_correctPanel() { 392 val serviceInfo = ServiceInfo( 393 componentName, 394 activityName 395 ) 396 397 `when`(packageManager.getComponentEnabledSetting(eq(activityName))) 398 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) 399 400 setUpQueryResult(listOf( 401 ActivityInfo( 402 activityName, 403 enabled = true, 404 exported = true, 405 permission = Manifest.permission.BIND_CONTROLS 406 ) 407 )) 408 409 val list = listOf(serviceInfo) 410 serviceListingCallbackCaptor.value.onServicesReloaded(list) 411 412 executor.runAllReady() 413 414 assertEquals(activityName, controller.getCurrentServices()[0].panelActivity) 415 } 416 417 @Test 418 fun testActivityDefaultDisabled_nullPanel() { 419 val serviceInfo = ServiceInfo( 420 componentName, 421 activityName 422 ) 423 424 `when`(packageManager.getComponentEnabledSetting(eq(activityName))) 425 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) 426 427 setUpQueryResult(listOf( 428 ActivityInfo( 429 activityName, 430 enabled = false, 431 exported = true, 432 permission = Manifest.permission.BIND_CONTROLS 433 ) 434 )) 435 436 val list = listOf(serviceInfo) 437 serviceListingCallbackCaptor.value.onServicesReloaded(list) 438 439 executor.runAllReady() 440 441 assertNull(controller.getCurrentServices()[0].panelActivity) 442 } 443 444 @Test 445 fun testActivityDifferentPackage_nullPanel() { 446 val serviceInfo = ServiceInfo( 447 componentName, 448 ComponentName("other_package", "cls") 449 ) 450 451 `when`(packageManager.getComponentEnabledSetting(eq(activityName))) 452 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) 453 454 setUpQueryResult(listOf( 455 ActivityInfo( 456 activityName, 457 enabled = true, 458 exported = true, 459 permission = Manifest.permission.BIND_CONTROLS 460 ) 461 )) 462 463 val list = listOf(serviceInfo) 464 serviceListingCallbackCaptor.value.onServicesReloaded(list) 465 466 executor.runAllReady() 467 468 assertNull(controller.getCurrentServices()[0].panelActivity) 469 } 470 471 @Test 472 fun testPackageNotPreferred_nullPanel() { 473 mContext.orCreateTestableResources 474 .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>()) 475 476 val serviceInfo = ServiceInfo( 477 componentName, 478 activityName 479 ) 480 481 `when`(packageManager.getComponentEnabledSetting(eq(activityName))) 482 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED) 483 484 setUpQueryResult(listOf( 485 ActivityInfo( 486 activityName, 487 exported = true, 488 permission = Manifest.permission.BIND_CONTROLS 489 ) 490 )) 491 492 val list = listOf(serviceInfo) 493 serviceListingCallbackCaptor.value.onServicesReloaded(list) 494 495 executor.runAllReady() 496 497 assertNull(controller.getCurrentServices()[0].panelActivity) 498 } 499 500 @Test 501 fun testPackageNotPreferred_allowAllApps_correctPanel() { 502 `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(true) 503 504 mContext.orCreateTestableResources 505 .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>()) 506 507 val serviceInfo = ServiceInfo( 508 componentName, 509 activityName 510 ) 511 512 `when`(packageManager.getComponentEnabledSetting(eq(activityName))) 513 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED) 514 515 setUpQueryResult(listOf( 516 ActivityInfo( 517 activityName, 518 exported = true, 519 permission = Manifest.permission.BIND_CONTROLS 520 ) 521 )) 522 523 val list = listOf(serviceInfo) 524 serviceListingCallbackCaptor.value.onServicesReloaded(list) 525 526 executor.runAllReady() 527 528 assertEquals(activityName, controller.getCurrentServices()[0].panelActivity) 529 } 530 531 @Test 532 fun testListingsNotModifiedByCallback() { 533 // This test checks that if the list passed to the callback is modified, it has no effect 534 // in the resulting services 535 val list = mutableListOf<ServiceInfo>() 536 serviceListingCallbackCaptor.value.onServicesReloaded(list) 537 538 list.add(ServiceInfo(ComponentName("a", "b"))) 539 executor.runAllReady() 540 541 assertTrue(controller.getCurrentServices().isEmpty()) 542 } 543 544 @Test 545 fun testForceReloadQueriesPackageManager() { 546 val user = 10 547 `when`(userTracker.userHandle).thenReturn(UserHandle.of(user)) 548 549 controller.forceReload() 550 verify(packageManager).queryIntentServicesAsUser( 551 argThat(IntentMatcherAction(ControlsProviderService.SERVICE_CONTROLS)), 552 argThat(FlagsMatcher( 553 PackageManager.GET_META_DATA.toLong() or 554 PackageManager.GET_SERVICES.toLong() or 555 PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or 556 PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong() 557 )), 558 eq(UserHandle.of(user)) 559 ) 560 } 561 562 @Test 563 fun testForceReloadUpdatesList() { 564 val resolveInfo = ResolveInfo() 565 resolveInfo.serviceInfo = ServiceInfo(componentName) 566 567 `when`(packageManager.queryIntentServicesAsUser( 568 argThat(IntentMatcherAction(ControlsProviderService.SERVICE_CONTROLS)), 569 argThat(FlagsMatcher( 570 PackageManager.GET_META_DATA.toLong() or 571 PackageManager.GET_SERVICES.toLong() or 572 PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or 573 PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong() 574 )), 575 any<UserHandle>() 576 )).thenReturn(listOf(resolveInfo)) 577 578 controller.forceReload() 579 580 val services = controller.getCurrentServices() 581 assertThat(services.size).isEqualTo(1) 582 assertThat(services[0].serviceInfo.componentName).isEqualTo(componentName) 583 } 584 585 @Test 586 fun testForceReloadCallsListeners() { 587 controller.addCallback(mockCallback) 588 executor.runAllReady() 589 590 @Suppress("unchecked_cast") 591 val captor: ArgumentCaptor<List<ControlsServiceInfo>> = 592 ArgumentCaptor.forClass(List::class.java) 593 as ArgumentCaptor<List<ControlsServiceInfo>> 594 595 val resolveInfo = ResolveInfo() 596 resolveInfo.serviceInfo = ServiceInfo(componentName) 597 598 `when`(packageManager.queryIntentServicesAsUser( 599 any(), 600 any<PackageManager.ResolveInfoFlags>(), 601 any<UserHandle>() 602 )).thenReturn(listOf(resolveInfo)) 603 604 reset(mockCallback) 605 controller.forceReload() 606 607 verify(mockCallback).onServicesUpdated(capture(captor)) 608 609 val services = captor.value 610 611 assertThat(services.size).isEqualTo(1) 612 assertThat(services[0].serviceInfo.componentName).isEqualTo(componentName) 613 } 614 615 @Test 616 fun testNoPanelIfMultiWindowNotSupported() { 617 `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(false) 618 619 val serviceInfo = ServiceInfo( 620 componentName, 621 activityName 622 ) 623 624 `when`(packageManager.getComponentEnabledSetting(eq(activityName))) 625 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) 626 627 setUpQueryResult(listOf( 628 ActivityInfo( 629 activityName, 630 enabled = true, 631 exported = true, 632 permission = Manifest.permission.BIND_CONTROLS 633 ) 634 )) 635 636 val list = listOf(serviceInfo) 637 serviceListingCallbackCaptor.value.onServicesReloaded(list) 638 639 executor.runAllReady() 640 641 assertNull(controller.getCurrentServices()[0].panelActivity) 642 } 643 644 private fun ServiceInfo( 645 componentName: ComponentName, 646 panelActivityComponentName: ComponentName? = null 647 ): ServiceInfo { 648 return ServiceInfo().apply { 649 packageName = componentName.packageName 650 name = componentName.className 651 panelActivityComponentName?.let { 652 metaData = Bundle().apply { 653 putString( 654 ControlsProviderService.META_DATA_PANEL_ACTIVITY, 655 it.flattenToShortString() 656 ) 657 } 658 } 659 } 660 } 661 662 private fun ActivityInfo( 663 componentName: ComponentName, 664 exported: Boolean = false, 665 enabled: Boolean = true, 666 permission: String? = null 667 ): ActivityInfo { 668 return ActivityInfo().apply { 669 packageName = componentName.packageName 670 name = componentName.className 671 this.permission = permission 672 this.exported = exported 673 this.enabled = enabled 674 } 675 } 676 677 private fun setUpQueryResult(infos: List<ActivityInfo>) { 678 `when`( 679 packageManager.queryIntentActivitiesAsUser( 680 argThat(IntentMatcherComponent(activityName)), 681 argThat(FlagsMatcher(FLAGS)), 682 eq(UserHandle.of(user)) 683 ) 684 ).thenReturn(infos.map { 685 ResolveInfo().apply { activityInfo = it } 686 }) 687 } 688 689 private class IntentMatcherComponent( 690 private val componentName: ComponentName 691 ) : ArgumentMatcher<Intent> { 692 override fun matches(argument: Intent?): Boolean { 693 return argument?.component == componentName 694 } 695 } 696 697 private class IntentMatcherAction( 698 private val action: String 699 ) : ArgumentMatcher<Intent> { 700 override fun matches(argument: Intent?): Boolean { 701 return argument?.action == action 702 } 703 } 704 705 private class FlagsMatcher( 706 private val flags: Long 707 ) : ArgumentMatcher<PackageManager.ResolveInfoFlags> { 708 override fun matches(argument: PackageManager.ResolveInfoFlags?): Boolean { 709 return flags == argument?.value 710 } 711 } 712 } 713