Merge "Controls UI - Better structure/app switching" into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-03-11 22:04:57 +00:00
committed by Android (Google) Code Review
6 changed files with 351 additions and 135 deletions

View File

@@ -31,7 +31,6 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy import dagger.Lazy
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -47,8 +46,6 @@ open class ControlsBindingControllerImpl @Inject constructor(
private const val TAG = "ControlsBindingControllerImpl" private const val TAG = "ControlsBindingControllerImpl"
} }
private val refreshing = AtomicBoolean(false)
private var currentUser = UserHandle.of(ActivityManager.getCurrentUser()) private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
override val currentUserId: Int override val currentUserId: Int
@@ -56,6 +53,12 @@ open class ControlsBindingControllerImpl @Inject constructor(
private var currentProvider: ControlsProviderLifecycleManager? = null private var currentProvider: ControlsProviderLifecycleManager? = null
/*
* Will track any active subscriber for subscribe/unsubscribe requests coming into
* this controller. Only one can be active at any time
*/
private var statefulControlSubscriber: StatefulControlSubscriber? = null
private val actionCallbackService = object : IControlsActionCallback.Stub() { private val actionCallbackService = object : IControlsActionCallback.Stub() {
override fun accept( override fun accept(
token: IBinder, token: IBinder,
@@ -66,27 +69,6 @@ open class ControlsBindingControllerImpl @Inject constructor(
} }
} }
private val subscriberService = object : IControlsSubscriber.Stub() {
override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
backgroundExecutor.execute(OnSubscribeRunnable(token, subs))
}
override fun onNext(token: IBinder, c: Control) {
if (!refreshing.get()) {
Log.d(TAG, "Refresh outside of window for token:$token")
} else {
backgroundExecutor.execute(OnNextRunnable(token, c))
}
}
override fun onError(token: IBinder, s: String) {
backgroundExecutor.execute(OnErrorRunnable(token, s))
}
override fun onComplete(token: IBinder) {
backgroundExecutor.execute(OnCompleteRunnable(token))
}
}
@VisibleForTesting @VisibleForTesting
internal open fun createProviderManager(component: ComponentName): internal open fun createProviderManager(component: ComponentName):
ControlsProviderLifecycleManager { ControlsProviderLifecycleManager {
@@ -94,23 +76,21 @@ open class ControlsBindingControllerImpl @Inject constructor(
context, context,
backgroundExecutor, backgroundExecutor,
actionCallbackService, actionCallbackService,
subscriberService,
currentUser, currentUser,
component component
) )
} }
private fun retrieveLifecycleManager(component: ComponentName): private fun retrieveLifecycleManager(component: ComponentName):
ControlsProviderLifecycleManager? { ControlsProviderLifecycleManager {
if (currentProvider != null && currentProvider?.componentName != component) { if (currentProvider != null && currentProvider?.componentName != component) {
unbind() unbind()
} }
if (currentProvider == null) { val provider = currentProvider ?: createProviderManager(component)
currentProvider = createProviderManager(component) currentProvider = provider
}
return currentProvider return provider
} }
override fun bindAndLoad( override fun bindAndLoad(
@@ -118,21 +98,23 @@ open class ControlsBindingControllerImpl @Inject constructor(
callback: ControlsBindingController.LoadCallback callback: ControlsBindingController.LoadCallback
): Runnable { ): Runnable {
val subscriber = LoadSubscriber(callback) val subscriber = LoadSubscriber(callback)
retrieveLifecycleManager(component)?.maybeBindAndLoad(subscriber) retrieveLifecycleManager(component).maybeBindAndLoad(subscriber)
return subscriber.loadCancel() return subscriber.loadCancel()
} }
override fun subscribe(structureInfo: StructureInfo) { override fun subscribe(structureInfo: StructureInfo) {
if (refreshing.compareAndSet(false, true)) { // make sure this has happened. only allow one active subscription
val provider = retrieveLifecycleManager(structureInfo.componentName) unsubscribe()
provider?.maybeBindAndSubscribe(structureInfo.controls.map { it.controlId })
} statefulControlSubscriber = null
val provider = retrieveLifecycleManager(structureInfo.componentName)
val scs = StatefulControlSubscriber(lazyController.get(), provider, backgroundExecutor)
statefulControlSubscriber = scs
provider.maybeBindAndSubscribe(structureInfo.controls.map { it.controlId }, scs)
} }
override fun unsubscribe() { override fun unsubscribe() {
if (refreshing.compareAndSet(true, false)) { statefulControlSubscriber?.cancel()
currentProvider?.unsubscribe()
}
} }
override fun action( override fun action(
@@ -140,20 +122,24 @@ open class ControlsBindingControllerImpl @Inject constructor(
controlInfo: ControlInfo, controlInfo: ControlInfo,
action: ControlAction action: ControlAction
) { ) {
retrieveLifecycleManager(componentName) if (statefulControlSubscriber == null) {
?.maybeBindAndSendAction(controlInfo.controlId, action) Log.w(TAG, "No actions can occur outside of an active subscription. Ignoring.")
} else {
retrieveLifecycleManager(componentName)
.maybeBindAndSendAction(controlInfo.controlId, action)
}
} }
override fun bindService(component: ComponentName) { override fun bindService(component: ComponentName) {
retrieveLifecycleManager(component)?.bindService() retrieveLifecycleManager(component).bindService()
} }
override fun changeUser(newUser: UserHandle) { override fun changeUser(newUser: UserHandle) {
if (newUser == currentUser) return if (newUser == currentUser) return
unsubscribe()
unbind() unbind()
currentProvider = null
refreshing.set(false)
currentUser = newUser currentUser = newUser
} }
@@ -174,8 +160,8 @@ open class ControlsBindingControllerImpl @Inject constructor(
override fun toString(): String { override fun toString(): String {
return StringBuilder(" ControlsBindingController:\n").apply { return StringBuilder(" ControlsBindingController:\n").apply {
append(" refreshing=${refreshing.get()}\n")
append(" currentUser=$currentUser\n") append(" currentUser=$currentUser\n")
append(" StatefulControlSubscriber=$statefulControlSubscriber")
append(" Providers=$currentProvider\n") append(" Providers=$currentProvider\n")
}.toString() }.toString()
} }
@@ -213,53 +199,12 @@ open class ControlsBindingControllerImpl @Inject constructor(
} }
} }
private inner class OnNextRunnable(
token: IBinder,
val control: Control
) : CallbackRunnable(token) {
override fun doRun() {
if (!refreshing.get()) {
Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}")
}
provider?.let {
lazyController.get().refreshStatus(it.componentName, control)
}
}
}
private inner class OnSubscribeRunnable( private inner class OnSubscribeRunnable(
token: IBinder, token: IBinder,
val subscription: IControlsSubscription val subscription: IControlsSubscription
) : CallbackRunnable(token) { ) : CallbackRunnable(token) {
override fun doRun() { override fun doRun() {
if (!refreshing.get()) { provider?.startSubscription(subscription)
Log.d(TAG, "onRefresh outside of window from '${provider?.componentName}'")
}
provider?.let {
it.startSubscription(subscription)
}
}
}
private inner class OnCompleteRunnable(
token: IBinder
) : CallbackRunnable(token) {
override fun doRun() {
provider?.let {
Log.i(TAG, "onComplete receive from '${it.componentName}'")
}
}
}
private inner class OnErrorRunnable(
token: IBinder,
val error: String
) : CallbackRunnable(token) {
override fun doRun() {
provider?.let {
Log.e(TAG, "onError receive from '${it.componentName}': $error")
}
} }
} }

