summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/service/controls/ControlsProviderService.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt60
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt351
4 files changed, 492 insertions, 41 deletions
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 47b16a351806..d2a4ae282061 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -55,6 +55,20 @@ public abstract class ControlsProviderService extends Service {
"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
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index 588ef5c4e68f..4dfcd6398a4d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -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
-) \ No newline at end of file
+) {
+ 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()
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 2d76ff2774d6..115edd115ffe 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -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()}")
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index db41d8d37a43..dedc7239bf85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -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
+ }
+ }
}