Merge "Controls UI - Suggested controls" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
653604d01b
@@ -40,14 +40,20 @@
|
|||||||
android:paddingBottom="8dp" />
|
android:paddingBottom="8dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
style="@style/TextAppearance.ControlSetup.Title"
|
||||||
android:id="@+id/controls_title"
|
android:id="@+id/controls_title"
|
||||||
android:text="@string/quick_controls_title"
|
android:text="@string/quick_controls_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="true"
|
android:layout_gravity="center" />
|
||||||
android:layout_gravity="center"
|
|
||||||
android:textSize="25sp"
|
<TextView
|
||||||
android:textColor="@*android:color/foreground_material_dark"
|
style="@style/TextAppearance.ControlSetup.Subtitle"
|
||||||
android:fontFamily="@*android:string/config_headlineFontFamily" />
|
android:id="@+id/controls_subtitle"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</merge>
|
</merge>
|
||||||
|
|||||||
@@ -532,4 +532,8 @@
|
|||||||
<!-- Respect drawable/rounded.xml intrinsic size for multiple radius corner path customization -->
|
<!-- Respect drawable/rounded.xml intrinsic size for multiple radius corner path customization -->
|
||||||
<bool name="config_roundedCornerMultipleRadius">false</bool>
|
<bool name="config_roundedCornerMultipleRadius">false</bool>
|
||||||
|
|
||||||
|
<!-- Controls can query a preferred application for limited number of suggested controls.
|
||||||
|
This config value should contain the package name of that preferred application.
|
||||||
|
-->
|
||||||
|
<string translatable="false" name="config_controlsPreferredPackage"></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2657,4 +2657,8 @@
|
|||||||
|
|
||||||
<!-- Tooltip to show in management screen when there are multiple structures [CHAR_LIMIT=50] -->
|
<!-- Tooltip to show in management screen when there are multiple structures [CHAR_LIMIT=50] -->
|
||||||
<string name="controls_structure_tooltip">Swipe to see other structures</string>
|
<string name="controls_structure_tooltip">Swipe to see other structures</string>
|
||||||
|
|
||||||
|
<!-- Message to tell the user to wait while systemui attempts to load a set of
|
||||||
|
recommended controls [CHAR_LIMIT=30] -->
|
||||||
|
<string name="controls_seeding_in_progress">Loading recommendations</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -699,6 +699,20 @@
|
|||||||
<item name="*android:colorPopupBackground">@color/control_list_popup_background</item>
|
<item name="*android:colorPopupBackground">@color/control_list_popup_background</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="TextAppearance.ControlSetup">
|
||||||
|
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
|
||||||
|
<item name="android:textColor">@color/control_primary_text</item>
|
||||||
|
<item name="android:singleLine">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="TextAppearance.ControlSetup.Title">
|
||||||
|
<item name="android:textSize">25sp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="TextAppearance.ControlSetup.Subtitle">
|
||||||
|
<item name="android:textSize">16sp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="Theme.ControlsRequestDialog" parent="@style/Theme.SystemUI.MediaProjectionAlertDialog"/>
|
<style name="Theme.ControlsRequestDialog" parent="@style/Theme.SystemUI.MediaProjectionAlertDialog"/>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -42,6 +42,14 @@ interface ControlsBindingController : UserAwareController {
|
|||||||
*/
|
*/
|
||||||
fun bindAndLoad(component: ComponentName, callback: LoadCallback): Runnable
|
fun bindAndLoad(component: ComponentName, callback: LoadCallback): Runnable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request bind to a service and load a limited number of suggested controls.
|
||||||
|
*
|
||||||
|
* @param component The [ComponentName] of the service to bind
|
||||||
|
* @param callback a callback to return the loaded controls to (or an error).
|
||||||
|
*/
|
||||||
|
fun bindAndLoadSuggested(component: ComponentName, callback: LoadCallback)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request to bind to the given service.
|
* Request to bind to the given service.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "ControlsBindingControllerImpl"
|
private const val TAG = "ControlsBindingControllerImpl"
|
||||||
|
private const val MAX_CONTROLS_REQUEST = 100000L
|
||||||
|
private const val SUGGESTED_CONTROLS_REQUEST = 4L
|
||||||
}
|
}
|
||||||
|
|
||||||
private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
|
private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
|
||||||
@@ -97,24 +99,37 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
|||||||
component: ComponentName,
|
component: ComponentName,
|
||||||
callback: ControlsBindingController.LoadCallback
|
callback: ControlsBindingController.LoadCallback
|
||||||
): Runnable {
|
): Runnable {
|
||||||
val subscriber = LoadSubscriber(callback)
|
val subscriber = LoadSubscriber(callback, MAX_CONTROLS_REQUEST)
|
||||||
retrieveLifecycleManager(component).maybeBindAndLoad(subscriber)
|
retrieveLifecycleManager(component).maybeBindAndLoad(subscriber)
|
||||||
return subscriber.loadCancel()
|
return subscriber.loadCancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun bindAndLoadSuggested(
|
||||||
|
component: ComponentName,
|
||||||
|
callback: ControlsBindingController.LoadCallback
|
||||||
|
) {
|
||||||
|
val subscriber = LoadSubscriber(callback, SUGGESTED_CONTROLS_REQUEST)
|
||||||
|
retrieveLifecycleManager(component).maybeBindAndLoadSuggested(subscriber)
|
||||||
|
}
|
||||||
|
|
||||||
override fun subscribe(structureInfo: StructureInfo) {
|
override fun subscribe(structureInfo: StructureInfo) {
|
||||||
// make sure this has happened. only allow one active subscription
|
// make sure this has happened. only allow one active subscription
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
|
|
||||||
statefulControlSubscriber = null
|
|
||||||
val provider = retrieveLifecycleManager(structureInfo.componentName)
|
val provider = retrieveLifecycleManager(structureInfo.componentName)
|
||||||
val scs = StatefulControlSubscriber(lazyController.get(), provider, backgroundExecutor)
|
val scs = StatefulControlSubscriber(
|
||||||
|
lazyController.get(),
|
||||||
|
provider,
|
||||||
|
backgroundExecutor,
|
||||||
|
MAX_CONTROLS_REQUEST
|
||||||
|
)
|
||||||
statefulControlSubscriber = scs
|
statefulControlSubscriber = scs
|
||||||
provider.maybeBindAndSubscribe(structureInfo.controls.map { it.controlId }, scs)
|
provider.maybeBindAndSubscribe(structureInfo.controls.map { it.controlId }, scs)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unsubscribe() {
|
override fun unsubscribe() {
|
||||||
statefulControlSubscriber?.cancel()
|
statefulControlSubscriber?.cancel()
|
||||||
|
statefulControlSubscriber = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun action(
|
override fun action(
|
||||||
@@ -201,10 +216,11 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
|||||||
|
|
||||||
private inner class OnSubscribeRunnable(
|
private inner class OnSubscribeRunnable(
|
||||||
token: IBinder,
|
token: IBinder,
|
||||||
val subscription: IControlsSubscription
|
val subscription: IControlsSubscription,
|
||||||
|
val requestLimit: Long
|
||||||
) : CallbackRunnable(token) {
|
) : CallbackRunnable(token) {
|
||||||
override fun doRun() {
|
override fun doRun() {
|
||||||
provider?.startSubscription(subscription)
|
provider?.startSubscription(subscription, requestLimit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +250,8 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private inner class LoadSubscriber(
|
private inner class LoadSubscriber(
|
||||||
val callback: ControlsBindingController.LoadCallback
|
val callback: ControlsBindingController.LoadCallback,
|
||||||
|
val requestLimit: Long
|
||||||
) : IControlsSubscriber.Stub() {
|
) : IControlsSubscriber.Stub() {
|
||||||
val loadedControls = ArrayList<Control>()
|
val loadedControls = ArrayList<Control>()
|
||||||
var hasError = false
|
var hasError = false
|
||||||
@@ -246,7 +263,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
|||||||
|
|
||||||
override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
|
override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
|
||||||
_loadCancelInternal = subs::cancel
|
_loadCancelInternal = subs::cancel
|
||||||
backgroundExecutor.execute(OnSubscribeRunnable(token, subs))
|
backgroundExecutor.execute(OnSubscribeRunnable(token, subs, requestLimit))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNext(token: IBinder, c: Control) {
|
override fun onNext(token: IBinder, c: Control) {
|
||||||
|
|||||||
@@ -113,6 +113,25 @@ interface ControlsController : UserAwareController {
|
|||||||
|
|
||||||
// FAVORITE MANAGEMENT
|
// FAVORITE MANAGEMENT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a request to seed favorites into the persisted XML file
|
||||||
|
*
|
||||||
|
* @param componentName the component to seed controls from
|
||||||
|
* @param callback true if the favorites were persisted
|
||||||
|
*/
|
||||||
|
fun seedFavoritesForComponent(
|
||||||
|
componentName: ComponentName,
|
||||||
|
callback: Consumer<Boolean>
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to be informed when the seeding process has finished
|
||||||
|
*
|
||||||
|
* @param callback consumer accepts true if successful
|
||||||
|
* @return true if seeding is in progress and the callback was added
|
||||||
|
*/
|
||||||
|
fun addSeedingFavoritesCallback(callback: Consumer<Boolean>): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the favorites.
|
* Get all the favorites.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import android.os.UserHandle
|
|||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.service.controls.Control
|
import android.service.controls.Control
|
||||||
import android.service.controls.actions.ControlAction
|
import android.service.controls.actions.ControlAction
|
||||||
|
import android.util.ArrayMap
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.android.internal.annotations.VisibleForTesting
|
import com.android.internal.annotations.VisibleForTesting
|
||||||
import com.android.systemui.Dumpable
|
import com.android.systemui.Dumpable
|
||||||
@@ -74,6 +75,9 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
|
|
||||||
private var loadCanceller: Runnable? = null
|
private var loadCanceller: Runnable? = null
|
||||||
|
|
||||||
|
private var seedingInProgress = false
|
||||||
|
private val seedingCallbacks = mutableListOf<Consumer<Boolean>>()
|
||||||
|
|
||||||
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
|
||||||
@@ -280,6 +284,84 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun addSeedingFavoritesCallback(callback: Consumer<Boolean>): Boolean {
|
||||||
|
if (!seedingInProgress) return false
|
||||||
|
executor.execute {
|
||||||
|
// status may have changed by this point, so check again and inform the
|
||||||
|
// caller if necessary
|
||||||
|
if (seedingInProgress) seedingCallbacks.add(callback)
|
||||||
|
else callback.accept(false)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun seedFavoritesForComponent(
|
||||||
|
componentName: ComponentName,
|
||||||
|
callback: Consumer<Boolean>
|
||||||
|
) {
|
||||||
|
Log.i(TAG, "Beginning request to seed favorites for: $componentName")
|
||||||
|
if (!confirmAvailability()) {
|
||||||
|
if (userChanging) {
|
||||||
|
// Try again later, userChanging should not last forever. If so, we have bigger
|
||||||
|
// problems. This will return a runnable that allows to cancel the delayed version,
|
||||||
|
// it will not be able to cancel the load if
|
||||||
|
executor.executeDelayed(
|
||||||
|
{ seedFavoritesForComponent(componentName, callback) },
|
||||||
|
USER_CHANGE_RETRY_DELAY,
|
||||||
|
TimeUnit.MILLISECONDS
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
callback.accept(false)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seedingInProgress = true
|
||||||
|
bindingController.bindAndLoadSuggested(
|
||||||
|
componentName,
|
||||||
|
object : ControlsBindingController.LoadCallback {
|
||||||
|
override fun accept(controls: List<Control>) {
|
||||||
|
executor.execute {
|
||||||
|
val structureToControls =
|
||||||
|
ArrayMap<CharSequence, MutableList<ControlInfo>>()
|
||||||
|
|
||||||
|
controls.forEach {
|
||||||
|
val structure = it.structure ?: ""
|
||||||
|
val list = structureToControls.get(structure)
|
||||||
|
?: mutableListOf<ControlInfo>()
|
||||||
|
list.add(ControlInfo(it.controlId, it.title, it.deviceType))
|
||||||
|
structureToControls.put(structure, list)
|
||||||
|
}
|
||||||
|
|
||||||
|
structureToControls.forEach {
|
||||||
|
(s, cs) -> Favorites.replaceControls(
|
||||||
|
StructureInfo(componentName, s, cs))
|
||||||
|
}
|
||||||
|
|
||||||
|
persistenceWrapper.storeFavorites(Favorites.getAllStructures())
|
||||||
|
callback.accept(true)
|
||||||
|
endSeedingCall(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun error(message: String) {
|
||||||
|
Log.e(TAG, "Unable to seed favorites: $message")
|
||||||
|
executor.execute {
|
||||||
|
callback.accept(false)
|
||||||
|
endSeedingCall(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun endSeedingCall(state: Boolean) {
|
||||||
|
seedingInProgress = false
|
||||||
|
seedingCallbacks.forEach {
|
||||||
|
it.accept(state)
|
||||||
|
}
|
||||||
|
seedingCallbacks.clear()
|
||||||
|
}
|
||||||
|
|
||||||
override fun cancelLoad() {
|
override fun cancelLoad() {
|
||||||
loadCanceller?.let {
|
loadCanceller?.let {
|
||||||
executor.execute(it)
|
executor.execute(it)
|
||||||
|
|||||||
@@ -66,22 +66,17 @@ class ControlsProviderLifecycleManager(
|
|||||||
@GuardedBy("subscriptions")
|
@GuardedBy("subscriptions")
|
||||||
private val subscriptions = mutableListOf<IControlsSubscription>()
|
private val subscriptions = mutableListOf<IControlsSubscription>()
|
||||||
private var requiresBound = false
|
private var requiresBound = false
|
||||||
@GuardedBy("queuedMessages")
|
@GuardedBy("queuedServiceMethods")
|
||||||
private val queuedMessages: MutableSet<Message> = ArraySet()
|
private val queuedServiceMethods: MutableSet<ServiceMethod> = ArraySet()
|
||||||
private var wrapper: ServiceWrapper? = null
|
private var wrapper: ServiceWrapper? = null
|
||||||
private var bindTryCount = 0
|
private var bindTryCount = 0
|
||||||
private val TAG = javaClass.simpleName
|
private val TAG = javaClass.simpleName
|
||||||
private var onLoadCanceller: Runnable? = null
|
private var onLoadCanceller: Runnable? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val MSG_LOAD = 0
|
|
||||||
private const val MSG_SUBSCRIBE = 1
|
|
||||||
private const val MSG_ACTION = 2
|
|
||||||
private const val MSG_UNBIND = 3
|
|
||||||
private const val BIND_RETRY_DELAY = 1000L // ms
|
private const val BIND_RETRY_DELAY = 1000L // ms
|
||||||
private const val LOAD_TIMEOUT_SECONDS = 30L // seconds
|
private const val LOAD_TIMEOUT_SECONDS = 30L // seconds
|
||||||
private const val MAX_BIND_RETRIES = 5
|
private const val MAX_BIND_RETRIES = 5
|
||||||
private const val MAX_CONTROLS_REQUEST = 100000L
|
|
||||||
private const val DEBUG = true
|
private const val DEBUG = true
|
||||||
private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or
|
private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or
|
||||||
Context.BIND_WAIVE_PRIORITY
|
Context.BIND_WAIVE_PRIORITY
|
||||||
@@ -130,7 +125,7 @@ class ControlsProviderLifecycleManager(
|
|||||||
try {
|
try {
|
||||||
service.linkToDeath(this@ControlsProviderLifecycleManager, 0)
|
service.linkToDeath(this@ControlsProviderLifecycleManager, 0)
|
||||||
} catch (_: RemoteException) {}
|
} catch (_: RemoteException) {}
|
||||||
handlePendingMessages()
|
handlePendingServiceMethods()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName?) {
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
@@ -140,29 +135,14 @@ class ControlsProviderLifecycleManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePendingMessages() {
|
private fun handlePendingServiceMethods() {
|
||||||
val queue = synchronized(queuedMessages) {
|
val queue = synchronized(queuedServiceMethods) {
|
||||||
ArraySet(queuedMessages).also {
|
ArraySet(queuedServiceMethods).also {
|
||||||
queuedMessages.clear()
|
queuedServiceMethods.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Message.Unbind in queue) {
|
queue.forEach {
|
||||||
bindService(false)
|
it.run()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.filter { it is Message.Load }.forEach {
|
|
||||||
val msg = it as Message.Load
|
|
||||||
load(msg.subscriber)
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.filter { it is Message.Subscribe }.forEach {
|
|
||||||
val msg = it as Message.Subscribe
|
|
||||||
subscribe(msg.list, msg.subscriber)
|
|
||||||
}
|
|
||||||
queue.filter { it is Message.Action }.forEach {
|
|
||||||
val msg = it as Message.Action
|
|
||||||
action(msg.id, msg.action)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,33 +157,17 @@ class ControlsProviderLifecycleManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun queueMessage(message: Message) {
|
private fun queueServiceMethod(sm: ServiceMethod) {
|
||||||
synchronized(queuedMessages) {
|
synchronized(queuedServiceMethods) {
|
||||||
queuedMessages.add(message)
|
queuedServiceMethods.add(sm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unqueueMessageType(type: Int) {
|
private fun invokeOrQueue(sm: ServiceMethod) {
|
||||||
synchronized(queuedMessages) {
|
|
||||||
queuedMessages.removeIf { it.type == type }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun load(subscriber: IControlsSubscriber.Stub) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "load $componentName")
|
|
||||||
}
|
|
||||||
if (!(wrapper?.load(subscriber) ?: false)) {
|
|
||||||
queueMessage(Message.Load(subscriber))
|
|
||||||
binderDied()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun invokeOrQueue(f: () -> Unit, msg: Message) {
|
|
||||||
wrapper?.run {
|
wrapper?.run {
|
||||||
f()
|
sm.run()
|
||||||
} ?: run {
|
} ?: run {
|
||||||
queueMessage(msg)
|
queueServiceMethod(sm)
|
||||||
bindService(true)
|
bindService(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,7 +181,6 @@ 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) {
|
||||||
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")
|
||||||
@@ -225,7 +188,26 @@ class ControlsProviderLifecycleManager(
|
|||||||
unbindService()
|
unbindService()
|
||||||
}, LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
}, LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
|
||||||
invokeOrQueue({ load(subscriber) }, Message.Load(subscriber))
|
invokeOrQueue(Load(subscriber))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a call to [IControlsProvider.loadSuggested].
|
||||||
|
*
|
||||||
|
* If the service is not bound, the call will be queued and the service will be bound first.
|
||||||
|
* The service will be unbound after the controls are returned or the call times out.
|
||||||
|
*
|
||||||
|
* @param subscriber the subscriber that manages coordination for loading controls
|
||||||
|
*/
|
||||||
|
fun maybeBindAndLoadSuggested(subscriber: IControlsSubscriber.Stub) {
|
||||||
|
onLoadCanceller = executor.executeDelayed({
|
||||||
|
// Didn't receive a response in time, log and send back error
|
||||||
|
Log.d(TAG, "Timeout waiting onLoadSuggested for $componentName")
|
||||||
|
subscriber.onError(token, "Timeout waiting onLoadSuggested")
|
||||||
|
unbindService()
|
||||||
|
}, LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
|
||||||
|
invokeOrQueue(Suggest(subscriber))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelLoadTimeout() {
|
fun cancelLoadTimeout() {
|
||||||
@@ -240,23 +222,8 @@ 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>, subscriber: IControlsSubscriber) {
|
fun maybeBindAndSubscribe(controlIds: List<String>, subscriber: IControlsSubscriber) =
|
||||||
invokeOrQueue(
|
invokeOrQueue(Subscribe(controlIds, subscriber))
|
||||||
{ subscribe(controlIds, subscriber) },
|
|
||||||
Message.Subscribe(controlIds, subscriber)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun subscribe(controlIds: List<String>, subscriber: IControlsSubscriber) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "subscribe $componentName - $controlIds")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(wrapper?.subscribe(controlIds, subscriber) ?: false)) {
|
|
||||||
queueMessage(Message.Subscribe(controlIds, subscriber))
|
|
||||||
binderDied()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request a call to [ControlsProviderService.performControlAction].
|
* Request a call to [ControlsProviderService.performControlAction].
|
||||||
@@ -266,19 +233,8 @@ class ControlsProviderLifecycleManager(
|
|||||||
* @param controlId the id of the [Control] the action is performed on
|
* @param controlId the id of the [Control] the action is performed on
|
||||||
* @param action the action performed
|
* @param action the action performed
|
||||||
*/
|
*/
|
||||||
fun maybeBindAndSendAction(controlId: String, action: ControlAction) {
|
fun maybeBindAndSendAction(controlId: String, action: ControlAction) =
|
||||||
invokeOrQueue({ action(controlId, action) }, Message.Action(controlId, action))
|
invokeOrQueue(Action(controlId, action))
|
||||||
}
|
|
||||||
|
|
||||||
private fun action(controlId: String, action: ControlAction) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onAction $componentName - $controlId")
|
|
||||||
}
|
|
||||||
if (!(wrapper?.action(controlId, action, actionCallbackService) ?: false)) {
|
|
||||||
queueMessage(Message.Action(controlId, action))
|
|
||||||
binderDied()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the subscription to the [ControlsProviderService] and requests status of controls.
|
* Starts the subscription to the [ControlsProviderService] and requests status of controls.
|
||||||
@@ -286,14 +242,14 @@ class ControlsProviderLifecycleManager(
|
|||||||
* @param subscription the subscription 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, requestLimit: Long) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "startSubscription: $subscription")
|
Log.d(TAG, "startSubscription: $subscription")
|
||||||
}
|
}
|
||||||
synchronized(subscriptions) {
|
synchronized(subscriptions) {
|
||||||
subscriptions.add(subscription)
|
subscriptions.add(subscription)
|
||||||
}
|
}
|
||||||
wrapper?.request(subscription, MAX_CONTROLS_REQUEST)
|
wrapper?.request(subscription, requestLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -316,7 +272,6 @@ class ControlsProviderLifecycleManager(
|
|||||||
* Request bind to the service.
|
* Request bind to the service.
|
||||||
*/
|
*/
|
||||||
fun bindService() {
|
fun bindService() {
|
||||||
unqueueMessageType(MSG_UNBIND)
|
|
||||||
bindService(true)
|
bindService(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,21 +305,55 @@ class ControlsProviderLifecycleManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Messages for the internal queue.
|
* Service methods that can be queued or invoked, and are retryable for failure scenarios
|
||||||
*/
|
*/
|
||||||
sealed class Message {
|
abstract inner class ServiceMethod {
|
||||||
abstract val type: Int
|
fun run() {
|
||||||
class Load(val subscriber: IControlsSubscriber.Stub) : Message() {
|
if (!callWrapper()) {
|
||||||
override val type = MSG_LOAD
|
queueServiceMethod(this)
|
||||||
|
binderDied()
|
||||||
}
|
}
|
||||||
object Unbind : Message() {
|
|
||||||
override val type = MSG_UNBIND
|
|
||||||
}
|
}
|
||||||
class Subscribe(val list: List<String>, val subscriber: IControlsSubscriber) : Message() {
|
|
||||||
override val type = MSG_SUBSCRIBE
|
internal abstract fun callWrapper(): Boolean
|
||||||
}
|
}
|
||||||
class Action(val id: String, val action: ControlAction) : Message() {
|
|
||||||
override val type = MSG_ACTION
|
inner class Load(val subscriber: IControlsSubscriber.Stub) : ServiceMethod() {
|
||||||
|
override fun callWrapper(): Boolean {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "load $componentName")
|
||||||
|
}
|
||||||
|
return wrapper?.load(subscriber) ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class Suggest(val subscriber: IControlsSubscriber.Stub) : ServiceMethod() {
|
||||||
|
override fun callWrapper(): Boolean {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "suggest $componentName")
|
||||||
|
}
|
||||||
|
return wrapper?.loadSuggested(subscriber) ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inner class Subscribe(
|
||||||
|
val list: List<String>,
|
||||||
|
val subscriber: IControlsSubscriber
|
||||||
|
) : ServiceMethod() {
|
||||||
|
override fun callWrapper(): Boolean {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "subscribe $componentName - $list")
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapper?.subscribe(list, subscriber) ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class Action(val id: String, val action: ControlAction) : ServiceMethod() {
|
||||||
|
override fun callWrapper(): Boolean {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onAction $componentName - $id")
|
||||||
|
}
|
||||||
|
return wrapper?.action(id, action, actionCallbackService) ?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ class ServiceWrapper(val service: IControlsProvider) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadSuggested(subscriber: IControlsSubscriber): Boolean {
|
||||||
|
return callThroughService {
|
||||||
|
service.loadSuggested(subscriber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun subscribe(controlIds: List<String>, subscriber: IControlsSubscriber): Boolean {
|
fun subscribe(controlIds: List<String>, subscriber: IControlsSubscriber): Boolean {
|
||||||
return callThroughService {
|
return callThroughService {
|
||||||
service.subscribe(controlIds, subscriber)
|
service.subscribe(controlIds, subscriber)
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ import com.android.systemui.util.concurrency.DelayableExecutor
|
|||||||
class StatefulControlSubscriber(
|
class StatefulControlSubscriber(
|
||||||
private val controller: ControlsController,
|
private val controller: ControlsController,
|
||||||
private val provider: ControlsProviderLifecycleManager,
|
private val provider: ControlsProviderLifecycleManager,
|
||||||
private val bgExecutor: DelayableExecutor
|
private val bgExecutor: DelayableExecutor,
|
||||||
|
private val requestLimit: Long
|
||||||
) : IControlsSubscriber.Stub() {
|
) : IControlsSubscriber.Stub() {
|
||||||
private var subscriptionOpen = false
|
private var subscriptionOpen = false
|
||||||
private var subscription: IControlsSubscription? = null
|
private var subscription: IControlsSubscription? = null
|
||||||
@@ -50,7 +51,7 @@ class StatefulControlSubscriber(
|
|||||||
run(token) {
|
run(token) {
|
||||||
subscriptionOpen = true
|
subscriptionOpen = true
|
||||||
subscription = subs
|
subscription = subs
|
||||||
provider.startSubscription(subs)
|
provider.startSubscription(subs, requestLimit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class ControlsRequestDialog @Inject constructor(
|
|||||||
private lateinit var control: Control
|
private lateinit var control: Control
|
||||||
private var dialog: Dialog? = null
|
private var dialog: Dialog? = null
|
||||||
private val callback = object : ControlsListingController.ControlsListingCallback {
|
private val callback = object : ControlsListingController.ControlsListingCallback {
|
||||||
override fun onServicesUpdated(candidates: List<ControlsServiceInfo>) {}
|
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
|
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import com.android.systemui.R
|
|||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
|
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -89,6 +90,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
private var popup: ListPopupWindow? = null
|
private var popup: ListPopupWindow? = null
|
||||||
private var activeDialog: Dialog? = null
|
private var activeDialog: Dialog? = null
|
||||||
private val addControlsItem: SelectionItem
|
private val addControlsItem: SelectionItem
|
||||||
|
private var hidden = true
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val addDrawable = context.getDrawable(R.drawable.ic_add).apply {
|
val addDrawable = context.getDrawable(R.drawable.ic_add).apply {
|
||||||
@@ -134,11 +136,15 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
override fun show(parent: ViewGroup) {
|
override fun show(parent: ViewGroup) {
|
||||||
Log.d(ControlsUiController.TAG, "show()")
|
Log.d(ControlsUiController.TAG, "show()")
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
|
hidden = false
|
||||||
|
|
||||||
allStructures = controlsController.get().getFavorites()
|
allStructures = controlsController.get().getFavorites()
|
||||||
selectedStructure = loadPreference(allStructures)
|
selectedStructure = loadPreference(allStructures)
|
||||||
|
|
||||||
if (selectedStructure.controls.isEmpty() && allStructures.size <= 1) {
|
val cb = Consumer<Boolean> { _ -> reload(parent) }
|
||||||
|
if (controlsController.get().addSeedingFavoritesCallback(cb)) {
|
||||||
|
listingCallback = createCallback(::showSeedingView)
|
||||||
|
} else if (selectedStructure.controls.isEmpty() && allStructures.size <= 1) {
|
||||||
// only show initial view if there are really no favorites across any structure
|
// only show initial view if there are really no favorites across any structure
|
||||||
listingCallback = createCallback(::showInitialSetupView)
|
listingCallback = createCallback(::showInitialSetupView)
|
||||||
} else {
|
} else {
|
||||||
@@ -154,6 +160,20 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
controlsListingController.get().addCallback(listingCallback)
|
controlsListingController.get().addCallback(listingCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun reload(parent: ViewGroup) {
|
||||||
|
if (hidden) return
|
||||||
|
show(parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSeedingView(items: List<SelectionItem>) {
|
||||||
|
parent.removeAllViews()
|
||||||
|
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
inflater.inflate(R.layout.controls_no_favorites, parent, true)
|
||||||
|
val subtitle = parent.requireViewById<TextView>(R.id.controls_subtitle)
|
||||||
|
subtitle.setVisibility(View.VISIBLE)
|
||||||
|
}
|
||||||
|
|
||||||
private fun showInitialSetupView(items: List<SelectionItem>) {
|
private fun showInitialSetupView(items: List<SelectionItem>) {
|
||||||
parent.removeAllViews()
|
parent.removeAllViews()
|
||||||
|
|
||||||
@@ -320,13 +340,14 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
selectedStructure = newSelection
|
selectedStructure = newSelection
|
||||||
updatePreferences(selectedStructure)
|
updatePreferences(selectedStructure)
|
||||||
controlsListingController.get().removeCallback(listingCallback)
|
controlsListingController.get().removeCallback(listingCallback)
|
||||||
show(parent)
|
reload(parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hide() {
|
override fun hide() {
|
||||||
Log.d(ControlsUiController.TAG, "hide()")
|
Log.d(ControlsUiController.TAG, "hide()")
|
||||||
|
hidden = true
|
||||||
popup?.dismiss()
|
popup?.dismiss()
|
||||||
activeDialog?.dismiss()
|
activeDialog?.dismiss()
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,13 @@ import android.app.WallpaperManager;
|
|||||||
import android.app.admin.DevicePolicyManager;
|
import android.app.admin.DevicePolicyManager;
|
||||||
import android.app.trust.TrustManager;
|
import android.app.trust.TrustManager;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.UserInfo;
|
import android.content.pm.UserInfo;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
@@ -92,6 +94,8 @@ import com.android.systemui.MultiListLayout;
|
|||||||
import com.android.systemui.MultiListLayout.MultiListAdapter;
|
import com.android.systemui.MultiListLayout.MultiListAdapter;
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||||
import com.android.systemui.colorextraction.SysuiColorExtractor;
|
import com.android.systemui.colorextraction.SysuiColorExtractor;
|
||||||
|
import com.android.systemui.controls.ControlsServiceInfo;
|
||||||
|
import com.android.systemui.controls.controller.ControlsController;
|
||||||
import com.android.systemui.controls.management.ControlsListingController;
|
import com.android.systemui.controls.management.ControlsListingController;
|
||||||
import com.android.systemui.controls.ui.ControlsUiController;
|
import com.android.systemui.controls.ui.ControlsUiController;
|
||||||
import com.android.systemui.dagger.qualifiers.Background;
|
import com.android.systemui.dagger.qualifiers.Background;
|
||||||
@@ -148,6 +152,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
|
|||||||
private static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
|
private static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
|
||||||
private static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
|
private static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
|
||||||
|
|
||||||
|
private static final String PREFS_CONTROLS_SEEDING_COMPLETED = "ControlsSeedingCompleted";
|
||||||
|
private static final String PREFS_CONTROLS_FILE = "controls_prefs";
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final GlobalActionsManager mWindowManagerFuncs;
|
private final GlobalActionsManager mWindowManagerFuncs;
|
||||||
private final AudioManager mAudioManager;
|
private final AudioManager mAudioManager;
|
||||||
@@ -215,7 +222,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
|
|||||||
NotificationShadeWindowController notificationShadeWindowController,
|
NotificationShadeWindowController notificationShadeWindowController,
|
||||||
ControlsUiController controlsUiController, IWindowManager iWindowManager,
|
ControlsUiController controlsUiController, IWindowManager iWindowManager,
|
||||||
@Background Executor backgroundExecutor,
|
@Background Executor backgroundExecutor,
|
||||||
ControlsListingController controlsListingController) {
|
ControlsListingController controlsListingController,
|
||||||
|
ControlsController controlsController) {
|
||||||
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
|
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
|
||||||
mWindowManagerFuncs = windowManagerFuncs;
|
mWindowManagerFuncs = windowManagerFuncs;
|
||||||
mAudioManager = audioManager;
|
mAudioManager = audioManager;
|
||||||
@@ -279,8 +287,45 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mControlsListingController.addCallback(list -> mAnyControlsProviders = !list.isEmpty());
|
String preferredControlsPackage = mContext.getResources()
|
||||||
|
.getString(com.android.systemui.R.string.config_controlsPreferredPackage);
|
||||||
|
mControlsListingController.addCallback(list -> {
|
||||||
|
mAnyControlsProviders = !list.isEmpty();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See if any service providers match the preferred component. If they do,
|
||||||
|
* and there are no current favorites, and we haven't successfully loaded favorites to
|
||||||
|
* date, query the preferred component for a limited number of suggested controls.
|
||||||
|
*/
|
||||||
|
ComponentName preferredComponent = null;
|
||||||
|
for (ControlsServiceInfo info : list) {
|
||||||
|
if (info.componentName.getPackageName().equals(preferredControlsPackage)) {
|
||||||
|
preferredComponent = info.componentName;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferredComponent == null) return;
|
||||||
|
|
||||||
|
SharedPreferences prefs = context.getSharedPreferences(PREFS_CONTROLS_FILE,
|
||||||
|
Context.MODE_PRIVATE);
|
||||||
|
boolean isSeeded = prefs.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false);
|
||||||
|
boolean hasFavorites = controlsController.getFavorites().size() > 0;
|
||||||
|
if (!isSeeded && !hasFavorites) {
|
||||||
|
controlsController.seedFavoritesForComponent(
|
||||||
|
preferredComponent,
|
||||||
|
(accepted) -> {
|
||||||
|
Log.i(TAG, "Controls seeded: " + accepted);
|
||||||
|
prefs.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED,
|
||||||
|
accepted).apply();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the global actions dialog (creating if necessary)
|
* Show the global actions dialog (creating if necessary)
|
||||||
|
|||||||
@@ -206,6 +206,56 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
|
|||||||
verify(subscription, never()).cancel()
|
verify(subscription, never()).cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBindAndLoadSuggested() {
|
||||||
|
val callback = object : ControlsBindingController.LoadCallback {
|
||||||
|
override fun error(message: String) {}
|
||||||
|
|
||||||
|
override fun accept(t: List<Control>) {}
|
||||||
|
}
|
||||||
|
controller.bindAndLoadSuggested(TEST_COMPONENT_NAME_1, callback)
|
||||||
|
|
||||||
|
verify(providers[0]).maybeBindAndLoadSuggested(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLoadSuggested_onCompleteRemovesTimeout() {
|
||||||
|
val callback = object : ControlsBindingController.LoadCallback {
|
||||||
|
override fun error(message: String) {}
|
||||||
|
|
||||||
|
override fun accept(t: List<Control>) {}
|
||||||
|
}
|
||||||
|
val subscription = mock(IControlsSubscription::class.java)
|
||||||
|
|
||||||
|
controller.bindAndLoadSuggested(TEST_COMPONENT_NAME_1, callback)
|
||||||
|
|
||||||
|
verify(providers[0]).maybeBindAndLoadSuggested(capture(subscriberCaptor))
|
||||||
|
val b = Binder()
|
||||||
|
subscriberCaptor.value.onSubscribe(b, subscription)
|
||||||
|
|
||||||
|
subscriberCaptor.value.onComplete(b)
|
||||||
|
verify(providers[0]).cancelLoadTimeout()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLoadSuggested_onErrorRemovesTimeout() {
|
||||||
|
val callback = object : ControlsBindingController.LoadCallback {
|
||||||
|
override fun error(message: String) {}
|
||||||
|
|
||||||
|
override fun accept(t: List<Control>) {}
|
||||||
|
}
|
||||||
|
val subscription = mock(IControlsSubscription::class.java)
|
||||||
|
|
||||||
|
controller.bindAndLoadSuggested(TEST_COMPONENT_NAME_1, callback)
|
||||||
|
|
||||||
|
verify(providers[0]).maybeBindAndLoadSuggested(capture(subscriberCaptor))
|
||||||
|
val b = Binder()
|
||||||
|
subscriberCaptor.value.onSubscribe(b, subscription)
|
||||||
|
|
||||||
|
subscriberCaptor.value.onError(b, "")
|
||||||
|
verify(providers[0]).cancelLoadTimeout()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBindService() {
|
fun testBindService() {
|
||||||
controller.bindService(TEST_COMPONENT_NAME_1)
|
controller.bindService(TEST_COMPONENT_NAME_1)
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Captor
|
@Captor
|
||||||
private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
|
private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private lateinit var booleanConsumer: ArgumentCaptor<Consumer<Boolean>>
|
||||||
|
|
||||||
@Captor
|
@Captor
|
||||||
private lateinit var controlLoadCallbackCaptor:
|
private lateinit var controlLoadCallbackCaptor:
|
||||||
ArgumentCaptor<ControlsBindingController.LoadCallback>
|
ArgumentCaptor<ControlsBindingController.LoadCallback>
|
||||||
@@ -155,9 +159,13 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
|||||||
verify(listingController).addCallback(capture(listingCallbackCaptor))
|
verify(listingController).addCallback(capture(listingCallbackCaptor))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builderFromInfo(controlInfo: ControlInfo): Control.StatelessBuilder {
|
private fun builderFromInfo(
|
||||||
|
controlInfo: ControlInfo,
|
||||||
|
structure: CharSequence = ""
|
||||||
|
): Control.StatelessBuilder {
|
||||||
return Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
|
return Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
|
||||||
.setDeviceType(controlInfo.deviceType).setTitle(controlInfo.controlTitle)
|
.setDeviceType(controlInfo.deviceType).setTitle(controlInfo.controlTitle)
|
||||||
|
.setStructure(structure)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -746,4 +754,70 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
|||||||
inOrder.verify(persistenceWrapper).readFavorites()
|
inOrder.verify(persistenceWrapper).readFavorites()
|
||||||
inOrder.verify(listingController).addCallback(listingCallbackCaptor.value)
|
inOrder.verify(listingController).addCallback(listingCallbackCaptor.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSeedFavoritesForComponent() {
|
||||||
|
var succeeded = false
|
||||||
|
val control = builderFromInfo(TEST_CONTROL_INFO, TEST_STRUCTURE_INFO.structure).build()
|
||||||
|
|
||||||
|
controller.seedFavoritesForComponent(TEST_COMPONENT, Consumer { accepted ->
|
||||||
|
succeeded = accepted
|
||||||
|
})
|
||||||
|
|
||||||
|
verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT),
|
||||||
|
capture(controlLoadCallbackCaptor))
|
||||||
|
|
||||||
|
controlLoadCallbackCaptor.value.accept(listOf(control))
|
||||||
|
|
||||||
|
delayableExecutor.runAllReady()
|
||||||
|
|
||||||
|
assertEquals(listOf(TEST_STRUCTURE_INFO),
|
||||||
|
controller.getFavoritesForComponent(TEST_COMPONENT))
|
||||||
|
assertTrue(succeeded)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSeedFavoritesForComponent_error() {
|
||||||
|
var succeeded = false
|
||||||
|
|
||||||
|
controller.seedFavoritesForComponent(TEST_COMPONENT, Consumer { accepted ->
|
||||||
|
succeeded = accepted
|
||||||
|
})
|
||||||
|
|
||||||
|
verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT),
|
||||||
|
capture(controlLoadCallbackCaptor))
|
||||||
|
|
||||||
|
controlLoadCallbackCaptor.value.error("Error loading")
|
||||||
|
|
||||||
|
delayableExecutor.runAllReady()
|
||||||
|
|
||||||
|
assertEquals(listOf<StructureInfo>(), controller.getFavoritesForComponent(TEST_COMPONENT))
|
||||||
|
assertFalse(succeeded)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSeedFavoritesForComponent_inProgressCallback() {
|
||||||
|
var succeeded = false
|
||||||
|
var seeded = false
|
||||||
|
val control = builderFromInfo(TEST_CONTROL_INFO, TEST_STRUCTURE_INFO.structure).build()
|
||||||
|
|
||||||
|
controller.seedFavoritesForComponent(TEST_COMPONENT, Consumer { accepted ->
|
||||||
|
succeeded = accepted
|
||||||
|
})
|
||||||
|
|
||||||
|
verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT),
|
||||||
|
capture(controlLoadCallbackCaptor))
|
||||||
|
|
||||||
|
controller.addSeedingFavoritesCallback(Consumer { accepted ->
|
||||||
|
seeded = accepted
|
||||||
|
})
|
||||||
|
controlLoadCallbackCaptor.value.accept(listOf(control))
|
||||||
|
|
||||||
|
delayableExecutor.runAllReady()
|
||||||
|
|
||||||
|
assertEquals(listOf(TEST_STRUCTURE_INFO),
|
||||||
|
controller.getFavoritesForComponent(TEST_COMPONENT))
|
||||||
|
assertTrue(succeeded)
|
||||||
|
assertTrue(seeded)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,22 @@ class ServiceWrapperTest : SysuiTestCase() {
|
|||||||
assertFalse(result)
|
assertFalse(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLoadSuggested_happyPath() {
|
||||||
|
val result = wrapper.loadSuggested(subscriber)
|
||||||
|
|
||||||
|
assertTrue(result)
|
||||||
|
verify(service).loadSuggested(subscriber)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLoadSuggested_error() {
|
||||||
|
`when`(service.loadSuggested(any())).thenThrow(exception)
|
||||||
|
val result = wrapper.loadSuggested(subscriber)
|
||||||
|
|
||||||
|
assertFalse(result)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSubscribe_happyPath() {
|
fun testSubscribe_happyPath() {
|
||||||
val list = listOf("TEST_ID")
|
val list = listOf("TEST_ID")
|
||||||
|
|||||||
@@ -59,13 +59,15 @@ class StatefulControlSubscriberTest : SysuiTestCase() {
|
|||||||
|
|
||||||
private lateinit var scs: StatefulControlSubscriber
|
private lateinit var scs: StatefulControlSubscriber
|
||||||
|
|
||||||
|
private val REQUEST_LIMIT = 5L
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
`when`(provider.componentName).thenReturn(TEST_COMPONENT)
|
`when`(provider.componentName).thenReturn(TEST_COMPONENT)
|
||||||
`when`(provider.token).thenReturn(token)
|
`when`(provider.token).thenReturn(token)
|
||||||
scs = StatefulControlSubscriber(controller, provider, executor)
|
scs = StatefulControlSubscriber(controller, provider, executor, REQUEST_LIMIT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -73,7 +75,7 @@ class StatefulControlSubscriberTest : SysuiTestCase() {
|
|||||||
scs.onSubscribe(token, subscription)
|
scs.onSubscribe(token, subscription)
|
||||||
|
|
||||||
executor.runAllReady()
|
executor.runAllReady()
|
||||||
verify(provider).startSubscription(subscription)
|
verify(provider).startSubscription(subscription, REQUEST_LIMIT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -81,7 +83,7 @@ class StatefulControlSubscriberTest : SysuiTestCase() {
|
|||||||
scs.onSubscribe(badToken, subscription)
|
scs.onSubscribe(badToken, subscription)
|
||||||
|
|
||||||
executor.runAllReady()
|
executor.runAllReady()
|
||||||
verify(provider, never()).startSubscription(subscription)
|
verify(provider, never()).startSubscription(subscription, REQUEST_LIMIT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user