View File

@@ -58,7 +58,6 @@ class ControlsProviderLifecycleManager(
private val context: Context, private val context: Context,
private val executor: DelayableExecutor, private val executor: DelayableExecutor,
private val actionCallbackService: IControlsActionCallback.Stub, private val actionCallbackService: IControlsActionCallback.Stub,
private val subscriberService: IControlsSubscriber.Stub,
val user: UserHandle, val user: UserHandle,
val componentName: ComponentName val componentName: ComponentName
) : IBinder.DeathRecipient { ) : IBinder.DeathRecipient {
@@ -157,10 +156,9 @@ class ControlsProviderLifecycleManager(
load(msg.subscriber) load(msg.subscriber)
} }
queue.filter { it is Message.Subscribe }.flatMap { (it as Message.Subscribe).list }.run { queue.filter { it is Message.Subscribe }.forEach {
if (this.isNotEmpty()) { val msg = it as Message.Subscribe
subscribe(this) subscribe(msg.list, msg.subscriber)
}
} }
queue.filter { it is Message.Action }.forEach { queue.filter { it is Message.Action }.forEach {
val msg = it as Message.Action val msg = it as Message.Action
@@ -185,9 +183,9 @@ class ControlsProviderLifecycleManager(
} }
} }
private fun unqueueMessage(message: Message) { private fun unqueueMessageType(type: Int) {
synchronized(queuedMessages) { synchronized(queuedMessages) {
queuedMessages.removeIf { it.type == message.type } queuedMessages.removeIf { it.type == type }
} }
} }
@@ -219,7 +217,7 @@ class ControlsProviderLifecycleManager(
* @param subscriber the subscriber that manages coordination for loading controls * @param subscriber the subscriber that manages coordination for loading controls
*/ */
fun maybeBindAndLoad(subscriber: IControlsSubscriber.Stub) { fun maybeBindAndLoad(subscriber: IControlsSubscriber.Stub) {
unqueueMessage(Message.Unbind) unqueueMessageType(MSG_UNBIND)
onLoadCanceller = executor.executeDelayed({ onLoadCanceller = executor.executeDelayed({
// Didn't receive a response in time, log and send back error // Didn't receive a response in time, log and send back error
Log.d(TAG, "Timeout waiting onLoad for $componentName") Log.d(TAG, "Timeout waiting onLoad for $componentName")
@@ -237,16 +235,20 @@ class ControlsProviderLifecycleManager(
* *
* @param controlIds a list of the ids of controls to send status back. * @param controlIds a list of the ids of controls to send status back.
*/ */
fun maybeBindAndSubscribe(controlIds: List<String>) { fun maybeBindAndSubscribe(controlIds: List<String>, subscriber: IControlsSubscriber) {
invokeOrQueue({ subscribe(controlIds) }, Message.Subscribe(controlIds)) invokeOrQueue(
{ subscribe(controlIds, subscriber) },
Message.Subscribe(controlIds, subscriber)
)
} }
private fun subscribe(controlIds: List<String>) { private fun subscribe(controlIds: List<String>, subscriber: IControlsSubscriber) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "subscribe $componentName - $controlIds") Log.d(TAG, "subscribe $componentName - $controlIds")
} }
if (!(wrapper?.subscribe(controlIds, subscriberService) ?: false)) {
queueMessage(Message.Subscribe(controlIds)) if (!(wrapper?.subscribe(controlIds, subscriber) ?: false)) {
queueMessage(Message.Subscribe(controlIds, subscriber))
binderDied() binderDied()
} }
} }
@@ -276,10 +278,13 @@ class ControlsProviderLifecycleManager(
/** /**
* Starts the subscription to the [ControlsProviderService] and requests status of controls. * Starts the subscription to the [ControlsProviderService] and requests status of controls.
* *
* @param subscription the subscriber to use to request controls * @param subscription the subscription to use to request controls
* @see maybeBindAndLoad * @see maybeBindAndLoad
*/ */
fun startSubscription(subscription: IControlsSubscription) { fun startSubscription(subscription: IControlsSubscription) {
if (DEBUG) {
Log.d(TAG, "startSubscription: $subscription")
}
synchronized(subscriptions) { synchronized(subscriptions) {
subscriptions.add(subscription) subscriptions.add(subscription)
} }
@@ -287,30 +292,26 @@ class ControlsProviderLifecycleManager(
} }
/** /**
* Unsubscribe from this service, cancelling all status requests. * Cancels the subscription to the [ControlsProviderService].
*
* @param subscription the subscription to cancel
* @see maybeBindAndLoad
*/ */
fun unsubscribe() { fun cancelSubscription(subscription: IControlsSubscription) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "unsubscribe $componentName") Log.d(TAG, "cancelSubscription: $subscription")
} }
unqueueMessage(Message.Subscribe(emptyList())) // Removes all subscribe messages synchronized(subscriptions) {
subscriptions.remove(subscription)
val subs = synchronized(subscriptions) {
ArrayList(subscriptions).also {
subscriptions.clear()
}
}
subs.forEach {
wrapper?.cancel(it)
} }
wrapper?.cancel(subscription)
} }
/** /**
* Request bind to the service. * Request bind to the service.
*/ */
fun bindService() { fun bindService() {
unqueueMessage(Message.Unbind) unqueueMessageType(MSG_UNBIND)
bindService(true) bindService(true)
} }
@@ -321,8 +322,16 @@ class ControlsProviderLifecycleManager(
onLoadCanceller?.run() onLoadCanceller?.run()
onLoadCanceller = null onLoadCanceller = null
// just in case this wasn't called already // be sure to cancel all subscriptions
unsubscribe() val subs = synchronized(subscriptions) {
ArrayList(subscriptions).also {
subscriptions.clear()
}
}
subs.forEach {
wrapper?.cancel(it)
}
bindService(false) bindService(false)
} }
@@ -346,7 +355,7 @@ class ControlsProviderLifecycleManager(
object Unbind : Message() { object Unbind : Message() {
override val type = MSG_UNBIND override val type = MSG_UNBIND
} }
class Subscribe(val list: List<String>) : Message() { class Subscribe(val list: List<String>, val subscriber: IControlsSubscriber) : Message() {
override val type = MSG_SUBSCRIBE override val type = MSG_SUBSCRIBE
} }
class Action(val id: String, val action: ControlAction) : Message() { class Action(val id: String, val action: ControlAction) : Message() {

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2020 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.controller
import android.os.IBinder
import android.service.controls.Control
import android.service.controls.IControlsSubscriber
import android.service.controls.IControlsSubscription
import android.util.Log
import com.android.systemui.util.concurrency.DelayableExecutor
/**
* A single subscriber, supporting stateful controls for publishers created by
* {@link ControlsProviderService#createPublisherFor}. In general, this subscription will remain
* active until the SysUi chooses to cancel it.
*/
class StatefulControlSubscriber(
private val controller: ControlsController,
private val provider: ControlsProviderLifecycleManager,
private val bgExecutor: DelayableExecutor
) : IControlsSubscriber.Stub() {
private var subscriptionOpen = false
private var subscription: IControlsSubscription? = null
companion object {
private const val TAG = "StatefulControlSubscriber"
}
private fun run(token: IBinder, f: () -> Unit) {
if (provider.token == token) {
bgExecutor.execute { f() }
}
}
override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
run(token) {
subscriptionOpen = true
subscription = subs
provider.startSubscription(subs)
}
}
override fun onNext(token: IBinder, control: Control) {
run(token) {
if (!subscriptionOpen) {
Log.w(TAG, "Refresh outside of window for token:$token")
} else {
controller.refreshStatus(provider.componentName, control)
}
}
}
override fun onError(token: IBinder, error: String) {
run(token) {
if (subscriptionOpen) {
subscriptionOpen = false
Log.e(TAG, "onError receive from '${provider.componentName}': $error")
}
}
}
override fun onComplete(token: IBinder) {
run(token) {
if (subscriptionOpen) {
subscriptionOpen = false
Log.i(TAG, "onComplete receive from '${provider.componentName}'")
}
}
}
fun cancel() {
if (!subscriptionOpen) return
bgExecutor.execute {
if (subscriptionOpen) {
subscriptionOpen = false
subscription?.let {
provider.cancelSubscription(it)
}
subscription = null
}
}
}
}

View File

@@ -52,8 +52,8 @@ import org.mockito.MockitoAnnotations
class ControlsBindingControllerImplTest : SysuiTestCase() { class ControlsBindingControllerImplTest : SysuiTestCase() {
companion object { companion object {
fun <T> any(): T = Mockito.any<T>()
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
fun <T> any(): T = Mockito.any<T>()
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")
@@ -61,8 +61,15 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
@Mock @Mock
private lateinit var mockControlsController: ControlsController private lateinit var mockControlsController: ControlsController
@Captor @Captor
private lateinit var subscriberCaptor: ArgumentCaptor<IControlsSubscriber.Stub> private lateinit var subscriberCaptor: ArgumentCaptor<IControlsSubscriber>
@Captor
private lateinit var loadSubscriberCaptor: ArgumentCaptor<IControlsSubscriber.Stub>
@Captor
private lateinit var listStringCaptor: ArgumentCaptor<List<String>>
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)
@@ -114,8 +121,8 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
val canceller = controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback) val canceller = controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
verify(providers[0]).maybeBindAndLoad(capture(subscriberCaptor)) verify(providers[0]).maybeBindAndLoad(capture(loadSubscriberCaptor))
subscriberCaptor.value.onSubscribe(Binder(), subscription) loadSubscriberCaptor.value.onSubscribe(Binder(), subscription)
canceller.run() canceller.run()
verify(subscription).cancel() verify(subscription).cancel()
@@ -132,11 +139,11 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
val canceller = controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback) val canceller = controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
verify(providers[0]).maybeBindAndLoad(capture(subscriberCaptor)) verify(providers[0]).maybeBindAndLoad(capture(loadSubscriberCaptor))
val b = Binder() val b = Binder()
subscriberCaptor.value.onSubscribe(b, subscription) loadSubscriberCaptor.value.onSubscribe(b, subscription)
subscriberCaptor.value.onComplete(b) loadSubscriberCaptor.value.onComplete(b)
canceller.run() canceller.run()
verify(subscription, never()).cancel() verify(subscription, never()).cancel()
} }
@@ -152,11 +159,11 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
val canceller = controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback) val canceller = controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
verify(providers[0]).maybeBindAndLoad(capture(subscriberCaptor)) verify(providers[0]).maybeBindAndLoad(capture(loadSubscriberCaptor))
val b = Binder() val b = Binder()
subscriberCaptor.value.onSubscribe(b, subscription) loadSubscriberCaptor.value.onSubscribe(b, subscription)
subscriberCaptor.value.onError(b, "") loadSubscriberCaptor.value.onError(b, "")
canceller.run() canceller.run()
verify(subscription, never()).cancel() verify(subscription, never()).cancel()
} }
@@ -180,8 +187,13 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
executor.runAllReady() executor.runAllReady()
val subs = mock(IControlsSubscription::class.java)
verify(providers[0]).maybeBindAndSubscribe( verify(providers[0]).maybeBindAndSubscribe(
capture(listStringCaptor), capture(subscriberCaptor))
assertEquals(listStringCaptor.value,
listOf(controlInfo1.controlId, controlInfo2.controlId)) listOf(controlInfo1.controlId, controlInfo2.controlId))
subscriberCaptor.value.onSubscribe(providers[0].token, subs)
} }
@Test @Test
@@ -191,7 +203,7 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
executor.runAllReady() executor.runAllReady()
verify(providers[0], never()).unsubscribe() verify(providers[0], never()).cancelSubscription(any())
} }
@Test @Test
@@ -202,12 +214,21 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
StructureInfo(TEST_COMPONENT_NAME_1, "Home", listOf(controlInfo1, controlInfo2)) StructureInfo(TEST_COMPONENT_NAME_1, "Home", listOf(controlInfo1, controlInfo2))
controller.subscribe(structure) controller.subscribe(structure)
controller.unsubscribe()
executor.runAllReady() executor.runAllReady()
verify(providers[0]).unsubscribe() val subs = mock(IControlsSubscription::class.java)
verify(providers[0]).maybeBindAndSubscribe(
capture(listStringCaptor), capture(subscriberCaptor))
assertEquals(listStringCaptor.value,
listOf(controlInfo1.controlId, controlInfo2.controlId))
subscriberCaptor.value.onSubscribe(providers[0].token, subs)
executor.runAllReady()
controller.unsubscribe()
executor.runAllReady()
verify(providers[0]).cancelSubscription(subs)
} }
@Test @Test

