Bind panels with lower priority flags

When showing a panel in device controls, bind to it (but with lower
priority flags). That way, the application is kept in memory and results
in shorter latency when relaunching the panel with the TaskView.

Use BIND_NOT_PERCEPTIBLE so it will be at lower priority from SystemUI.
However, don't use WAIVE_PRIORITY, as by itself, it will kill the app
once the Task is finished in the device controls panel.

Test: manual
Test: atest com.android.systemui.controls
Fixes: 263790347

Change-Id: Ia99ade528bfdbfe2f67ecfe78f687f18df8b6cfd
This commit is contained in:
Fabian Kozynski
2022-12-20 09:49:20 -05:00
parent a9a57f0550
commit 7173a3fa57
10 changed files with 79 additions and 4 deletions

View File

@@ -51,12 +51,21 @@ interface ControlsBindingController : UserAwareController {
fun bindAndLoadSuggested(component: ComponentName, callback: LoadCallback)
/**
* Request to bind to the given service.
* Request to bind to the given service. This should only be used for services using the full
* [ControlsProviderService] API, where SystemUI renders the devices' UI.
*
* @param component The [ComponentName] of the service to bind
*/
fun bindService(component: ComponentName)
/**
* Bind to a service that provides a Device Controls panel (embedded activity). This will allow
* the app to remain "warm", and reduce latency.
*
* @param component The [ComponentName] of the [ControlsProviderService] to bind.
*/
fun bindServiceForPanel(component: ComponentName)
/**
* Send a subscribe message to retrieve status of a set of controls.
*

View File

@@ -170,6 +170,10 @@ open class ControlsBindingControllerImpl @Inject constructor(
retrieveLifecycleManager(component).bindService()
}
override fun bindServiceForPanel(component: ComponentName) {
retrieveLifecycleManager(component).bindServiceForPanel()
}
override fun changeUser(newUser: UserHandle) {
if (newUser == currentUser) return

View File

@@ -188,6 +188,14 @@ interface ControlsController : UserAwareController {
/** See [ControlsUiController.getPreferredSelectedItem]. */
fun getPreferredSelection(): SelectedItem
/**
* Bind to a service that provides a Device Controls panel (embedded activity). This will allow
* the app to remain "warm", and reduce latency.
*
* @param component The [ComponentName] of the [ControlsProviderService] to bind.
*/
fun bindComponentForPanel(componentName: ComponentName)
/**
* Interface for structure to pass data to [ControlsFavoritingActivity].
*/

View File

@@ -477,6 +477,10 @@ class ControlsControllerImpl @Inject constructor (
bindingController.unsubscribe()
}
override fun bindComponentForPanel(componentName: ComponentName) {
bindingController.bindServiceForPanel(componentName)
}
override fun addFavorite(
componentName: ComponentName,
structureName: CharSequence,

View File

@@ -78,6 +78,10 @@ class ControlsProviderLifecycleManager(
private const val DEBUG = true
private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or
Context.BIND_NOT_PERCEPTIBLE
// Use BIND_NOT_PERCEPTIBLE so it will be at lower priority from SystemUI.
// However, don't use WAIVE_PRIORITY, as by itself, it will kill the app
// once the Task is finished in the device controls panel.
private val BIND_FLAGS_PANEL = Context.BIND_AUTO_CREATE or Context.BIND_NOT_PERCEPTIBLE
}
private val intent = Intent().apply {
@@ -87,18 +91,19 @@ class ControlsProviderLifecycleManager(
})
}
private fun bindService(bind: Boolean) {
private fun bindService(bind: Boolean, forPanel: Boolean = false) {
executor.execute {
requiresBound = bind
if (bind) {
if (bindTryCount != MAX_BIND_RETRIES) {
if (bindTryCount != MAX_BIND_RETRIES && wrapper == null) {
if (DEBUG) {
Log.d(TAG, "Binding service $intent")
}
bindTryCount++
try {
val flags = if (forPanel) BIND_FLAGS_PANEL else BIND_FLAGS
val bound = context
.bindServiceAsUser(intent, serviceConnection, BIND_FLAGS, user)
.bindServiceAsUser(intent, serviceConnection, flags, user)
if (!bound) {
context.unbindService(serviceConnection)
}
@@ -279,6 +284,10 @@ class ControlsProviderLifecycleManager(
bindService(true)
}
fun bindServiceForPanel() {
bindService(bind = true, forPanel = true)
}
/**
* Request unbind from the service.
*/

View File

@@ -232,6 +232,8 @@ class ControlsUiControllerImpl @Inject constructor (
ControlKey(selected.structure.componentName, it.ci.controlId)
}
controlsController.get().subscribeToFavorites(selected.structure)
} else {
controlsController.get().bindComponentForPanel(selected.componentName)
}
listingCallback = createCallback(::showControlsView)
}

View File

@@ -268,6 +268,14 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
verify(providers[0]).bindService()
}
@Test
fun testBindServiceForPanel() {
controller.bindServiceForPanel(TEST_COMPONENT_NAME_1)
executor.runAllReady()
verify(providers[0]).bindServiceForPanel()
}
@Test
fun testSubscribe() {
val controlInfo1 = ControlInfo("id_1", "", "", DeviceTypes.TYPE_UNKNOWN)

View File

@@ -919,6 +919,12 @@ class ControlsControllerImplTest : SysuiTestCase() {
.getFile(ControlsFavoritePersistenceWrapper.FILE_NAME, context.user.identifier)
assertThat(userStructure.file).isNotNull()
}
@Test
fun testBindForPanel() {
controller.bindComponentForPanel(TEST_COMPONENT)
verify(bindingController).bindServiceForPanel(TEST_COMPONENT)
}
}
private class DidRunRunnable() : Runnable {

View File

@@ -104,6 +104,22 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
assertTrue(context.isBound(componentName))
}
@Test
fun testBindForPanel() {
manager.bindServiceForPanel()
executor.runAllReady()
assertTrue(context.isBound(componentName))
}
@Test
fun testUnbindPanelIsUnbound() {
manager.bindServiceForPanel()
executor.runAllReady()
manager.unbindService()
executor.runAllReady()
assertFalse(context.isBound(componentName))
}
@Test
fun testNullBinding() {
val mockContext = mock(Context::class.java)

View File

@@ -228,6 +228,15 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
verify(controlsController, never()).refreshStatus(any(), any())
}
@Test
fun testPanelBindsForPanel() {
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
setUpPanel(panel)
underTest.show(parent, {}, context)
verify(controlsController).bindComponentForPanel(panel.componentName)
}
@Test
fun testPanelCallsTaskViewFactoryCreate() {
mockLayoutInflater()