Add code for collecting panel activity component
We collect the possible metadata from the ControlsProviderService and check whether it satisfies the requirements for the panel activity. Test: atest ControlsListingControllerImplTest Test: manual dump with mock app Fixes: 257283506 Change-Id: I61922e99dac81fccd5e3a00c10210966464678fe
This commit is contained in:
@@ -16,16 +16,120 @@
|
||||
|
||||
package com.android.systemui.controls
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE
|
||||
import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.UserHandle
|
||||
import android.service.controls.ControlsProviderService
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.android.settingslib.applications.DefaultAppInfo
|
||||
import java.util.Objects
|
||||
|
||||
class ControlsServiceInfo(
|
||||
context: Context,
|
||||
private val context: Context,
|
||||
val serviceInfo: ServiceInfo
|
||||
) : DefaultAppInfo(
|
||||
context,
|
||||
context.packageManager,
|
||||
context.userId,
|
||||
serviceInfo.componentName
|
||||
)
|
||||
) {
|
||||
private val _panelActivity: ComponentName?
|
||||
|
||||
init {
|
||||
val metadata = serviceInfo.metaData
|
||||
?.getString(ControlsProviderService.META_DATA_PANEL_ACTIVITY) ?: ""
|
||||
val unflatenned = ComponentName.unflattenFromString(metadata)
|
||||
if (unflatenned != null && unflatenned.packageName == componentName.packageName) {
|
||||
_panelActivity = unflatenned
|
||||
} else {
|
||||
_panelActivity = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component name of an activity that will be shown embedded in the device controls space
|
||||
* instead of using the controls rendered by SystemUI.
|
||||
*
|
||||
* The activity must be in the same package, exported, enabled and protected by the
|
||||
* [Manifest.permission.BIND_CONTROLS] permission.
|
||||
*/
|
||||
var panelActivity: ComponentName? = null
|
||||
private set
|
||||
|
||||
private var resolved: Boolean = false
|
||||
|
||||
@WorkerThread
|
||||
fun resolvePanelActivity() {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
panelActivity = _panelActivity?.let {
|
||||
val resolveInfos = mPm.queryIntentActivitiesAsUser(
|
||||
Intent().setComponent(it),
|
||||
PackageManager.ResolveInfoFlags.of(
|
||||
MATCH_DIRECT_BOOT_AWARE.toLong() or
|
||||
MATCH_DIRECT_BOOT_UNAWARE.toLong()
|
||||
),
|
||||
UserHandle.of(userId)
|
||||
)
|
||||
if (resolveInfos.isNotEmpty() && verifyResolveInfo(resolveInfos[0])) {
|
||||
it
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the panel activity is enabled, exported and protected by the correct
|
||||
* permission. This last check is to prevent apps from forgetting to protect the activity, as
|
||||
* they won't be able to see the panel until they do.
|
||||
*/
|
||||
@WorkerThread
|
||||
private fun verifyResolveInfo(resolveInfo: ResolveInfo): Boolean {
|
||||
return resolveInfo.activityInfo?.let {
|
||||
it.permission == Manifest.permission.BIND_CONTROLS &&
|
||||
it.exported && isComponentActuallyEnabled(it)
|
||||
} ?: false
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun isComponentActuallyEnabled(activityInfo: ActivityInfo): Boolean {
|
||||
return when (mPm.getComponentEnabledSetting(activityInfo.componentName)) {
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> activityInfo.enabled
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is ControlsServiceInfo &&
|
||||
userId == other.userId &&
|
||||
componentName == other.componentName &&
|
||||
panelActivity == other.panelActivity
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return Objects.hash(userId, componentName, panelActivity)
|
||||
}
|
||||
|
||||
fun copy(): ControlsServiceInfo {
|
||||
return ControlsServiceInfo(context, serviceInfo).also {
|
||||
it.panelActivity = this.panelActivity
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return """
|
||||
ControlsServiceInfo(serviceInfo=$serviceInfo, panelActivity=$panelActivity, resolved=$resolved)
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
@@ -18,17 +18,23 @@ package com.android.systemui.controls.management
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.UserHandle
|
||||
import android.service.controls.ControlsProviderService
|
||||
import android.util.Log
|
||||
import com.android.internal.annotations.VisibleForTesting
|
||||
import com.android.settingslib.applications.ServiceListing
|
||||
import com.android.settingslib.widget.CandidateInfo
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.controls.ControlsServiceInfo
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import com.android.systemui.util.asIndenting
|
||||
import com.android.systemui.util.indentIfPossible
|
||||
import java.io.PrintWriter
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import javax.inject.Inject
|
||||
@@ -57,16 +63,19 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
|
||||
private val context: Context,
|
||||
@Background private val backgroundExecutor: Executor,
|
||||
private val serviceListingBuilder: (Context) -> ServiceListing,
|
||||
userTracker: UserTracker
|
||||
) : ControlsListingController {
|
||||
private val userTracker: UserTracker,
|
||||
dumpManager: DumpManager,
|
||||
featureFlags: FeatureFlags
|
||||
) : ControlsListingController, Dumpable {
|
||||
|
||||
@Inject
|
||||
constructor(context: Context, executor: Executor, userTracker: UserTracker): this(
|
||||
context,
|
||||
executor,
|
||||
::createServiceListing,
|
||||
userTracker
|
||||
)
|
||||
constructor(
|
||||
context: Context,
|
||||
@Background executor: Executor,
|
||||
userTracker: UserTracker,
|
||||
dumpManager: DumpManager,
|
||||
featureFlags: FeatureFlags
|
||||
) : this(context, executor, ::createServiceListing, userTracker, dumpManager, featureFlags)
|
||||
|
||||
private var serviceListing = serviceListingBuilder(context)
|
||||
// All operations in background thread
|
||||
@@ -76,27 +85,25 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
|
||||
private const val TAG = "ControlsListingControllerImpl"
|
||||
}
|
||||
|
||||
private var availableComponents = emptySet<ComponentName>()
|
||||
private var availableServices = emptyList<ServiceInfo>()
|
||||
private var availableServices = emptyList<ControlsServiceInfo>()
|
||||
private var userChangeInProgress = AtomicInteger(0)
|
||||
|
||||
override var currentUserId = userTracker.userId
|
||||
private set
|
||||
|
||||
private val serviceListingCallback = ServiceListing.Callback {
|
||||
val newServices = it.toList()
|
||||
val newComponents =
|
||||
newServices.mapTo(mutableSetOf<ComponentName>(), { s -> s.getComponentName() })
|
||||
|
||||
backgroundExecutor.execute {
|
||||
if (userChangeInProgress.get() > 0) return@execute
|
||||
if (!newComponents.equals(availableComponents)) {
|
||||
Log.d(TAG, "ServiceConfig reloaded, count: ${newComponents.size}")
|
||||
availableComponents = newComponents
|
||||
Log.d(TAG, "ServiceConfig reloaded, count: ${it.size}")
|
||||
val newServices = it.map { ControlsServiceInfo(userTracker.userContext, it) }
|
||||
if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
|
||||
newServices.forEach(ControlsServiceInfo::resolvePanelActivity)
|
||||
}
|
||||
|
||||
if (newServices != availableServices) {
|
||||
availableServices = newServices
|
||||
val currentServices = getCurrentServices()
|
||||
callbacks.forEach {
|
||||
it.onServicesUpdated(currentServices)
|
||||
it.onServicesUpdated(getCurrentServices())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,6 +111,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
|
||||
|
||||
init {
|
||||
Log.d(TAG, "Initializing")
|
||||
dumpManager.registerDumpable(TAG, this)
|
||||
serviceListing.addCallback(serviceListingCallback)
|
||||
serviceListing.setListening(true)
|
||||
serviceListing.reload()
|
||||
@@ -165,7 +173,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
|
||||
* [ControlsProviderService]
|
||||
*/
|
||||
override fun getCurrentServices(): List<ControlsServiceInfo> =
|
||||
availableServices.map { ControlsServiceInfo(context, it) }
|
||||
availableServices.map(ControlsServiceInfo::copy)
|
||||
|
||||
/**
|
||||
* Get the localized label for the component.
|
||||
@@ -174,7 +182,15 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
|
||||
* @return a label as returned by [CandidateInfo.loadLabel] or `null`.
|
||||
*/
|
||||
override fun getAppLabel(name: ComponentName): CharSequence? {
|
||||
return getCurrentServices().firstOrNull { it.componentName == name }
|
||||
return availableServices.firstOrNull { it.componentName == name }
|
||||
?.loadLabel()
|
||||
}
|
||||
|
||||
override fun dump(writer: PrintWriter, args: Array<out String>) {
|
||||
writer.println("ControlsListingController:")
|
||||
writer.asIndenting().indentIfPossible {
|
||||
println("Callbacks: $callbacks")
|
||||
println("Services: ${getCurrentServices()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,27 +16,42 @@
|
||||
|
||||
package com.android.systemui.controls.management
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Bundle
|
||||
import android.os.UserHandle
|
||||
import android.service.controls.ControlsProviderService
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.settingslib.applications.ServiceListing
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.controls.ControlsServiceInfo
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags.USE_APP_PANELS
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.argThat
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatcher
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.inOrder
|
||||
import org.mockito.Mockito.mock
|
||||
@@ -51,10 +66,8 @@ import java.util.concurrent.Executor
|
||||
class ControlsListingControllerImplTest : SysuiTestCase() {
|
||||
|
||||
companion object {
|
||||
private const val TEST_LABEL = "TEST_LABEL"
|
||||
private const val TEST_PERMISSION = "permission"
|
||||
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
||||
fun <T> any(): T = Mockito.any<T>()
|
||||
private const val FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
|
||||
PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
|
||||
}
|
||||
|
||||
@Mock
|
||||
@@ -63,15 +76,17 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
||||
private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
|
||||
@Mock
|
||||
private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
|
||||
@Mock
|
||||
private lateinit var serviceInfo: ServiceInfo
|
||||
@Mock
|
||||
private lateinit var serviceInfo2: ServiceInfo
|
||||
@Mock(stubOnly = true)
|
||||
private lateinit var userTracker: UserTracker
|
||||
@Mock(stubOnly = true)
|
||||
private lateinit var dumpManager: DumpManager
|
||||
@Mock
|
||||
private lateinit var packageManager: PackageManager
|
||||
@Mock
|
||||
private lateinit var featureFlags: FeatureFlags
|
||||
|
||||
private var componentName = ComponentName("pkg1", "class1")
|
||||
private var componentName2 = ComponentName("pkg2", "class2")
|
||||
private var componentName = ComponentName("pkg", "class1")
|
||||
private var activityName = ComponentName("pkg", "activity")
|
||||
|
||||
private val executor = FakeExecutor(FakeSystemClock())
|
||||
|
||||
@@ -87,9 +102,15 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
`when`(serviceInfo.componentName).thenReturn(componentName)
|
||||
`when`(serviceInfo2.componentName).thenReturn(componentName2)
|
||||
`when`(userTracker.userId).thenReturn(user)
|
||||
`when`(userTracker.userContext).thenReturn(context)
|
||||
// Return disabled by default
|
||||
`when`(packageManager.getComponentEnabledSetting(any()))
|
||||
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
|
||||
mContext.setMockPackageManager(packageManager)
|
||||
|
||||
// Return true by default, we'll test the false path
|
||||
`when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(true)
|
||||
|
||||
val wrapper = object : ContextWrapper(mContext) {
|
||||
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
|
||||
@@ -97,7 +118,14 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
controller = ControlsListingControllerImpl(wrapper, executor, { mockSL }, userTracker)
|
||||
controller = ControlsListingControllerImpl(
|
||||
wrapper,
|
||||
executor,
|
||||
{ mockSL },
|
||||
userTracker,
|
||||
dumpManager,
|
||||
featureFlags
|
||||
)
|
||||
verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
|
||||
}
|
||||
|
||||
@@ -123,9 +151,16 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
||||
Unit
|
||||
}
|
||||
`when`(mockServiceListing.reload()).then {
|
||||
callback?.onServicesReloaded(listOf(serviceInfo))
|
||||
callback?.onServicesReloaded(listOf(ServiceInfo(componentName)))
|
||||
}
|
||||
ControlsListingControllerImpl(mContext, exec, { mockServiceListing }, userTracker)
|
||||
ControlsListingControllerImpl(
|
||||
mContext,
|
||||
exec,
|
||||
{ mockServiceListing },
|
||||
userTracker,
|
||||
dumpManager,
|
||||
featureFlags
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -148,7 +183,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testCallbackGetsList() {
|
||||
val list = listOf(serviceInfo)
|
||||
val list = listOf(ServiceInfo(componentName))
|
||||
controller.addCallback(mockCallback)
|
||||
controller.addCallback(mockCallbackOther)
|
||||
|
||||
@@ -188,6 +223,8 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testChangeUserSendsCorrectServiceUpdate() {
|
||||
val serviceInfo = ServiceInfo(componentName)
|
||||
|
||||
val list = listOf(serviceInfo)
|
||||
controller.addCallback(mockCallback)
|
||||
|
||||
@@ -223,4 +260,284 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
||||
verify(mockCallback).onServicesUpdated(capture(captor))
|
||||
assertEquals(0, captor.value.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_nullPanelActivity() {
|
||||
val list = listOf(ServiceInfo(componentName))
|
||||
serviceListingCallbackCaptor.value.onServicesReloaded(list)
|
||||
|
||||
executor.runAllReady()
|
||||
|
||||
assertNull(controller.getCurrentServices()[0].panelActivity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoActivity_nullPanel() {
|
||||
val serviceInfo = ServiceInfo(
|
||||
componentName,
|
||||
activityName
|
||||
)
|
||||
|
||||
val list = listOf(serviceInfo)
|
||||
serviceListingCallbackCaptor.value.onServicesReloaded(list)
|
||||
|
||||
executor.runAllReady()
|
||||
|
||||
assertNull(controller.getCurrentServices()[0].panelActivity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityWithoutPermission_nullPanel() {
|
||||
val serviceInfo = ServiceInfo(
|
||||
componentName,
|
||||
activityName
|
||||
)
|
||||
|
||||
setUpQueryResult(listOf(ActivityInfo(activityName)))
|
||||
|
||||
val list = listOf(serviceInfo)
|
||||
serviceListingCallbackCaptor.value.onServicesReloaded(list)
|
||||
|
||||
executor.runAllReady()
|
||||
|
||||
assertNull(controller.getCurrentServices()[0].panelActivity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityPermissionNotExported_nullPanel() {
|
||||
val serviceInfo = ServiceInfo(
|
||||
componentName,
|
||||
activityName
|
||||
)
|
||||
|
||||
setUpQueryResult(listOf(
|
||||
ActivityInfo(activityName, permission = Manifest.permission.BIND_CONTROLS)
|
||||
))
|
||||
|
||||
val list = listOf(serviceInfo)
|
||||
serviceListingCallbackCaptor.value.onServicesReloaded(list)
|
||||
|
||||
executor.runAllReady()
|
||||
|
||||
assertNull(controller.getCurrentServices()[0].panelActivity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityDisabled_nullPanel() {
|
||||
val serviceInfo = ServiceInfo(
|
||||
componentName,
|
||||
activityName
|
||||
)
|
||||
|
||||
setUpQueryResult(listOf(
|
||||
ActivityInfo(
|
||||
activityName,
|
||||
exported = true,
|
||||
permission = Manifest.permission.BIND_CONTROLS
|
||||
)
|
||||
))
|
||||
|
||||
val list = listOf(serviceInfo)
|
||||
serviceListingCallbackCaptor.value.onServicesReloaded(list)
|
||||
|
||||
executor.runAllReady()
|
||||
|
||||
assertNull(controller.getCurrentServices()[0].panelActivity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityEnabled_correctPanel() {
|
||||
val serviceInfo = ServiceInfo(
|
||||
componentName,
|
||||
activityName
|
||||
)
|
||||
|
||||
`when`(packageManager.getComponentEnabledSetting(eq(activityName)))
|
||||
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
|
||||
|
||||
setUpQueryResult(listOf(
|
||||
ActivityInfo(
|
||||
activityName,
|
||||
exported = true,
|
||||
permission = Manifest.permission.BIND_CONTROLS
|
||||
)
|
||||
))
|
||||
|
||||
val list = listOf(serviceInfo)
|
||||
serviceListingCallbackCaptor.value.onServicesReloaded(list)
|
||||
|
||||
executor.runAllReady()
|
||||
|
||||
assertEquals(activityName, controller.getCurrentServices()[0].panelActivity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityDefaultEnabled_correctPanel() {
|
||||
val serviceInfo = ServiceInfo(
|
||||
componentName,
|
||||
activityName
|
||||
)
|
||||
|
||||
`when`(packageManager.getComponentEnabledSetting(eq(activityName)))
|
||||
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
|
||||
|
||||
setUpQueryResult(listOf(
|
||||
ActivityInfo(
|
||||
activityName,
|
||||
enabled = true,
|
||||
exported = true,
|
||||
permission = Manifest.permission.BIND_CONTROLS
|
||||
)
|
||||
))
|
||||
|
||||
val list = listOf(serviceInfo)
|
||||
serviceListingCallbackCaptor.value.onServicesReloaded(list)
|
||||
|
||||
executor.runAllReady()
|
||||
|
||||
assertEquals(activityName, controller.getCurrentServices()[0].panelActivity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityDefaultDisabled_nullPanel() {
|
||||
val serviceInfo = ServiceInfo(
|
||||
componentName,
|
||||
activityName
|
||||
)
|
||||
|
||||
`when`(packageManager.getComponentEnabledSetting(eq(activityName)))
|
||||
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
|
||||
|
||||
setUpQueryResult(listOf(
|
||||
ActivityInfo(
|
||||
activityName,
|
||||
enabled = false,
|
||||
exported = true,
|
||||
permission = Manifest.permission.BIND_CONTROLS
|
||||
)
|
||||
))
|
||||
|
||||
val list = listOf(serviceInfo)
|
||||
serviceListingCallbackCaptor.value.onServicesReloaded(list)
|
||||
|
||||
executor.runAllReady()
|
||||
|
||||
assertNull(controller.getCurrentServices()[0].panelActivity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityDefaultEnabled_flagDisabled_nullPanel() {
|
||||
`when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(false)
|
||||
val serviceInfo = ServiceInfo(
|
||||
componentName,
|
||||
activityName,
|
||||
)
|
||||
|
||||
`when`(packageManager.getComponentEnabledSetting(eq(activityName)))
|
||||
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
|
||||
|
||||
setUpQueryResult(listOf(
|
||||
ActivityInfo(
|
||||
activityName,
|
||||
enabled = true,
|
||||
exported = true,
|
||||
permission = Manifest.permission.BIND_CONTROLS
|
||||
)
|
||||
))
|
||||
|
||||
val list = listOf(serviceInfo)
|
||||
serviceListingCallbackCaptor.value.onServicesReloaded(list)
|
||||
|
||||
executor.runAllReady()
|
||||
|
||||
assertNull(controller.getCurrentServices()[0].panelActivity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityDifferentPackage_nullPanel() {
|
||||
val serviceInfo = ServiceInfo(
|
||||
componentName,
|
||||
ComponentName("other_package", "cls")
|
||||
)
|
||||
|
||||
`when`(packageManager.getComponentEnabledSetting(eq(activityName)))
|
||||
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
|
||||
|
||||
setUpQueryResult(listOf(
|
||||
ActivityInfo(
|
||||
activityName,
|
||||
enabled = true,
|
||||
exported = true,
|
||||
permission = Manifest.permission.BIND_CONTROLS
|
||||
)
|
||||
))
|
||||
|
||||
val list = listOf(serviceInfo)
|
||||
serviceListingCallbackCaptor.value.onServicesReloaded(list)
|
||||
|
||||
executor.runAllReady()
|
||||
|
||||
assertNull(controller.getCurrentServices()[0].panelActivity)
|
||||
}
|
||||
|
||||
private fun ServiceInfo(
|
||||
componentName: ComponentName,
|
||||
panelActivityComponentName: ComponentName? = null
|
||||
): ServiceInfo {
|
||||
return ServiceInfo().apply {
|
||||
packageName = componentName.packageName
|
||||
name = componentName.className
|
||||
panelActivityComponentName?.let {
|
||||
metaData = Bundle().apply {
|
||||
putString(
|
||||
ControlsProviderService.META_DATA_PANEL_ACTIVITY,
|
||||
it.flattenToShortString()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ActivityInfo(
|
||||
componentName: ComponentName,
|
||||
exported: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
permission: String? = null
|
||||
): ActivityInfo {
|
||||
return ActivityInfo().apply {
|
||||
packageName = componentName.packageName
|
||||
name = componentName.className
|
||||
this.permission = permission
|
||||
this.exported = exported
|
||||
this.enabled = enabled
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpQueryResult(infos: List<ActivityInfo>) {
|
||||
`when`(
|
||||
packageManager.queryIntentActivitiesAsUser(
|
||||
argThat(IntentMatcher(activityName)),
|
||||
argThat(FlagsMatcher(FLAGS)),
|
||||
eq(UserHandle.of(user))
|
||||
)
|
||||
).thenReturn(infos.map {
|
||||
ResolveInfo().apply { activityInfo = it }
|
||||
})
|
||||
}
|
||||
|
||||
private class IntentMatcher(
|
||||
private val componentName: ComponentName
|
||||
) : ArgumentMatcher<Intent> {
|
||||
override fun matches(argument: Intent?): Boolean {
|
||||
return argument?.component == componentName
|
||||
}
|
||||
}
|
||||
|
||||
private class FlagsMatcher(
|
||||
private val flags: Long
|
||||
) : ArgumentMatcher<PackageManager.ResolveInfoFlags> {
|
||||
override fun matches(argument: PackageManager.ResolveInfoFlags?): Boolean {
|
||||
return flags == argument?.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user