Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4d4b46b2 authored by Behnam Heydarshahi's avatar Behnam Heydarshahi
Browse files

Use CopyOnWrite ControlListingController callbacks

Otherwise when dumping the controller there is a risk of
ConcurrentModificationException

Flag: EXEMPT This is a minor bug fix.
Fixes: 356752728
Test: atest ControlsListingControllerImplTest
Change-Id: I98d06fbb9c2cdb074a3597b1030a2aec75d8df35

Change-Id: If129d345a24f9f76f8d5946701c80bd7215efbe4
parent e1e1c88d
Loading
Loading
Loading
Loading
+48 −43
Original line number Diff line number Diff line
@@ -38,31 +38,35 @@ import com.android.systemui.util.ActivityTaskManagerProxy
import com.android.systemui.util.asIndenting
import com.android.systemui.util.indentIfPossible
import java.io.PrintWriter
import java.util.concurrent.CopyOnWriteArraySet
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject

private fun createServiceListing(context: Context): ServiceListing {
    return ServiceListing.Builder(context).apply {
    return ServiceListing.Builder(context)
        .apply {
            setIntentAction(ControlsProviderService.SERVICE_CONTROLS)
            setPermission("android.permission.BIND_CONTROLS")
            setNoun("Controls Provider")
            setSetting("controls_providers")
            setTag("controls_providers")
            setAddDeviceLockedFlags(true)
    }.build()
        }
        .build()
}

/**
 * Provides a listing of components to be used as ControlsServiceProvider.
 *
 * This controller keeps track of components that satisfy:
 *
 * * Has an intent-filter responding to [ControlsProviderService.CONTROLS_ACTION]
 * * Has the bind permission `android.permission.BIND_CONTROLS`
 */
