Merge "Cancels loading when moving out of Favorites screen" into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-03-11 17:24:27 +00:00
committed by Android (Google) Code Review
7 changed files with 195 additions and 17 deletions

View File

@@ -38,8 +38,9 @@ interface ControlsBindingController : UserAwareController {
*
* @param component The [ComponentName] of the service to bind
* @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.

View File

@@ -116,8 +116,10 @@ open class ControlsBindingControllerImpl @Inject constructor(
override fun bindAndLoad(
component: ComponentName,
callback: ControlsBindingController.LoadCallback
) {
retrieveLifecycleManager(component)?.maybeBindAndLoad(LoadSubscriber(callback))
): Runnable {
val subscriber = LoadSubscriber(callback)
retrieveLifecycleManager(component)?.maybeBindAndLoad(subscriber)
return subscriber.loadCancel()
}
override fun subscribe(structureInfo: StructureInfo) {
@@ -208,7 +210,6 @@ open class ControlsBindingControllerImpl @Inject constructor(
) : CallbackRunnable(token) {
override fun doRun() {
callback.accept(list)
provider?.unbindService()
}
}
@@ -292,8 +293,14 @@ open class ControlsBindingControllerImpl @Inject constructor(
) : IControlsSubscriber.Stub() {
val loadedControls = ArrayList<Control>()
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) {
_loadCancelInternal = subs::cancel
backgroundExecutor.execute(OnSubscribeRunnable(token, subs))
}
@@ -302,10 +309,12 @@ open class ControlsBindingControllerImpl @Inject constructor(
}
override fun onError(token: IBinder, s: String) {
hasError = true
_loadCancelInternal = {}
backgroundExecutor.execute(OnLoadErrorRunnable(token, s, callback))
}
override fun onComplete(token: IBinder) {
_loadCancelInternal = {}
if (!hasError) {
backgroundExecutor.execute(OnLoadRunnable(token, loadedControls, callback))
}

View File

@@ -58,6 +58,11 @@ interface ControlsController : UserAwareController {
dataCallback: Consumer<LoadData>
)
/**
* Cancels a pending load call
*/
fun cancelLoad()
/**
* Request to subscribe for favorited controls per structure
*

View File

@@ -72,6 +72,8 @@ class ControlsControllerImpl @Inject constructor (
private var userChanging: Boolean = true
private var loadCanceller: Runnable? = null
private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
override val currentUserId
get() = currentUser.identifier
@@ -213,8 +215,9 @@ class ControlsControllerImpl @Inject constructor (
if (!confirmAvailability()) {
if (userChanging) {
// Try again later, userChanging should not last forever. If so, we have bigger
// problems
executor.executeDelayed(
// problems. This will return a runnable that allows to cancel the delayed version,
// it will not be able to cancel the load if
loadCanceller = executor.executeDelayed(
{ loadForComponent(componentName, dataCallback) },
USER_CHANGE_RETRY_DELAY,
TimeUnit.MILLISECONDS
@@ -224,10 +227,11 @@ class ControlsControllerImpl @Inject constructor (
}
return
}
bindingController.bindAndLoad(
loadCanceller = bindingController.bindAndLoad(
componentName,
object : ControlsBindingController.LoadCallback {
override fun accept(controls: List<Control>) {
loadCanceller = null
executor.execute {
val favoritesForComponentKeys = Favorites
.getControlsForComponent(componentName).map { it.controlId }
@@ -251,12 +255,12 @@ class ControlsControllerImpl @Inject constructor (
controlsWithFavorite,
favoritesForComponentKeys
)
dataCallback.accept(loadData)
}
}
override fun error(message: String) {
loadCanceller = null
executor.execute {
val loadData = Favorites.getControlsForComponent(componentName)
.let { controls ->
@@ -269,7 +273,6 @@ class ControlsControllerImpl @Inject constructor (
true
)
}
dataCallback.accept(loadData)
}
}
@@ -277,6 +280,12 @@ class ControlsControllerImpl @Inject constructor (
)
}
override fun cancelLoad() {
loadCanceller?.let {
executor.execute(it)
}
}
private fun createRemovedStatus(
componentName: ComponentName,
controlInfo: ControlInfo,

View File

@@ -70,6 +70,7 @@ class ControlsFavoritingActivity @Inject constructor(
private lateinit var iconFrame: View
private lateinit var pageIndicator: ManagementPageIndicator
private var mTooltipManager: TooltipManager? = null
private lateinit var doneButton: View
private var listOfStructures = emptyList<StructureContainer>()
private lateinit var comparator: Comparator<StructureContainer>
@@ -137,6 +138,7 @@ class ControlsFavoritingActivity @Inject constructor(
StructureContainer(it.key, AllModel(it.value, favoriteKeys, emptyZoneString))
}.sortedWith(comparator)
executor.execute {
doneButton.isEnabled = true
structurePager.adapter = StructureAdapter(listOfStructures)
if (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
inflate()
}
statusText = requireViewById(R.id.status_message)
if (shouldShowTooltip()) {
mTooltipManager = TooltipManager(statusText.context,
@@ -248,15 +251,18 @@ class ControlsFavoritingActivity @Inject constructor(
}
}
requireViewById<Button>(R.id.done).setOnClickListener {
if (component == null) return@setOnClickListener
listOfStructures.forEach {
val favoritesForStorage = it.model.favorites.map { it.build() }
controller.replaceFavoritesForStructure(StructureInfo(component!!, it.structureName,
favoritesForStorage))
doneButton = requireViewById<Button>(R.id.done).apply {
isEnabled = false
setOnClickListener {
if (component == null) return@setOnClickListener
listOfStructures.forEach {
val favoritesForStorage = it.model.favorites.map { it.build() }
controller.replaceFavoritesForStructure(
StructureInfo(component!!, it.structureName, favoritesForStorage)
)
}
finishAffinity()
}
finishAffinity()
}
}
@@ -273,6 +279,7 @@ class ControlsFavoritingActivity @Inject constructor(
override fun onDestroy() {
currentUserTracker.stopTracking()
listingController.removeCallback(listingCallback)
controller.cancelLoad()
super.onDestroy()
}

View File

@@ -22,6 +22,8 @@ import android.os.Binder
import android.os.UserHandle
import android.service.controls.Control
import android.service.controls.DeviceTypes
import android.service.controls.IControlsSubscriber
import android.service.controls.IControlsSubscription
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -34,6 +36,8 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when`
@@ -49,6 +53,7 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
companion object {
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_2 = ComponentName("TEST_PKG", "TEST_CLS_2")
private val TEST_COMPONENT_NAME_3 = ComponentName("TEST_PKG", "TEST_CLS_3")
@@ -56,6 +61,8 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
@Mock
private lateinit var mockControlsController: ControlsController
@Captor
private lateinit var subscriberCaptor: ArgumentCaptor<IControlsSubscriber.Stub>
private val user = UserHandle.of(mContext.userId)
private val otherUser = UserHandle.of(user.identifier + 1)
@@ -96,6 +103,64 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
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
fun testBindService() {
controller.bindService(TEST_COMPONENT_NAME_1)

View File

@@ -345,6 +345,88 @@ class ControlsControllerImplTest : SysuiTestCase() {
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
fun testFavoriteInformationModifiedOnLoad() {
controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)