Merge changes I61922e99,I656b81fe into tm-qpr-dev am: 7515e83e2b

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20385077

Change-Id: I895b9b802137f19bfc50c42ccdec2945fcd85b81
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Fabian Kozynski
2022-11-10 14:56:32 +00:00
committed by Automerger Merge Worker
4 changed files with 492 additions and 41 deletions

View File

@@ -54,6 +54,20 @@ public abstract class ControlsProviderService extends Service {
public static final String SERVICE_CONTROLS = public static final String SERVICE_CONTROLS =
"android.service.controls.ControlsProviderService"; "android.service.controls.ControlsProviderService";
/**
* Manifest metadata to show a custom embedded activity as part of device controls.
*
* The value of this metadata must be the {@link ComponentName} as a string of an activity in
* the same package that will be launched as part of a TaskView.
*
* The activity must be exported, enabled and protected by
* {@link Manifest.permission.BIND_CONTROLS}.
*
* @hide
*/
public static final String META_DATA_PANEL_ACTIVITY =
"android.service.controls.META_DATA_PANEL_ACTIVITY";
/** /**
* @hide * @hide
*/ */

View File

@@ -16,16 +16,120 @@
package com.android.systemui.controls package com.android.systemui.controls
import android.Manifest
import android.content.ComponentName
import android.content.Context 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.content.pm.ServiceInfo
import android.os.UserHandle
import android.service.controls.ControlsProviderService
import androidx.annotation.WorkerThread
import com.android.settingslib.applications.DefaultAppInfo import com.android.settingslib.applications.DefaultAppInfo
import java.util.Objects
class ControlsServiceInfo( class ControlsServiceInfo(
context: Context, private val context: Context,
val serviceInfo: ServiceInfo val serviceInfo: ServiceInfo
) : DefaultAppInfo( ) : DefaultAppInfo(
context, context,
context.packageManager, context.packageManager,
context.userId, context.userId,
serviceInfo.componentName 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()
}
}

View File

@@ -18,17 +18,23 @@ package com.android.systemui.controls.management
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.pm.ServiceInfo
import android.os.UserHandle import android.os.UserHandle
import android.service.controls.ControlsProviderService import android.service.controls.ControlsProviderService
import android.util.Log import android.util.Log
import com.android.internal.annotations.VisibleForTesting import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.applications.ServiceListing import com.android.settingslib.applications.ServiceListing
import com.android.settingslib.widget.CandidateInfo import com.android.settingslib.widget.CandidateInfo
import com.android.systemui.Dumpable
import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background 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.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.Executor
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject import javax.inject.Inject
@@ -57,16 +63,19 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
private val context: Context, private val context: Context,
@Background private val backgroundExecutor: Executor, @Background private val backgroundExecutor: Executor,
private val serviceListingBuilder: (Context) -> ServiceListing, private val serviceListingBuilder: (Context) -> ServiceListing,
userTracker: UserTracker private val userTracker: UserTracker,
) : ControlsListingController { dumpManager: DumpManager,
featureFlags: FeatureFlags
) : ControlsListingController, Dumpable {
@Inject @Inject
constructor(context: Context, executor: Executor, userTracker: UserTracker): this( constructor(
context, context: Context,
executor, @Background executor: Executor,
::createServiceListing, userTracker: UserTracker,
userTracker dumpManager: DumpManager,
) featureFlags: FeatureFlags
) : this(context, executor, ::createServiceListing, userTracker, dumpManager, featureFlags)
private var serviceListing = serviceListingBuilder(context) private var serviceListing = serviceListingBuilder(context)
// All operations in background thread // All operations in background thread
@@ -76,27 +85,25 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
private const val TAG = "ControlsListingControllerImpl" private const val TAG = "ControlsListingControllerImpl"
} }
private var availableComponents = emptySet<ComponentName>() private var availableServices = emptyList<ControlsServiceInfo>()
private var availableServices = emptyList<ServiceInfo>()
private var userChangeInProgress = AtomicInteger(0) private var userChangeInProgress = AtomicInteger(0)
override var currentUserId = userTracker.userId override var currentUserId = userTracker.userId
private set private set
private val serviceListingCallback = ServiceListing.Callback { private val serviceListingCallback = ServiceListing.Callback {
val newServices = it.toList()
val newComponents =
newServices.mapTo(mutableSetOf<ComponentName>(), { s -> s.getComponentName() })
backgroundExecutor.execute { backgroundExecutor.execute {
if (userChangeInProgress.get() > 0) return@execute if (userChangeInProgress.get() > 0) return@execute
if (!newComponents.equals(availableComponents)) { Log.d(TAG, "ServiceConfig reloaded, count: ${it.size}")
Log.d(TAG, "ServiceConfig reloaded, count: ${newComponents.size}") val newServices = it.map { ControlsServiceInfo(userTracker.userContext, it) }
availableComponents = newComponents if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
newServices.forEach(ControlsServiceInfo::resolvePanelActivity)
}
if (newServices != availableServices) {
availableServices = newServices availableServices = newServices
val currentServices = getCurrentServices()
callbacks.forEach { callbacks.forEach {
it.onServicesUpdated(currentServices) it.onServicesUpdated(getCurrentServices())
} }
} }
} }
@@ -104,6 +111,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
init { init {
Log.d(TAG, "Initializing") Log.d(TAG, "Initializing")
dumpManager.registerDumpable(TAG, this)
serviceListing.addCallback(serviceListingCallback) serviceListing.addCallback(serviceListingCallback)
serviceListing.setListening(true) serviceListing.setListening(true)
serviceListing.reload() serviceListing.reload()
@@ -165,7 +173,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
* [ControlsProviderService] * [ControlsProviderService]
*/ */
override fun getCurrentServices(): List<ControlsServiceInfo> = override fun getCurrentServices(): List<ControlsServiceInfo> =
availableServices.map { ControlsServiceInfo(context, it) } availableServices.map(ControlsServiceInfo::copy)
/** /**
* Get the localized label for the component. * 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`. * @return a label as returned by [CandidateInfo.loadLabel] or `null`.
*/ */
override fun getAppLabel(name: ComponentName): CharSequence? { override fun getAppLabel(name: ComponentName): CharSequence? {
return getCurrentServices().firstOrNull { it.componentName == name } return availableServices.firstOrNull { it.componentName == name }
?.loadLabel() ?.loadLabel()
} }
override fun dump(writer: PrintWriter, args: Array<out String>) {
writer.println("ControlsListingController:")
writer.asIndenting().indentIfPossible {
println("Callbacks: $callbacks")
println("Services: ${getCurrentServices()}")
}
}
} }

View File

@@ -16,27 +16,42 @@
package com.android.systemui.controls.management package com.android.systemui.controls.management
import android.Manifest
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.ContextWrapper 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.content.pm.ServiceInfo
import android.os.Bundle
import android.os.UserHandle import android.os.UserHandle
import android.service.controls.ControlsProviderService
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.settingslib.applications.ServiceListing import com.android.settingslib.applications.ServiceListing
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo 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.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor 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 com.android.systemui.util.time.FakeSystemClock
import org.junit.After import org.junit.After
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatcher
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when` import org.mockito.Mockito.`when`
import org.mockito.Mockito.inOrder import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock import org.mockito.Mockito.mock
@@ -51,10 +66,8 @@ import java.util.concurrent.Executor
class ControlsListingControllerImplTest : SysuiTestCase() { class ControlsListingControllerImplTest : SysuiTestCase() {
companion object { companion object {
private const val TEST_LABEL = "TEST_LABEL" private const val FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
private const val TEST_PERMISSION = "permission" PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
fun <T> any(): T = Mockito.any<T>()
} }
@Mock @Mock
@@ -63,15 +76,17 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
private lateinit var mockCallback: ControlsListingController.ControlsListingCallback private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
@Mock @Mock
private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
@Mock
private lateinit var serviceInfo: ServiceInfo
@Mock
private lateinit var serviceInfo2: ServiceInfo
@Mock(stubOnly = true) @Mock(stubOnly = true)
private lateinit var userTracker: UserTracker 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 componentName = ComponentName("pkg", "class1")
private var componentName2 = ComponentName("pkg2", "class2") private var activityName = ComponentName("pkg", "activity")
private val executor = FakeExecutor(FakeSystemClock()) private val executor = FakeExecutor(FakeSystemClock())
@@ -87,9 +102,15 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
`when`(serviceInfo.componentName).thenReturn(componentName)
`when`(serviceInfo2.componentName).thenReturn(componentName2)
`when`(userTracker.userId).thenReturn(user) `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) { val wrapper = object : ContextWrapper(mContext) {
override fun createContextAsUser(user: UserHandle, flags: Int): Context { 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)) verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
} }
@@ -123,9 +151,16 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
Unit Unit
} }
`when`(mockServiceListing.reload()).then { `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 @Test
@@ -148,7 +183,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
@Test @Test
fun testCallbackGetsList() { fun testCallbackGetsList() {
val list = listOf(serviceInfo) val list = listOf(ServiceInfo(componentName))
controller.addCallback(mockCallback) controller.addCallback(mockCallback)
controller.addCallback(mockCallbackOther) controller.addCallback(mockCallbackOther)
@@ -188,6 +223,8 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
@Test @Test
fun testChangeUserSendsCorrectServiceUpdate() { fun testChangeUserSendsCorrectServiceUpdate() {
val serviceInfo = ServiceInfo(componentName)
val list = listOf(serviceInfo) val list = listOf(serviceInfo)
controller.addCallback(mockCallback) controller.addCallback(mockCallback)
@@ -223,4 +260,284 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
verify(mockCallback).onServicesUpdated(capture(captor)) verify(mockCallback).onServicesUpdated(capture(captor))
assertEquals(0, captor.value.size) 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
}
}
} }