View File

@@ -1,7 +1,7 @@
/* /*
* Copyright (C) 2020 The Android Open Source Project * Copyright (C) 2020 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (149the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
@@ -83,7 +83,6 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
context, context,
executor, executor,
actionCallbackService, actionCallbackService,
subscriberService,
UserHandle.of(0), UserHandle.of(0),
componentName componentName
) )
@@ -146,7 +145,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
@Test @Test
fun testMaybeBindAndSubscribe() { fun testMaybeBindAndSubscribe() {
val list = listOf("TEST_ID") val list = listOf("TEST_ID")
manager.maybeBindAndSubscribe(list) manager.maybeBindAndSubscribe(list, subscriberService)
executor.runAllReady() executor.runAllReady()
assertTrue(mContext.isBound(componentName)) assertTrue(mContext.isBound(componentName))

View File

@@ -0,0 +1,146 @@
/*
* Copyright (C) 2020 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.controller
import android.content.ComponentName
import android.os.Binder
import android.service.controls.Control
import android.service.controls.IControlsSubscription
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
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.`when`
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class StatefulControlSubscriberTest : SysuiTestCase() {
@Mock
private lateinit var controller: ControlsController
@Mock
private lateinit var subscription: IControlsSubscription
@Mock
private lateinit var provider: ControlsProviderLifecycleManager
@Mock
private lateinit var control: Control
private val executor = FakeExecutor(FakeSystemClock())
private val token = Binder()
private val badToken = Binder()
private val TEST_COMPONENT = ComponentName("TEST_PKG", "TEST_CLS_1")
private lateinit var scs: StatefulControlSubscriber
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
`when`(provider.componentName).thenReturn(TEST_COMPONENT)
`when`(provider.token).thenReturn(token)
scs = StatefulControlSubscriber(controller, provider, executor)
}
@Test
fun testOnSubscribe() {
scs.onSubscribe(token, subscription)
executor.runAllReady()
verify(provider).startSubscription(subscription)
}
@Test
fun testOnSubscribe_badToken() {
scs.onSubscribe(badToken, subscription)
executor.runAllReady()
verify(provider, never()).startSubscription(subscription)
}
@Test
fun testOnNext() {
scs.onSubscribe(token, subscription)
scs.onNext(token, control)
executor.runAllReady()
verify(controller).refreshStatus(TEST_COMPONENT, control)
}
@Test
fun testOnNext_multiple() {
scs.onSubscribe(token, subscription)
scs.onNext(token, control)
scs.onNext(token, control)
scs.onNext(token, control)
executor.runAllReady()
verify(controller, times(3)).refreshStatus(TEST_COMPONENT, control)
}
@Test
fun testOnNext_noRefreshBeforeSubscribe() {
scs.onNext(token, control)
executor.runAllReady()
verify(controller, never()).refreshStatus(TEST_COMPONENT, control)
}
@Test
fun testOnNext_noRefreshAfterCancel() {
scs.onSubscribe(token, subscription)
executor.runAllReady()
scs.cancel()
scs.onNext(token, control)
executor.runAllReady()
verify(controller, never()).refreshStatus(TEST_COMPONENT, control)
}
@Test
fun testOnNext_noRefreshAfterError() {
scs.onSubscribe(token, subscription)
scs.onError(token, "Error")
scs.onNext(token, control)
executor.runAllReady()
verify(controller, never()).refreshStatus(TEST_COMPONENT, control)
}
@Test
fun testOnNext_noRefreshAfterComplete() {
scs.onSubscribe(token, subscription)
scs.onComplete(token)
scs.onNext(token, control)
executor.runAllReady()
verify(controller, never()).refreshStatus(TEST_COMPONENT, control)
}
}