Merge "Cancels loading when moving out of Favorites screen" into rvc-dev am: 6e2d2206d1
Change-Id: I41deeaa78cd295f3745428c323a73644cd1fc4c5
This commit is contained in:
@@ -38,8 +38,9 @@ interface ControlsBindingController : UserAwareController {
|
|||||||
*
|
*
|
||||||
* @param component The [ComponentName] of the service to bind
|
* @param component The [ComponentName] of the service to bind
|
||||||
* @param callback a callback to return the loaded controls to (or an error).
|
* @param callback a callback to return the loaded controls to (or an error).
|
||||||
|
* @return a runnable to cancel the load
|
||||||
*/
|
*/
|
||||||
fun bindAndLoad(component: ComponentName, callback: LoadCallback)
|
fun bindAndLoad(component: ComponentName, callback: LoadCallback): Runnable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request to bind to the given service.
|
* Request to bind to the given service.
|
||||||
|
|||||||
@@ -116,8 +116,10 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
|||||||
override fun bindAndLoad(
|
override fun bindAndLoad(
|
||||||
component: ComponentName,
|
component: ComponentName,
|
||||||
callback: ControlsBindingController.LoadCallback
|
callback: ControlsBindingController.LoadCallback
|
||||||
) {
|
): Runnable {
|
||||||
retrieveLifecycleManager(component)?.maybeBindAndLoad(LoadSubscriber(callback))
|
val subscriber = LoadSubscriber(callback)
|
||||||
|
retrieveLifecycleManager(component)?.maybeBindAndLoad(subscriber)
|
||||||
|
return subscriber.loadCancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun subscribe(structureInfo: StructureInfo) {
|
override fun subscribe(structureInfo: StructureInfo) {
|
||||||
@@ -208,7 +210,6 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
|||||||
) : CallbackRunnable(token) {
|
) : CallbackRunnable(token) {
|
||||||
override fun doRun() {
|
override fun doRun() {
|
||||||
callback.accept(list)
|
callback.accept(list)
|
||||||
provider?.unbindService()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,8 +293,14 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
|||||||
) : IControlsSubscriber.Stub() {
|
) : IControlsSubscriber.Stub() {
|
||||||
val loadedControls = ArrayList<Control>()
|
val loadedControls = ArrayList<Control>()
|
||||||
var hasError = false
|
var hasError = false
|
||||||
|
private var _loadCancelInternal: (() -> Unit)? = null
|
||||||
|
fun loadCancel() = Runnable {
|
||||||
|
Log.d(TAG, "Cancel load requested")
|
||||||
|
_loadCancelInternal?.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
|
override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
|
||||||
|
_loadCancelInternal = subs::cancel
|
||||||
backgroundExecutor.execute(OnSubscribeRunnable(token, subs))
|
backgroundExecutor.execute(OnSubscribeRunnable(token, subs))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,10 +309,12 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
override fun onError(token: IBinder, s: String) {
|
override fun onError(token: IBinder, s: String) {
|
||||||
hasError = true
|
hasError = true
|
||||||
|
_loadCancelInternal = {}
|
||||||
backgroundExecutor.execute(OnLoadErrorRunnable(token, s, callback))
|
backgroundExecutor.execute(OnLoadErrorRunnable(token, s, callback))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onComplete(token: IBinder) {
|
override fun onComplete(token: IBinder) {
|
||||||
|
_loadCancelInternal = {}
|
||||||
if (!hasError) {
|
if (!hasError) {
|
||||||
backgroundExecutor.execute(OnLoadRunnable(token, loadedControls, callback))
|
backgroundExecutor.execute(OnLoadRunnable(token, loadedControls, callback))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ interface ControlsController : UserAwareController {
|
|||||||
dataCallback: Consumer<LoadData>
|
dataCallback: Consumer<LoadData>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels a pending load call
|
||||||
|
*/
|
||||||
|
fun cancelLoad()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request to subscribe for favorited controls per structure
|
* Request to subscribe for favorited controls per structure
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
|
|
||||||
private var userChanging: Boolean = true
|
private var userChanging: Boolean = true
|
||||||
|
|
||||||
|
private var loadCanceller: Runnable? = null
|
||||||
|
|
||||||
private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
|
private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
|
||||||
override val currentUserId
|
override val currentUserId
|
||||||
get() = currentUser.identifier
|
get() = currentUser.identifier
|
||||||
@@ -213,8 +215,9 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
if (!confirmAvailability()) {
|
if (!confirmAvailability()) {
|
||||||
if (userChanging) {
|
if (userChanging) {
|
||||||
// Try again later, userChanging should not last forever. If so, we have bigger
|
// Try again later, userChanging should not last forever. If so, we have bigger
|
||||||
// problems
|
// problems. This will return a runnable that allows to cancel the delayed version,
|
||||||
executor.executeDelayed(
|
// it will not be able to cancel the load if
|
||||||
|
loadCanceller = executor.executeDelayed(
|
||||||
{ loadForComponent(componentName, dataCallback) },
|
{ loadForComponent(componentName, dataCallback) },
|
||||||
USER_CHANGE_RETRY_DELAY,
|
USER_CHANGE_RETRY_DELAY,
|
||||||
TimeUnit.MILLISECONDS
|
TimeUnit.MILLISECONDS
|
||||||
@@ -224,10 +227,11 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bindingController.bindAndLoad(
|
loadCanceller = bindingController.bindAndLoad(
|
||||||
componentName,
|
componentName,
|
||||||
object : ControlsBindingController.LoadCallback {
|
object : ControlsBindingController.LoadCallback {
|
||||||
override fun accept(controls: List<Control>) {
|
override fun accept(controls: List<Control>) {
|
||||||
|
loadCanceller = null
|
||||||
executor.execute {
|
executor.execute {
|
||||||
val favoritesForComponentKeys = Favorites
|
val favoritesForComponentKeys = Favorites
|
||||||
.getControlsForComponent(componentName).map { it.controlId }
|
.getControlsForComponent(componentName).map { it.controlId }
|
||||||
@@ -251,12 +255,12 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
controlsWithFavorite,
|
controlsWithFavorite,
|
||||||
favoritesForComponentKeys
|
favoritesForComponentKeys
|
||||||
)
|
)
|
||||||
|
|
||||||
dataCallback.accept(loadData)
|
dataCallback.accept(loadData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun error(message: String) {
|
override fun error(message: String) {
|
||||||
|
loadCanceller = null
|
||||||
executor.execute {
|
executor.execute {
|
||||||
val loadData = Favorites.getControlsForComponent(componentName)
|
val loadData = Favorites.getControlsForComponent(componentName)
|
||||||
.let { controls ->
|
.let { controls ->
|
||||||
@@ -269,7 +273,6 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
true
|
true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
dataCallback.accept(loadData)
|
dataCallback.accept(loadData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,6 +280,12 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun cancelLoad() {
|
||||||
|
loadCanceller?.let {
|
||||||
|
executor.execute(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createRemovedStatus(
|
private fun createRemovedStatus(
|
||||||
componentName: ComponentName,
|
componentName: ComponentName,
|
||||||
controlInfo: ControlInfo,
|
controlInfo: ControlInfo,
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ class ControlsFavoritingActivity @Inject constructor(
|
|||||||
private lateinit var iconFrame: View
|
private lateinit var iconFrame: View
|
||||||
private lateinit var pageIndicator: ManagementPageIndicator
|
private lateinit var pageIndicator: ManagementPageIndicator
|
||||||
private var mTooltipManager: TooltipManager? = null
|
private var mTooltipManager: TooltipManager? = null
|
||||||
|
private lateinit var doneButton: View
|
||||||
private var listOfStructures = emptyList<StructureContainer>()
|
private var listOfStructures = emptyList<StructureContainer>()
|
||||||
|
|
||||||
private lateinit var comparator: Comparator<StructureContainer>
|
private lateinit var comparator: Comparator<StructureContainer>
|
||||||
@@ -137,6 +138,7 @@ class ControlsFavoritingActivity @Inject constructor(
|
|||||||
StructureContainer(it.key, AllModel(it.value, favoriteKeys, emptyZoneString))
|
StructureContainer(it.key, AllModel(it.value, favoriteKeys, emptyZoneString))
|
||||||
}.sortedWith(comparator)
|
}.sortedWith(comparator)
|
||||||
executor.execute {
|
executor.execute {
|
||||||
|
doneButton.isEnabled = true
|
||||||
structurePager.adapter = StructureAdapter(listOfStructures)
|
structurePager.adapter = StructureAdapter(listOfStructures)
|
||||||
if (error) {
|
if (error) {
|
||||||
statusText.text = resources.getText(R.string.controls_favorite_load_error)
|
statusText.text = resources.getText(R.string.controls_favorite_load_error)
|
||||||
@@ -180,6 +182,7 @@ class ControlsFavoritingActivity @Inject constructor(
|
|||||||
layoutResource = R.layout.controls_management_favorites
|
layoutResource = R.layout.controls_management_favorites
|
||||||
inflate()
|
inflate()
|
||||||
}
|
}
|
||||||
|
|
||||||
statusText = requireViewById(R.id.status_message)
|
statusText = requireViewById(R.id.status_message)
|
||||||
if (shouldShowTooltip()) {
|
if (shouldShowTooltip()) {
|
||||||
mTooltipManager = TooltipManager(statusText.context,
|
mTooltipManager = TooltipManager(statusText.context,
|
||||||
@@ -248,17 +251,20 @@ class ControlsFavoritingActivity @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requireViewById<Button>(R.id.done).setOnClickListener {
|
doneButton = requireViewById<Button>(R.id.done).apply {
|
||||||
|
isEnabled = false
|
||||||
|
setOnClickListener {
|
||||||
if (component == null) return@setOnClickListener
|
if (component == null) return@setOnClickListener
|
||||||
listOfStructures.forEach {
|
listOfStructures.forEach {
|
||||||
val favoritesForStorage = it.model.favorites.map { it.build() }
|
val favoritesForStorage = it.model.favorites.map { it.build() }
|
||||||
controller.replaceFavoritesForStructure(StructureInfo(component!!, it.structureName,
|
controller.replaceFavoritesForStructure(
|
||||||
favoritesForStorage))
|
StructureInfo(component!!, it.structureName, favoritesForStorage)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
finishAffinity()
|
finishAffinity()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
@@ -273,6 +279,7 @@ class ControlsFavoritingActivity @Inject constructor(
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
currentUserTracker.stopTracking()
|
currentUserTracker.stopTracking()
|
||||||
listingController.removeCallback(listingCallback)
|
listingController.removeCallback(listingCallback)
|
||||||
|
controller.cancelLoad()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import android.os.Binder
|
|||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import android.service.controls.Control
|
import android.service.controls.Control
|
||||||
import android.service.controls.DeviceTypes
|
import android.service.controls.DeviceTypes
|
||||||
|
import android.service.controls.IControlsSubscriber
|
||||||
|
import android.service.controls.IControlsSubscription
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
@@ -34,6 +36,8 @@ import org.junit.Assert.assertEquals
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
|
import org.mockito.Captor
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
@@ -49,6 +53,7 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun <T> any(): T = Mockito.any<T>()
|
fun <T> any(): T = Mockito.any<T>()
|
||||||
|
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
||||||
private val TEST_COMPONENT_NAME_1 = ComponentName("TEST_PKG", "TEST_CLS_1")
|
private val TEST_COMPONENT_NAME_1 = ComponentName("TEST_PKG", "TEST_CLS_1")
|
||||||
private val TEST_COMPONENT_NAME_2 = ComponentName("TEST_PKG", "TEST_CLS_2")
|
private val TEST_COMPONENT_NAME_2 = ComponentName("TEST_PKG", "TEST_CLS_2")
|
||||||
private val TEST_COMPONENT_NAME_3 = ComponentName("TEST_PKG", "TEST_CLS_3")
|
private val TEST_COMPONENT_NAME_3 = ComponentName("TEST_PKG", "TEST_CLS_3")
|
||||||
@@ -56,6 +61,8 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var mockControlsController: ControlsController
|
private lateinit var mockControlsController: ControlsController
|
||||||
|
@Captor
|
||||||
|
private lateinit var subscriberCaptor: ArgumentCaptor<IControlsSubscriber.Stub>
|
||||||
|
|
||||||
private val user = UserHandle.of(mContext.userId)
|
private val user = UserHandle.of(mContext.userId)
|
||||||
private val otherUser = UserHandle.of(user.identifier + 1)
|
private val otherUser = UserHandle.of(user.identifier + 1)
|
||||||
@@ -96,6 +103,64 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
|
|||||||
verify(providers[0]).maybeBindAndLoad(any())
|
verify(providers[0]).maybeBindAndLoad(any())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBindAndLoad_cancel() {
|
||||||
|
val callback = object : ControlsBindingController.LoadCallback {
|
||||||
|
override fun error(message: String) {}
|
||||||
|
|
||||||
|
override fun accept(t: List<Control>) {}
|
||||||
|
}
|
||||||
|
val subscription = mock(IControlsSubscription::class.java)
|
||||||
|
|
||||||
|
val canceller = controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
|
||||||
|
|
||||||
|
verify(providers[0]).maybeBindAndLoad(capture(subscriberCaptor))
|
||||||
|
subscriberCaptor.value.onSubscribe(Binder(), subscription)
|
||||||
|
|
||||||
|
canceller.run()
|
||||||
|
verify(subscription).cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBindAndLoad_noCancelAfterOnComplete() {
|
||||||
|
val callback = object : ControlsBindingController.LoadCallback {
|
||||||
|
override fun error(message: String) {}
|
||||||
|
|
||||||
|
override fun accept(t: List<Control>) {}
|
||||||
|
}
|
||||||
|
val subscription = mock(IControlsSubscription::class.java)
|
||||||
|
|
||||||
|
val canceller = controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
|
||||||
|
|
||||||
|
verify(providers[0]).maybeBindAndLoad(capture(subscriberCaptor))
|
||||||
|
val b = Binder()
|
||||||
|
subscriberCaptor.value.onSubscribe(b, subscription)
|
||||||
|
|
||||||
|
subscriberCaptor.value.onComplete(b)
|
||||||
|
canceller.run()
|
||||||
|
verify(subscription, never()).cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBindAndLoad_noCancelAfterOnError() {
|
||||||
|
val callback = object : ControlsBindingController.LoadCallback {
|
||||||
|
override fun error(message: String) {}
|
||||||
|
|
||||||
|
override fun accept(t: List<Control>) {}
|
||||||
|
}
|
||||||
|
val subscription = mock(IControlsSubscription::class.java)
|
||||||
|
|
||||||
|
val canceller = controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
|
||||||
|
|
||||||
|
verify(providers[0]).maybeBindAndLoad(capture(subscriberCaptor))
|
||||||
|
val b = Binder()
|
||||||
|
subscriberCaptor.value.onSubscribe(b, subscription)
|
||||||
|
|
||||||
|
subscriberCaptor.value.onError(b, "")
|
||||||
|
canceller.run()
|
||||||
|
verify(subscription, never()).cancel()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBindService() {
|
fun testBindService() {
|
||||||
controller.bindService(TEST_COMPONENT_NAME_1)
|
controller.bindService(TEST_COMPONENT_NAME_1)
|
||||||
|
|||||||
@@ -345,6 +345,88 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
|||||||
assertTrue(loaded)
|
assertTrue(loaded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCancelLoad() {
|
||||||
|
val canceller = object : Runnable {
|
||||||
|
var ran = false
|
||||||
|
override fun run() {
|
||||||
|
ran = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
|
||||||
|
|
||||||
|
var loaded = false
|
||||||
|
controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
|
||||||
|
delayableExecutor.runAllReady()
|
||||||
|
controller.loadForComponent(TEST_COMPONENT, Consumer {
|
||||||
|
loaded = true
|
||||||
|
})
|
||||||
|
|
||||||
|
controller.cancelLoad()
|
||||||
|
delayableExecutor.runAllReady()
|
||||||
|
|
||||||
|
assertFalse(loaded)
|
||||||
|
assertTrue(canceller.ran)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCancelLoad_noCancelAfterSuccessfulLoad() {
|
||||||
|
val canceller = object : Runnable {
|
||||||
|
var ran = false
|
||||||
|
override fun run() {
|
||||||
|
ran = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
|
||||||
|
|
||||||
|
var loaded = false
|
||||||
|
controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
|
||||||
|
delayableExecutor.runAllReady()
|
||||||
|
controller.loadForComponent(TEST_COMPONENT, Consumer {
|
||||||
|
loaded = true
|
||||||
|
})
|
||||||
|
|
||||||
|
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
|
||||||
|
capture(controlLoadCallbackCaptor))
|
||||||
|
|
||||||
|
controlLoadCallbackCaptor.value.accept(emptyList())
|
||||||
|
|
||||||
|
controller.cancelLoad()
|
||||||
|
delayableExecutor.runAllReady()
|
||||||
|
|
||||||
|
assertTrue(loaded)
|
||||||
|
assertFalse(canceller.ran)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCancelLoad_noCancelAfterErrorLoad() {
|
||||||
|
val canceller = object : Runnable {
|
||||||
|
var ran = false
|
||||||
|
override fun run() {
|
||||||
|
ran = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
|
||||||
|
|
||||||
|
var loaded = false
|
||||||
|
controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
|
||||||
|
delayableExecutor.runAllReady()
|
||||||
|
controller.loadForComponent(TEST_COMPONENT, Consumer {
|
||||||
|
loaded = true
|
||||||
|
})
|
||||||
|
|
||||||
|
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
|
||||||
|
capture(controlLoadCallbackCaptor))
|
||||||
|
|
||||||
|
controlLoadCallbackCaptor.value.error("")
|
||||||
|
|
||||||
|
controller.cancelLoad()
|
||||||
|
delayableExecutor.runAllReady()
|
||||||
|
|
||||||
|
assertTrue(loaded)
|
||||||
|
assertFalse(canceller.ran)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFavoriteInformationModifiedOnLoad() {
|
fun testFavoriteInformationModifiedOnLoad() {
|
||||||
controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
|
controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
|
||||||
|
|||||||
Reference in New Issue
Block a user