Merge "Cancels loading when moving out of Favorites screen" into rvc-dev am: 6e2d2206d1

Change-Id: I41deeaa78cd295f3745428c323a73644cd1fc4c5
This commit is contained in:
Automerger Merge Worker
2020-03-11 20:15:38 +00:00
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 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.

View File

@@ -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))
} }

View File

@@ -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
* *

View File

@@ -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,

View File

@@ -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,15 +251,18 @@ class ControlsFavoritingActivity @Inject constructor(
} }
} }
requireViewById<Button>(R.id.done).setOnClickListener { doneButton = requireViewById<Button>(R.id.done).apply {
if (component == null) return@setOnClickListener isEnabled = false
listOfStructures.forEach { setOnClickListener {
val favoritesForStorage = it.model.favorites.map { it.build() } if (component == null) return@setOnClickListener
controller.replaceFavoritesForStructure(StructureInfo(component!!, it.structureName, listOfStructures.forEach {
favoritesForStorage)) 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() { override fun onDestroy() {
currentUserTracker.stopTracking() currentUserTracker.stopTracking()
listingController.removeCallback(listingCallback) listingController.removeCallback(listingCallback)
controller.cancelLoad()
super.onDestroy() super.onDestroy()
} }

View File

@@ -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)

View File

@@ -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)