Merge "Added core startable" into tm-qpr-dev

This commit is contained in:
Fabian Kozynski
2023-02-01 20:17:27 +00:00
committed by Android (Google) Code Review
7 changed files with 448 additions and 21 deletions

View File

@@ -28,12 +28,13 @@ import android.content.pm.ResolveInfo
import android.content.pm.ServiceInfo import android.content.pm.ServiceInfo
import android.os.UserHandle import android.os.UserHandle
import android.service.controls.ControlsProviderService import android.service.controls.ControlsProviderService
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.android.settingslib.applications.DefaultAppInfo import com.android.settingslib.applications.DefaultAppInfo
import com.android.systemui.R import com.android.systemui.R
import java.util.Objects import java.util.Objects
class ControlsServiceInfo( open class ControlsServiceInfo(
private val context: Context, private val context: Context,
val serviceInfo: ServiceInfo val serviceInfo: ServiceInfo
) : DefaultAppInfo( ) : DefaultAppInfo(
@@ -64,7 +65,7 @@ class ControlsServiceInfo(
* [R.array.config_controlsPreferredPackages] can declare activities for use as a panel. * [R.array.config_controlsPreferredPackages] can declare activities for use as a panel.
*/ */
var panelActivity: ComponentName? = null var panelActivity: ComponentName? = null
private set protected set
private var resolved: Boolean = false private var resolved: Boolean = false

View File

@@ -121,16 +121,13 @@ class ControlsControllerImpl @Inject constructor (
userChanging = false userChanging = false
} }
private val userTrackerCallback = object : UserTracker.Callback { override fun changeUser(newUser: UserHandle) {
override fun onUserChanged(newUser: Int, userContext: Context) { userChanging = true
userChanging = true if (currentUser == newUser) {
val newUserHandle = UserHandle.of(newUser) userChanging = false
if (currentUser == newUserHandle) { return
userChanging = false
return
}
setValuesForUser(newUserHandle)
} }
setValuesForUser(newUser)
} }
@VisibleForTesting @VisibleForTesting
@@ -231,7 +228,6 @@ class ControlsControllerImpl @Inject constructor (
dumpManager.registerDumpable(javaClass.name, this) dumpManager.registerDumpable(javaClass.name, this)
resetFavorites() resetFavorites()
userChanging = false userChanging = false
userTracker.addCallback(userTrackerCallback, executor)
context.registerReceiver( context.registerReceiver(
restoreFinishedReceiver, restoreFinishedReceiver,
IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED), IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
@@ -243,7 +239,6 @@ class ControlsControllerImpl @Inject constructor (
} }
fun destroy() { fun destroy() {
userTracker.removeCallback(userTrackerCallback)
context.unregisterReceiver(restoreFinishedReceiver) context.unregisterReceiver(restoreFinishedReceiver)
listingController.removeCallback(listingCallback) listingController.removeCallback(listingCallback)
} }

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.systemui.controls.dagger
import com.android.systemui.CoreStartable
import com.android.systemui.controls.start.ControlsStartable
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
abstract class StartControlsStartableModule {
@Binds
@IntoMap
@ClassKey(ControlsStartable::class)
abstract fun bindFeature(impl: ControlsStartable): CoreStartable
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.systemui.controls.start
import android.content.Context
import android.content.res.Resources
import android.os.UserHandle
import com.android.systemui.CoreStartable
import com.android.systemui.R
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import javax.inject.Inject
/**
* Started with SystemUI to perform early operations for device controls subsystem (only if enabled)
*
* In particular, it will perform the following:
* * If there is no preferred selection for provider and at least one of the preferred packages
* provides a panel, it will select the first one that does.
* * If the preferred selection provides a panel, it will bind to that service (to reduce latency on
* displaying the panel).
*
* It will also perform those operations on user change.
*/
@SysUISingleton
class ControlsStartable
@Inject
constructor(
@Main private val resources: Resources,
@Background private val executor: Executor,
private val controlsComponent: ControlsComponent,
private val userTracker: UserTracker
) : CoreStartable {
// These two controllers can only be accessed after `start` method once we've checked if the
// feature is enabled
private val controlsController: ControlsController
get() = controlsComponent.getControlsController().get()
private val controlsListingController: ControlsListingController
get() = controlsComponent.getControlsListingController().get()
private val userTrackerCallback =
object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
controlsController.changeUser(UserHandle.of(newUser))
startForUser()
}
}
override fun start() {
if (!controlsComponent.isEnabled()) {
// Controls is disabled, we don't need this anymore
return
}
startForUser()
userTracker.addCallback(userTrackerCallback, executor)
}
private fun startForUser() {
selectDefaultPanelIfNecessary()
bindToPanel()
}
private fun selectDefaultPanelIfNecessary() {
val currentSelection = controlsController.getPreferredSelection()
if (currentSelection == SelectedItem.EMPTY_SELECTION) {
val availableServices = controlsListingController.getCurrentServices()
val panels = availableServices.filter { it.panelActivity != null }
resources
.getStringArray(R.array.config_controlsPreferredPackages)
// Looking for the first element in the string array such that there is one package
// that has a panel. It will return null if there are no packages in the array,
// or if no packages in the array have a panel associated with it.
.firstNotNullOfOrNull { name ->
panels.firstOrNull { it.componentName.packageName == name }
}
?.let { info ->
controlsController.setPreferredSelection(
SelectedItem.PanelItem(info.loadLabel(), info.componentName)
)
}
}
}
private fun bindToPanel() {
val currentSelection = controlsController.getPreferredSelection()
val panels =
controlsListingController.getCurrentServices().filter { it.panelActivity != null }
if (
currentSelection is SelectedItem.PanelItem &&
panels.firstOrNull { it.componentName == currentSelection.componentName } != null
) {
controlsController.bindComponentForPanel(currentSelection.componentName)
}
}
}

View File

@@ -26,6 +26,7 @@ import com.android.systemui.accessibility.SystemActions
import com.android.systemui.accessibility.WindowMagnification import com.android.systemui.accessibility.WindowMagnification
import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.AuthController
import com.android.systemui.clipboardoverlay.ClipboardListener import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser import com.android.systemui.dagger.qualifiers.PerUser
import com.android.systemui.dreams.DreamMonitor import com.android.systemui.dreams.DreamMonitor
import com.android.systemui.globalactions.GlobalActionsComponent import com.android.systemui.globalactions.GlobalActionsComponent
@@ -65,7 +66,10 @@ import dagger.multibindings.IntoMap
/** /**
* Collection of {@link CoreStartable}s that should be run on AOSP. * Collection of {@link CoreStartable}s that should be run on AOSP.
*/ */
@Module(includes = [MultiUserUtilsModule::class]) @Module(includes = [
MultiUserUtilsModule::class,
StartControlsStartableModule::class
])
abstract class SystemUICoreStartableModule { abstract class SystemUICoreStartableModule {
/** Inject into AuthController. */ /** Inject into AuthController. */
@Binds @Binds

View File

@@ -103,8 +103,6 @@ class ControlsControllerImplTest : SysuiTestCase() {
private lateinit var controlLoadCallbackCaptor2: private lateinit var controlLoadCallbackCaptor2:
ArgumentCaptor<ControlsBindingController.LoadCallback> ArgumentCaptor<ControlsBindingController.LoadCallback>
@Captor
private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
@Captor @Captor
private lateinit var listingCallbackCaptor: private lateinit var listingCallbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback> ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -178,10 +176,6 @@ class ControlsControllerImplTest : SysuiTestCase() {
) )
controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper
verify(userTracker).addCallback(
capture(userTrackerCallbackCaptor), any()
)
verify(listingController).addCallback(capture(listingCallbackCaptor)) verify(listingController).addCallback(capture(listingCallbackCaptor))
} }
@@ -539,7 +533,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
reset(persistenceWrapper) reset(persistenceWrapper)
userTrackerCallbackCaptor.value.onUserChanged(otherUser, mContext) controller.changeUser(UserHandle.of(otherUser))
verify(persistenceWrapper).changeFileAndBackupManager(any(), any()) verify(persistenceWrapper).changeFileAndBackupManager(any(), any())
verify(persistenceWrapper).readFavorites() verify(persistenceWrapper).readFavorites()

View File

@@ -0,0 +1,281 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.systemui.controls.start
import android.content.ComponentName
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.ServiceInfo
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.SelectedItem
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.mock
import com.android.systemui.util.time.FakeSystemClock
import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class ControlsStartableTest : SysuiTestCase() {
@Mock private lateinit var controlsController: ControlsController
@Mock private lateinit var controlsListingController: ControlsListingController
@Mock private lateinit var userTracker: UserTracker
private lateinit var fakeExecutor: FakeExecutor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf<String>()
)
fakeExecutor = FakeExecutor(FakeSystemClock())
}
@Test
fun testDisabledNothingIsCalled() {
createStartable(enabled = false).start()
verifyZeroInteractions(controlsController, controlsListingController, userTracker)
}
@Test
fun testNoPreferredPackagesNoDefaultSelected_noNewSelection() {
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
createStartable(enabled = true).start()
verify(controlsController, never()).setPreferredSelection(any())
}
@Test
fun testPreferredPackagesNotInstalled_noNewSelection() {
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE_PANEL)
)
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
`when`(controlsListingController.getCurrentServices()).thenReturn(emptyList())
createStartable(enabled = true).start()
verify(controlsController, never()).setPreferredSelection(any())
}
@Test
fun testPreferredPackageNotPanel_noNewSelection() {
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE_PANEL)
)
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "not panel", hasPanel = false))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
createStartable(enabled = true).start()
verify(controlsController, never()).setPreferredSelection(any())
}
@Test
fun testExistingSelection_noNewSelection() {
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE_PANEL)
)
`when`(controlsController.getPreferredSelection())
.thenReturn(mock<SelectedItem.PanelItem>())
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
createStartable(enabled = true).start()
verify(controlsController, never()).setPreferredSelection(any())
}
@Test
fun testPanelAdded() {
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE_PANEL)
)
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
createStartable(enabled = true).start()
verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
}
@Test
fun testMultiplePreferredOnlyOnePanel_panelAdded() {
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf("other_package", TEST_PACKAGE_PANEL)
)
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings =
listOf(
ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true),
ControlsServiceInfo(ComponentName("other_package", "cls"), "non panel", false)
)
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
createStartable(enabled = true).start()
verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
}
@Test
fun testMultiplePreferredMultiplePanels_firstPreferredAdded() {
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE_PANEL, "other_package")
)
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings =
listOf(
ControlsServiceInfo(ComponentName("other_package", "cls"), "panel", true),
ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)
)
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
createStartable(enabled = true).start()
verify(controlsController).setPreferredSelection(listings[1].toPanelItem())
}
@Test
fun testPreferredSelectionIsPanel_bindOnStart() {
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
`when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem())
createStartable(enabled = true).start()
verify(controlsController).bindComponentForPanel(TEST_COMPONENT_PANEL)
}
@Test
fun testPreferredSelectionPanel_listingNoPanel_notBind() {
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
`when`(controlsController.getPreferredSelection())
.thenReturn(SelectedItem.PanelItem("panel", TEST_COMPONENT_PANEL))
createStartable(enabled = true).start()
verify(controlsController, never()).bindComponentForPanel(any())
}
@Test
fun testNotPanelSelection_noBind() {
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
createStartable(enabled = true).start()
verify(controlsController, never()).bindComponentForPanel(any())
}
private fun createStartable(enabled: Boolean): ControlsStartable {
val component: ControlsComponent =
mock() {
`when`(isEnabled()).thenReturn(enabled)
if (enabled) {
`when`(getControlsController()).thenReturn(Optional.of(controlsController))
`when`(getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
} else {
`when`(getControlsController()).thenReturn(Optional.empty())
`when`(getControlsListingController()).thenReturn(Optional.empty())
}
}
return ControlsStartable(context.resources, fakeExecutor, component, userTracker)
}
private fun ControlsServiceInfo(
componentName: ComponentName,
label: CharSequence,
hasPanel: Boolean
): ControlsServiceInfo {
val serviceInfo =
ServiceInfo().apply {
applicationInfo = ApplicationInfo()
packageName = componentName.packageName
name = componentName.className
}
return FakeControlsServiceInfo(context, serviceInfo, label, hasPanel)
}
private class FakeControlsServiceInfo(
context: Context,
serviceInfo: ServiceInfo,
private val label: CharSequence,
hasPanel: Boolean
) : ControlsServiceInfo(context, serviceInfo) {
init {
if (hasPanel) {
panelActivity = serviceInfo.componentName
}
}
override fun loadLabel(): CharSequence {
return label
}
}
companion object {
private fun ControlsServiceInfo.toPanelItem(): SelectedItem.PanelItem {
if (panelActivity == null) {
throw IllegalArgumentException("$this is not a panel")
}
return SelectedItem.PanelItem(loadLabel(), componentName)
}
private const val TEST_PACKAGE = "pkg"
private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
private const val TEST_PACKAGE_PANEL = "pkg.panel"
private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
}
}