Loading packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt +48 −43 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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" Loading @@ -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) Loading @@ -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()) } } } Loading @@ -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 */ Loading Loading @@ -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) Loading @@ -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) } Loading @@ -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>) { Loading packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt +218 −175 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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") Loading Loading @@ -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 }, Loading Loading @@ -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) Loading Loading @@ -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) Loading Loading @@ -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) Loading @@ -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))) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading Loading @@ -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)) ) } Loading @@ -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() Loading @@ -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() Loading @@ -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) Loading @@ -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 Loading @@ -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() Loading Loading @@ -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 } Loading Loading
packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt +48 −43 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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" Loading @@ -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) Loading @@ -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()) } } } Loading @@ -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 */ Loading Loading @@ -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) Loading @@ -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) } Loading @@ -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>) { Loading
packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt +218 −175 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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") Loading Loading @@ -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 }, Loading Loading @@ -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) Loading Loading @@ -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) Loading Loading @@ -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) Loading @@ -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))) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading Loading @@ -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)) ) } Loading @@ -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() Loading @@ -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() Loading @@ -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) Loading @@ -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 Loading @@ -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() Loading Loading @@ -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 } Loading