@SysUISingleton
class ControlsListingControllerImpl @VisibleForTesting constructor(
class ControlsListingControllerImpl
@VisibleForTesting
constructor(
    private val context: Context,
    @Background private val backgroundExecutor: Executor,
    private val serviceListingBuilder: (Context) -> ServiceListing,
@@ -92,7 +96,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(

    private var serviceListing = serviceListingBuilder(context)
    // All operations in background thread
    private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>()
    private val callbacks = CopyOnWriteArraySet<ControlsListingController.ControlsListingCallback>()

    companion object {
        private const val TAG = "ControlsListingControllerImpl"
@@ -104,10 +108,12 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
    override var currentUserId = userTracker.userId
        private set

    private val serviceListingCallback = ServiceListing.Callback { list ->
    private val serviceListingCallback =
        ServiceListing.Callback { list ->
            Log.d(TAG, "ServiceConfig reloaded, count: ${list.size}")
            val newServices = list.map { ControlsServiceInfo(userTracker.userContext, it) }
        // After here, `list` is not captured, so we don't risk modifying it outside of the callback
            // After here, `list` is not captured, so we don't risk modifying it outside of the
            // callback
            backgroundExecutor.execute {
                if (userChangeInProgress.get() > 0) return@execute
                updateServices(newServices)
@@ -124,15 +130,12 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(

    private fun updateServices(newServices: List<ControlsServiceInfo>) {
        if (activityTaskManagerProxy.supportsMultiWindow(context)) {
            newServices.forEach {
                it.resolvePanelActivity() }
            newServices.forEach { it.resolvePanelActivity() }
        }

        if (newServices != availableServices) {
            availableServices = newServices
            callbacks.forEach {
                it.onServicesUpdated(getCurrentServices())
            }
            callbacks.forEach { it.onServicesUpdated(getCurrentServices()) }
        }
    }

@@ -155,8 +158,8 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
    /**
     * Adds a callback to this controller.
     *
     * The callback will be notified after it is added as well as any time that the valid
     * components change.
     * The callback will be notified after it is added as well as any time that the valid components
     * change.
     *
     * @param listener a callback to be notified
     */
@@ -188,8 +191,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
    }

    /**
     * @return a list of components that satisfy the requirements to be a
     *         [ControlsProviderService]
     * @return a list of components that satisfy the requirements to be a [ControlsProviderService]
     */
    override fun getCurrentServices(): List<ControlsServiceInfo> =
        availableServices.map(ControlsServiceInfo::copy)
@@ -199,15 +201,19 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
        val packageManager = context.packageManager
        val intent = Intent(ControlsProviderService.SERVICE_CONTROLS)
        val user = userTracker.userHandle
        val flags = PackageManager.GET_SERVICES or
        val flags =
            PackageManager.GET_SERVICES or
                PackageManager.GET_META_DATA or
                PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
                PackageManager.MATCH_DIRECT_BOOT_AWARE
        val services = packageManager.queryIntentServicesAsUser(
        val services =
            packageManager
                .queryIntentServicesAsUser(
                    intent,
                    PackageManager.ResolveInfoFlags.of(flags.toLong()),
                    user
        ).map { ControlsServiceInfo(userTracker.userContext, it.serviceInfo) }
                )
                .map { ControlsServiceInfo(userTracker.userContext, it.serviceInfo) }
        updateServices(services)
    }

@@ -218,8 +224,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
     * @return a label as returned by [CandidateInfo.loadLabel] or `null`.
     */
    override fun getAppLabel(name: ComponentName): CharSequence? {
        return availableServices.firstOrNull { it.componentName == name }
                ?.loadLabel()
        return availableServices.firstOrNull { it.componentName == name }?.loadLabel()
    }

    override fun dump(writer: PrintWriter, args: Array<out String>) {
+218 −175
Original line number Diff line number Diff line
@@ -31,11 +31,11 @@ import android.service.controls.ControlsProviderService
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.applications.ServiceListing
import com.android.systemui.res.R
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.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.ActivityTaskManagerProxy
import com.android.systemui.util.concurrency.FakeExecutor
@@ -45,6 +45,8 @@ import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
import org.junit.After
import org.junit.Assert.assertEquals
@@ -56,39 +58,33 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatcher
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.mock

@SmallTest
@RunWith(AndroidJUnit4::class)
class ControlsListingControllerImplTest : SysuiTestCase() {

    companion object {
        private const val FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
        private const val FLAGS =
            PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
                PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
    }

    @Mock
    private lateinit var mockSL: ServiceListing
    @Mock
    private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
    @Mock
    private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
    @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
    @Mock
    private lateinit var activityTaskManagerProxy: ActivityTaskManagerProxy
    @Mock private lateinit var mockSL: ServiceListing
    @Mock private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
    @Mock private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
    @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
    @Mock private lateinit var activityTaskManagerProxy: ActivityTaskManagerProxy

    private var componentName = ComponentName("pkg", "class1")
    private var activityName = ComponentName("pkg", "activity")
@@ -116,19 +112,20 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
        `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(true)
        mContext.setMockPackageManager(packageManager)

        mContext.orCreateTestableResources
                .addOverride(
        mContext.orCreateTestableResources.addOverride(
            R.array.config_controlsPreferredPackages,
            arrayOf(componentName.packageName)
        )

        val wrapper = object : ContextWrapper(mContext) {
        val wrapper =
            object : ContextWrapper(mContext) {
                override fun createContextAsUser(user: UserHandle, flags: Int): Context {
                    return baseContext
                }
            }

        controller = ControlsListingControllerImpl(
        controller =
            ControlsListingControllerImpl(
                wrapper,
                executor,
                { mockSL },
@@ -201,8 +198,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {

        @Suppress("unchecked_cast")
        val captor: ArgumentCaptor<List<ControlsServiceInfo>> =
                ArgumentCaptor.forClass(List::class.java)
                        as ArgumentCaptor<List<ControlsServiceInfo>>
            ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<ControlsServiceInfo>>

        executor.runAllReady()
        reset(mockCallback)
@@ -242,8 +238,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {

        @Suppress("unchecked_cast")
        val captor: ArgumentCaptor<List<ControlsServiceInfo>> =
                ArgumentCaptor.forClass(List::class.java)
                        as ArgumentCaptor<List<ControlsServiceInfo>>
            ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<ControlsServiceInfo>>
        executor.runAllReady()
        reset(mockCallback)

@@ -285,10 +280,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {

    @Test
    fun testNoActivity_nullPanel() {
        val serviceInfo = ServiceInfo(
                componentName,
                activityName
        )
        val serviceInfo = ServiceInfo(componentName, activityName)

        val list = listOf(serviceInfo)
        serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -300,10 +292,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {

    @Test
    fun testActivityWithoutPermission_nullPanel() {
        val serviceInfo = ServiceInfo(
                componentName,
                activityName
        )
        val serviceInfo = ServiceInfo(componentName, activityName)

        setUpQueryResult(listOf(ActivityInfo(activityName)))

@@ -317,14 +306,11 @@ class ControlsListingControllerImplTest : SysuiTestCase() {

    @Test
    fun testActivityPermissionNotExported_nullPanel() {
        val serviceInfo = ServiceInfo(
                componentName,
                activityName
        )
        val serviceInfo = ServiceInfo(componentName, activityName)

        setUpQueryResult(listOf(
                ActivityInfo(activityName, permission = Manifest.permission.BIND_CONTROLS)
        ))
        setUpQueryResult(
            listOf(ActivityInfo(activityName, permission = Manifest.permission.BIND_CONTROLS))
        )

        val list = listOf(serviceInfo)
        serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -336,18 +322,17 @@ class ControlsListingControllerImplTest : SysuiTestCase() {

    @Test
    fun testActivityDisabled_nullPanel() {
        val serviceInfo = ServiceInfo(
                componentName,
                activityName
        )
        val serviceInfo = ServiceInfo(componentName, activityName)

        setUpQueryResult(listOf(
        setUpQueryResult(
            listOf(
                ActivityInfo(
                    activityName,
                    exported = true,
                    permission = Manifest.permission.BIND_CONTROLS
                )
        ))
            )
        )

        val list = listOf(serviceInfo)
        serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -359,21 +344,20 @@ class ControlsListingControllerImplTest : SysuiTestCase() {

    @Test
    fun testActivityEnabled_correctPanel() {
        val serviceInfo = ServiceInfo(
                componentName,
                activityName
        )
        val serviceInfo = ServiceInfo(componentName, activityName)

        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)

        setUpQueryResult(listOf(
        setUpQueryResult(
            listOf(
                ActivityInfo(
                    activityName,
                    exported = true,
                    permission = Manifest.permission.BIND_CONTROLS
                )
        ))
            )
        )

        val list = listOf(serviceInfo)
        serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -385,22 +369,21 @@ class ControlsListingControllerImplTest : SysuiTestCase() {

    @Test
    fun testActivityDefaultEnabled_correctPanel() {
        val serviceInfo = ServiceInfo(
                componentName,
                activityName
        )
        val serviceInfo = ServiceInfo(componentName, activityName)

        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)

        setUpQueryResult(listOf(
        setUpQueryResult(
            listOf(
                ActivityInfo(
                    activityName,
                    enabled = true,
                    exported = true,
                    permission = Manifest.permission.BIND_CONTROLS
                )
        ))
            )
        )

        val list = listOf(serviceInfo)
        serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -412,22 +395,21 @@ class ControlsListingControllerImplTest : SysuiTestCase() {

    @Test
    fun testActivityDefaultDisabled_nullPanel() {
        val serviceInfo = ServiceInfo(
                componentName,
                activityName
        )
        val serviceInfo = ServiceInfo(componentName, activityName)

        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)

        setUpQueryResult(listOf(
        setUpQueryResult(
            listOf(
                ActivityInfo(
                    activityName,
                    enabled = false,
                    exported = true,
                    permission = Manifest.permission.BIND_CONTROLS
                )
        ))
            )
        )

        val list = listOf(serviceInfo)
        serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -439,22 +421,21 @@ class ControlsListingControllerImplTest : SysuiTestCase() {

    @Test
    fun testActivityDifferentPackage_nullPanel() {
        val serviceInfo = ServiceInfo(
                componentName,
                ComponentName("other_package", "cls")
        )
        val serviceInfo = ServiceInfo(componentName, ComponentName("other_package", "cls"))

        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)

        setUpQueryResult(listOf(
        setUpQueryResult(
            listOf(
                ActivityInfo(
                    activityName,
                    enabled = true,
                    exported = true,
                    permission = Manifest.permission.BIND_CONTROLS
                )
        ))
            )
        )

        val list = listOf(serviceInfo)
        serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -466,24 +447,25 @@ class ControlsListingControllerImplTest : SysuiTestCase() {

    @Test
    fun testPackageNotPreferred_correctPanel() {
        mContext.orCreateTestableResources
                .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())

        val serviceInfo = ServiceInfo(
                componentName,
                activityName
        mContext.orCreateTestableResources.addOverride(
            R.array.config_controlsPreferredPackages,
            arrayOf<String>()
        )

        val serviceInfo = ServiceInfo(componentName, activityName)

        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)

        setUpQueryResult(listOf(
        setUpQueryResult(
            listOf(
                ActivityInfo(
                    activityName,
                    exported = true,
                    permission = Manifest.permission.BIND_CONTROLS
                )
        ))
            )
        )

        val list = listOf(serviceInfo)
        serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -512,14 +494,17 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
        `when`(userTracker.userHandle).thenReturn(UserHandle.of(user))

        controller.forceReload()
        verify(packageManager).queryIntentServicesAsUser(
        verify(packageManager)
            .queryIntentServicesAsUser(
                argThat(IntentMatcherAction(ControlsProviderService.SERVICE_CONTROLS)),
                argThat(FlagsMatcher(
                argThat(
                    FlagsMatcher(
                        PackageManager.GET_META_DATA.toLong() or
                            PackageManager.GET_SERVICES.toLong() or
                            PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
                            PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
                )),
                    )
                ),
                eq(UserHandle.of(user))
            )
    }
@@ -529,16 +514,21 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
        val resolveInfo = ResolveInfo()
        resolveInfo.serviceInfo = ServiceInfo(componentName)

        `when`(packageManager.queryIntentServicesAsUser(
        `when`(
                packageManager.queryIntentServicesAsUser(
                    argThat(IntentMatcherAction(ControlsProviderService.SERVICE_CONTROLS)),
                argThat(FlagsMatcher(
                    argThat(
                        FlagsMatcher(
                            PackageManager.GET_META_DATA.toLong() or
                                PackageManager.GET_SERVICES.toLong() or
                                PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
                                PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
                )),
                        )
                    ),
                    any<UserHandle>()
        )).thenReturn(listOf(resolveInfo))
                )
            )
            .thenReturn(listOf(resolveInfo))

        controller.forceReload()

@@ -554,17 +544,19 @@ class ControlsListingControllerImplTest : SysuiTestCase() {

        @Suppress("unchecked_cast")
        val captor: ArgumentCaptor<List<ControlsServiceInfo>> =
                ArgumentCaptor.forClass(List::class.java)
                        as ArgumentCaptor<List<ControlsServiceInfo>>
            ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<ControlsServiceInfo>>

        val resolveInfo = ResolveInfo()
        resolveInfo.serviceInfo = ServiceInfo(componentName)

        `when`(packageManager.queryIntentServicesAsUser(
        `when`(
                packageManager.queryIntentServicesAsUser(
                    any(),
                    any<PackageManager.ResolveInfoFlags>(),
                    any<UserHandle>()
        )).thenReturn(listOf(resolveInfo))
                )
            )
            .thenReturn(listOf(resolveInfo))

        reset(mockCallback)
        controller.forceReload()
@@ -581,22 +573,21 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
    fun testNoPanelIfMultiWindowNotSupported() {
        `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(false)

        val serviceInfo = ServiceInfo(
            componentName,
            activityName
        )
        val serviceInfo = ServiceInfo(componentName, activityName)

        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)

        setUpQueryResult(listOf(
        setUpQueryResult(
            listOf(
                ActivityInfo(
                    activityName,
                    enabled = true,
                    exported = true,
                    permission = Manifest.permission.BIND_CONTROLS
                )
        ))
            )
        )

        val list = listOf(serviceInfo)
        serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -606,6 +597,62 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
        assertNull(controller.getCurrentServices()[0].panelActivity)
    }

    @Test
    fun dumpAndAddRemoveCallback_willNotThrowConcurrentModificationException() {
        val repeat = 100
        controller.addCallback(mockCallback) // 1 extra callback increases the duration of iteration

        // the goal of these two barriers is to make the modify and iterate run concurrently
        val startSignal = CountDownLatch(2)
        val doneSignal = CountDownLatch(2)
        val modifyRunnable = Runnable {
            for (i in 1..repeat) {
                controller.addCallback(mockCallbackOther)
                executor.runAllReady()
                controller.removeCallback(mockCallbackOther)
                executor.runAllReady()
            }
        }
        val printWriter = mock<PrintWriter>()
        val arr = arrayOf<String>()
        val iterateRunnable = Runnable {
            for (i in 1..repeat) {
                controller.dump(printWriter, arr)
            }
        }

        val workerThread = Thread(Worker(startSignal, doneSignal, modifyRunnable))
        workerThread.start()
        val workerThreadOther = Thread(Worker(startSignal, doneSignal, iterateRunnable))
        workerThreadOther.start()
        doneSignal.await()
        workerThread.interrupt()
        workerThreadOther.interrupt()
    }

    class Worker : Runnable {
        private val startSignal: CountDownLatch
        private val doneSignal: CountDownLatch
        private val runnable: Runnable

        constructor(start: CountDownLatch, done: CountDownLatch, run: Runnable) {
            startSignal = start
            doneSignal = done
            runnable = run
        }

        override fun run() {
            try {
                startSignal.countDown()
                startSignal.await()
                runnable.run()
                doneSignal.countDown()
            } catch (ex: InterruptedException) {
                return
            }
        }
    }

    private fun ServiceInfo(
        componentName: ComponentName,
        panelActivityComponentName: ComponentName? = null
@@ -614,7 +661,8 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
            packageName = componentName.packageName
            name = componentName.className
            panelActivityComponentName?.let {
                metaData = Bundle().apply {
                metaData =
                    Bundle().apply {
                        putString(
                            ControlsProviderService.META_DATA_PANEL_ACTIVITY,
                            it.flattenToShortString()
@@ -646,30 +694,25 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
                    argThat(FlagsMatcher(FLAGS)),
                    eq(UserHandle.of(user))
                )
        ).thenReturn(infos.map {
            ResolveInfo().apply { activityInfo = it }
        })
            )
            .thenReturn(infos.map { ResolveInfo().apply { activityInfo = it } })
    }

    private class IntentMatcherComponent(
            private val componentName: ComponentName
    ) : ArgumentMatcher<Intent> {
    private class IntentMatcherComponent(private val componentName: ComponentName) :
        ArgumentMatcher<Intent> {
        override fun matches(argument: Intent?): Boolean {
            return argument?.component == componentName
        }
    }

    private class IntentMatcherAction(
            private val action: String
    ) : ArgumentMatcher<Intent> {
    private class IntentMatcherAction(private val action: String) : ArgumentMatcher<Intent> {
        override fun matches(argument: Intent?): Boolean {
            return argument?.action == action
        }
    }

    private class FlagsMatcher(
            private val flags: Long
    ) : ArgumentMatcher<PackageManager.ResolveInfoFlags> {
    private class FlagsMatcher(private val flags: Long) :
        ArgumentMatcher<PackageManager.ResolveInfoFlags> {
        override fun matches(argument: PackageManager.ResolveInfoFlags?): Boolean {
            return flags == argument?.value
        }