Added fallback for load failed
If the load call times out or fails in some other way, the management acitivity will show that and just show all favorites (without the removed tag). Also, added many docs Test: atest Test: manual Fixes: 149138396 Change-Id: I58e391b65df1e40989101f227c46fd47e3fb419a
This commit is contained in:
@@ -21,15 +21,32 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/controls_management_list_margin"
|
||||
android:text="@string/controls_favorite_load_error"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:visibility="gone"
|
||||
android:gravity="center_horizontal"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/text_favorites"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_favorites"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="FAVORITES"
|
||||
android:layout_marginTop="@dimen/controls_management_list_margin"
|
||||
android:text="@string/controls_favorite_header_favorites"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textAllCaps="true"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/divider1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/error_message"
|
||||
/>
|
||||
|
||||
<View
|
||||
@@ -60,8 +77,9 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/controls_management_list_margin"
|
||||
android:text="ALL"
|
||||
android:text="@string/controls_favorite_header_all"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textAllCaps="true"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/divider2"
|
||||
app:layout_constraintTop_toBottomOf="@id/listFavorites"
|
||||
|
||||
@@ -2589,4 +2589,10 @@
|
||||
<string name="controls_favorite_default_title">Controls</string>
|
||||
<!-- Controls management controls screen subtitle [CHAR LIMIT=NONE] -->
|
||||
<string name="controls_favorite_subtitle">Choose controls for quick access</string>
|
||||
<!-- Controls management controls screen favorites header [CHAR LIMIT=50] -->
|
||||
<string name="controls_favorite_header_favorites">Favorites</string>
|
||||
<!-- Controls management controls screen all header [CHAR LIMIT=50] -->
|
||||
<string name="controls_favorite_header_all">All</string>
|
||||
<!-- Controls management controls screen error on load message [CHAR LIMIT=50] -->
|
||||
<string name="controls_favorite_load_error">The list of all controls could not be loaded.</string>
|
||||
</resources>
|
||||
|
||||
@@ -18,8 +18,11 @@ package com.android.systemui.controls
|
||||
|
||||
import android.os.UserHandle
|
||||
|
||||
/**
|
||||
* An interface for controllers that keep track of the current user and can be notified of user
|
||||
* changes.
|
||||
*/
|
||||
interface UserAwareController {
|
||||
|
||||
fun changeUser(newUser: UserHandle) {}
|
||||
val currentUserId: Int
|
||||
}
|
||||
@@ -22,6 +22,15 @@ import android.util.Log
|
||||
|
||||
/**
|
||||
* Stores basic information about a [Control] to persist and keep track of favorites.
|
||||
*
|
||||
* The identifier of this [Control] is the combination of [component] and [controlId]. The other
|
||||
* two fields are there for persistence. In this way, basic information can be shown to the user
|
||||
* before the service has to report on the status.
|
||||
*
|
||||
* @property component the name of the component that provides the [Control].
|
||||
* @property controlId unique (for the given [component]) identifier for this [Control].
|
||||
* @property controlTitle last title reported for this [Control].
|
||||
* @property deviceType last reported type for this [Control].
|
||||
*/
|
||||
data class ControlInfo(
|
||||
val component: ComponentName,
|
||||
@@ -33,6 +42,14 @@ data class ControlInfo(
|
||||
companion object {
|
||||
private const val TAG = "ControlInfo"
|
||||
private const val SEPARATOR = ":"
|
||||
|
||||
/**
|
||||
* Creates a [ControlInfo] from a [SEPARATOR] separated list of fields.
|
||||
*
|
||||
* @param separator fields of a [ControlInfo] separated by [SEPARATOR]
|
||||
* @return a [ControlInfo] or `null` if there was an error.
|
||||
* @see [ControlInfo.toString]
|
||||
*/
|
||||
fun createFromString(string: String): ControlInfo? {
|
||||
val parts = string.split(SEPARATOR)
|
||||
val component = ComponentName.unflattenFromString(parts[0])
|
||||
@@ -53,6 +70,12 @@ data class ControlInfo(
|
||||
if (DeviceTypes.validDeviceType(type)) type else DeviceTypes.TYPE_UNKNOWN)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [String] representation of the fields separated using [SEPARATOR].
|
||||
*
|
||||
* @return a [String] representation of `this`
|
||||
*/
|
||||
override fun toString(): String {
|
||||
return component.flattenToString() +
|
||||
"$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType"
|
||||
|
||||
@@ -18,13 +18,72 @@ package com.android.systemui.controls.controller
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.ControlsProviderService
|
||||
import android.service.controls.actions.ControlAction
|
||||
import com.android.systemui.controls.UserAwareController
|
||||
import java.util.function.Consumer
|
||||
|
||||
/**
|
||||
* Controller for keeping track of any [ControlsProviderService] that needs to be bound.
|
||||
*
|
||||
* This controller serves as an interface between [ControlsController] and the services.
|
||||
*
|
||||
* This controller being a [UserAwareController] means that all binding and requests will be
|
||||
* performed on services bound as the current user.
|
||||
*/
|
||||
interface ControlsBindingController : UserAwareController {
|
||||
fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit)
|
||||
|
||||
/**
|
||||
* Request bind to a service and load all controls.
|
||||
*
|
||||
* @param component The [ComponentName] of the service to bind
|
||||
* @param callback a callback to return the loaded controls to (or an error).
|
||||
*/
|
||||
fun bindAndLoad(component: ComponentName, callback: LoadCallback)
|
||||
|
||||
/**
|
||||
* Request to bind to the given services.
|
||||
*
|
||||
* @param components a list of [ComponentName] of the services to bind
|
||||
*/
|
||||
fun bindServices(components: List<ComponentName>)
|
||||
|
||||
/**
|
||||
* Send a subscribe message to retrieve status of a set of controls.
|
||||
*
|
||||
* The controls passed do not have to belong to a single [ControlsProviderService]. The
|
||||
* corresponding service [ComponentName] is associated with each control.
|
||||
*
|
||||
* @param controls a list of controls with corresponding [ComponentName] to request status
|
||||
* update
|
||||
*/
|
||||
fun subscribe(controls: List<ControlInfo>)
|
||||
|
||||
/**
|
||||
* Send an action performed on a [Control].
|
||||
*
|
||||
* @param controlInfo information about the actioned control, including the [ComponentName]
|
||||
* @param action the action performed on the control
|
||||
*/
|
||||
fun action(controlInfo: ControlInfo, action: ControlAction)
|
||||
|
||||
/**
|
||||
* Unsubscribe from all services to stop status updates.
|
||||
*/
|
||||
fun unsubscribe()
|
||||
|
||||
/**
|
||||
* Consumer for load calls.
|
||||
*
|
||||
* Supports also sending error messages.
|
||||
*/
|
||||
interface LoadCallback : Consumer<List<Control>> {
|
||||
|
||||
/**
|
||||
* Indicates an error loading.
|
||||
*
|
||||
* @message an error message.
|
||||
*/
|
||||
fun error(message: String)
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,10 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit) {
|
||||
override fun bindAndLoad(
|
||||
component: ComponentName,
|
||||
callback: ControlsBindingController.LoadCallback
|
||||
) {
|
||||
val provider = retrieveLifecycleManager(component)
|
||||
provider.maybeBindAndLoad(callback)
|
||||
}
|
||||
@@ -230,7 +233,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
return
|
||||
}
|
||||
}
|
||||
provider.lastLoadCallback?.invoke(list) ?: run {
|
||||
provider.lastLoadCallback?.accept(list) ?: run {
|
||||
Log.w(TAG, "Null callback")
|
||||
}
|
||||
provider.unbindService()
|
||||
|
||||
@@ -18,33 +18,194 @@ package com.android.systemui.controls.controller
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.ControlsProviderService
|
||||
import android.service.controls.actions.ControlAction
|
||||
import com.android.systemui.controls.ControlStatus
|
||||
import com.android.systemui.controls.UserAwareController
|
||||
import com.android.systemui.controls.management.ControlsFavoritingActivity
|
||||
import com.android.systemui.controls.ui.ControlsUiController
|
||||
import java.util.function.Consumer
|
||||
|
||||
/**
|
||||
* Controller to handle communication between different parts of the controls system.
|
||||
*
|
||||
* This controller is in charge of:
|
||||
* * Keeping track of favorites
|
||||
* * Determining and keeping track of whether controls are enabled
|
||||
* * Listening for user change and propagating that message in the system
|
||||
* * Communicate between the UI and the [ControlsBindingController]
|
||||
*
|
||||
* This controller being a [UserAwareController] means that all operations will be conducted on
|
||||
* information for the current user only.
|
||||
*/
|
||||
interface ControlsController : UserAwareController {
|
||||
|
||||
/**
|
||||
* Whether the controls system is available for the current user.
|
||||
*/
|
||||
val available: Boolean
|
||||
|
||||
fun getFavoriteControls(): List<ControlInfo>
|
||||
// SERVICE COMMUNICATION
|
||||
|
||||
/**
|
||||
* Load all available [Control] for a given service.
|
||||
*
|
||||
* @param componentName the [ComponentName] of the [ControlsProviderService] to load from
|
||||
* @param dataCallback a callback in which to retrieve the result.
|
||||
*/
|
||||
fun loadForComponent(
|
||||
componentName: ComponentName,
|
||||
callback: (List<ControlStatus>, List<String>) -> Unit
|
||||
dataCallback: Consumer<LoadData>
|
||||
)
|
||||
|
||||
/**
|
||||
* Request to subscribe for all favorite controls.
|
||||
*
|
||||
* @see [ControlsBindingController.subscribe]
|
||||
*/
|
||||
fun subscribeToFavorites()
|
||||
fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
|
||||
fun replaceFavoritesForComponent(componentName: ComponentName, favorites: List<ControlInfo>)
|
||||
|
||||
fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo>
|
||||
fun countFavoritesForComponent(componentName: ComponentName): Int
|
||||
|
||||
/**
|
||||
* Request to unsubscribe to all providers.
|
||||
*
|
||||
* @see [ControlsBindingController.unsubscribe]
|
||||
*/
|
||||
fun unsubscribe()
|
||||
|
||||
/**
|
||||
* Notify a [ControlsProviderService] that an action has been performed on a [Control].
|
||||
*
|
||||
* @param controlInfo information of the [Control] receiving the action
|
||||
* @param action action performed on the [Control]
|
||||
* @see [ControlsBindingController.action]
|
||||
*/
|
||||
fun action(controlInfo: ControlInfo, action: ControlAction)
|
||||
|
||||
/**
|
||||
* Refresh the status of a [Control] with information provided from the service.
|
||||
*
|
||||
* @param componentName the name of the service that provides the [Control]
|
||||
* @param control a stateful [Control] with updated information
|
||||
* @see [ControlsUiController.onRefreshState]
|
||||
*/
|
||||
fun refreshStatus(componentName: ComponentName, control: Control)
|
||||
|
||||
/**
|
||||
* Indicate the result of a [ControlAction] performed on a [Control].
|
||||
*
|
||||
* @param componentName the name of the service that provides the [Control]
|
||||
* @param controlId the id of the [Control] the actioned was performed on
|
||||
* @param response the result of the action.
|
||||
* @see [ControlsUiController.onActionResponse]
|
||||
*/
|
||||
fun onActionResponse(
|
||||
componentName: ComponentName,
|
||||
controlId: String,
|
||||
@ControlAction.ResponseResult response: Int
|
||||
)
|
||||
|
||||
// FAVORITE MANAGEMENT
|
||||
|
||||
/**
|
||||
* Get a list of all favorite controls.
|
||||
*
|
||||
* @return a list of [ControlInfo] with persistent information about the controls, including
|
||||
* their corresponding [ComponentName].
|
||||
*/
|
||||
fun getFavoriteControls(): List<ControlInfo>
|
||||
|
||||
/**
|
||||
* Get all the favorites for a given component.
|
||||
*
|
||||
* @param componentName the name of the component of the [ControlsProviderService] with
|
||||
* which to filter the favorites.
|
||||
* @return a list of the favorite controls for the given service. All the elements of the list
|
||||
* will have the same [ControlInfo.component] matching the one requested.
|
||||
*/
|
||||
fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo>
|
||||
|
||||
/**
|
||||
* Replaces the favorites for the given component.
|
||||
*
|
||||
* Calling this method will eliminate the previous selection of favorites and replace it with a
|
||||
* new one.
|
||||
*
|
||||
* @param componentName The name of the component for the [ControlsProviderService]
|
||||
* @param favorites a list of [ControlInfo] to replace the previous favorites.
|
||||
*/
|
||||
fun replaceFavoritesForComponent(componentName: ComponentName, favorites: List<ControlInfo>)
|
||||
|
||||
/**
|
||||
* Change the favorite status of a single [Control].
|
||||
*
|
||||
* If the control is added to favorites, it will be added to the end of the list for that
|
||||
* particular component. Matching for removing the control will be done based on
|
||||
* [ControlInfo.component] and [ControlInfo.controlId].
|
||||
*
|
||||
* Trying to add an already favorite control or trying to remove one that is not a favorite is
|
||||
* a no-op.
|
||||
*
|
||||
* @param controlInfo persistent information about the [Control].
|
||||
* @param state `true` to add to favorites and `false` to remove.
|
||||
*/
|
||||
fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
|
||||
|
||||
/**
|
||||
* Return the number of favorites for a given component.
|
||||
*
|
||||
* This call returns the same as `getFavoritesForComponent(componentName).size`.
|
||||
*
|
||||
* @param componentName the name of the component
|
||||
* @return the number of current favorites for the given component
|
||||
*/
|
||||
fun countFavoritesForComponent(componentName: ComponentName): Int
|
||||
|
||||
/**
|
||||
* Clears the list of all favorites.
|
||||
*
|
||||
* To clear the list of favorites for a given service, call [replaceFavoritesForComponent] with
|
||||
* an empty list.
|
||||
*/
|
||||
fun clearFavorites()
|
||||
|
||||
/**
|
||||
* Interface for structure to pass data to [ControlsFavoritingActivity].
|
||||
*/
|
||||
interface LoadData {
|
||||
/**
|
||||
* All of the available controls for the loaded [ControlsProviderService].
|
||||
*
|
||||
* This will indicate if they are currently a favorite and whether they were removed (a
|
||||
* favorite but not retrieved on load).
|
||||
*/
|
||||
val allControls: List<ControlStatus>
|
||||
|
||||
/**
|
||||
* Ordered list of ids of favorite controls.
|
||||
*/
|
||||
val favoritesIds: List<String>
|
||||
|
||||
/**
|
||||
* Whether there was an error in loading.
|
||||
*
|
||||
* In this case, [allControls] will only contain those that were favorited and will not be
|
||||
* marked as removed.
|
||||
*/
|
||||
val errorOnLoad: Boolean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a basic implementation of a [LoadData].
|
||||
*/
|
||||
fun createLoadDataObject(
|
||||
allControls: List<ControlStatus>,
|
||||
favorites: List<String>,
|
||||
error: Boolean = false
|
||||
): ControlsController.LoadData {
|
||||
return object : ControlsController.LoadData {
|
||||
override val allControls = allControls
|
||||
override val favoritesIds = favorites
|
||||
override val errorOnLoad = error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ import com.android.systemui.DumpController
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.controls.ControlStatus
|
||||
import com.android.systemui.controls.management.ControlsFavoritingActivity
|
||||
import com.android.systemui.controls.management.ControlsListingController
|
||||
import com.android.systemui.controls.ui.ControlsUiController
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
@@ -47,6 +46,7 @@ import java.io.FileDescriptor
|
||||
import java.io.PrintWriter
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.function.Consumer
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -188,47 +188,74 @@ class ControlsControllerImpl @Inject constructor (
|
||||
|
||||
override fun loadForComponent(
|
||||
componentName: ComponentName,
|
||||
callback: (List<ControlStatus>, List<String>) -> Unit
|
||||
dataCallback: Consumer<ControlsController.LoadData>
|
||||
) {
|
||||
if (!confirmAvailability()) {
|
||||
if (userChanging) {
|
||||
// Try again later, userChanging should not last forever. If so, we have bigger
|
||||
// problems
|
||||
executor.executeDelayed(
|
||||
{ loadForComponent(componentName, callback) },
|
||||
{ loadForComponent(componentName, dataCallback) },
|
||||
USER_CHANGE_RETRY_DELAY,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
} else {
|
||||
callback(emptyList(), emptyList())
|
||||
dataCallback.accept(createLoadDataObject(emptyList(), emptyList(), true))
|
||||
}
|
||||
return
|
||||
}
|
||||
bindingController.bindAndLoad(componentName) {
|
||||
synchronized(currentFavorites) {
|
||||
val favoritesForComponentKeys: List<String> =
|
||||
currentFavorites.getValue(componentName).map { it.controlId }
|
||||
val changed = updateFavoritesLocked(componentName, it, favoritesForComponentKeys)
|
||||
if (changed) {
|
||||
persistenceWrapper.storeFavorites(favoritesAsListLocked())
|
||||
bindingController.bindAndLoad(
|
||||
componentName,
|
||||
object : ControlsBindingController.LoadCallback {
|
||||
override fun accept(controls: List<Control>) {
|
||||
val loadData = synchronized(currentFavorites) {
|
||||
val favoritesForComponentKeys: List<String> =
|
||||
currentFavorites.getValue(componentName).map { it.controlId }
|
||||
val changed = updateFavoritesLocked(componentName, controls,
|
||||
favoritesForComponentKeys)
|
||||
if (changed) {
|
||||
persistenceWrapper.storeFavorites(favoritesAsListLocked())
|
||||
}
|
||||
val removed = findRemovedLocked(favoritesForComponentKeys.toSet(),
|
||||
controls)
|
||||
val controlsWithFavorite = controls.map {
|
||||
ControlStatus(it, it.controlId in favoritesForComponentKeys)
|
||||
}
|
||||
createLoadDataObject(
|
||||
currentFavorites.getValue(componentName)
|
||||
.filter { it.controlId in removed }
|
||||
.map { createRemovedStatus(it) } +
|
||||
controlsWithFavorite,
|
||||
favoritesForComponentKeys
|
||||
)
|
||||
}
|
||||
dataCallback.accept(loadData)
|
||||
}
|
||||
|
||||
override fun error(message: String) {
|
||||
val loadData = synchronized(currentFavorites) {
|
||||
val favoritesForComponent = currentFavorites.getValue(componentName)
|
||||
val favoritesForComponentKeys = favoritesForComponent
|
||||
.map { it.controlId }
|
||||
createLoadDataObject(
|
||||
favoritesForComponent.map { createRemovedStatus(it, false) },
|
||||
favoritesForComponentKeys,
|
||||
true
|
||||
)
|
||||
}
|
||||
dataCallback.accept(loadData)
|
||||
}
|
||||
}
|
||||
val removed = findRemovedLocked(favoritesForComponentKeys.toSet(), it)
|
||||
val controlsWithFavorite =
|
||||
it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) }
|
||||
callback(
|
||||
currentFavorites.getValue(componentName)
|
||||
.filter { it.controlId in removed }
|
||||
.map(::createRemovedStatus) + controlsWithFavorite,
|
||||
favoritesForComponentKeys
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus {
|
||||
val intent = Intent(context, ControlsFavoritingActivity::class.java).apply {
|
||||
putExtra(Intent.EXTRA_COMPONENT_NAME, controlInfo.component)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
private fun createRemovedStatus(
|
||||
controlInfo: ControlInfo,
|
||||
setRemoved: Boolean = true
|
||||
): ControlStatus {
|
||||
val intent = Intent(Intent.ACTION_MAIN).apply {
|
||||
addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
this.`package` = controlInfo.component.packageName
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(context,
|
||||
controlInfo.component.hashCode(),
|
||||
@@ -238,7 +265,7 @@ class ControlsControllerImpl @Inject constructor (
|
||||
.setTitle(controlInfo.controlTitle)
|
||||
.setDeviceType(controlInfo.deviceType)
|
||||
.build()
|
||||
return ControlStatus(control, true, true)
|
||||
return ControlStatus(control, true, setRemoved)
|
||||
}
|
||||
|
||||
@GuardedBy("currentFavorites")
|
||||
|
||||
@@ -19,9 +19,7 @@ package com.android.systemui.controls.controller
|
||||
import android.content.ComponentName
|
||||
import android.util.AtomicFile
|
||||
import android.util.Log
|
||||
import android.util.Slog
|
||||
import android.util.Xml
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import libcore.io.IoUtils
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
@@ -29,10 +27,18 @@ import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
/**
|
||||
* Manages persistence of favorite controls.
|
||||
*
|
||||
* This class uses an [AtomicFile] to serialize the favorite controls to an xml.
|
||||
* @property file a file location for storing/reading the favorites.
|
||||
* @property executor an executor in which to execute storing the favorites.
|
||||
*/
|
||||
class ControlsFavoritePersistenceWrapper(
|
||||
private var file: File,
|
||||
private var executor: DelayableExecutor
|
||||
private val executor: Executor
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -46,10 +52,20 @@ class ControlsFavoritePersistenceWrapper(
|
||||
private const val TAG_TYPE = "type"
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the file location for storing/reading the favorites
|
||||
*
|
||||
* @param fileName new location
|
||||
*/
|
||||
fun changeFile(fileName: File) {
|
||||
file = fileName
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the list of favorites in the corresponding file.
|
||||
*
|
||||
* @param list a list of favorite controls. The list will be stored in the same order.
|
||||
*/
|
||||
fun storeFavorites(list: List<ControlInfo>) {
|
||||
executor.execute {
|
||||
Log.d(TAG, "Saving data to file: $file")
|
||||
@@ -87,6 +103,12 @@ class ControlsFavoritePersistenceWrapper(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the list of favorites in the corresponding file.
|
||||
*
|
||||
* @return a list of stored favorite controls. Return an empty list if the file is not found
|
||||
* @throws [IllegalStateException] if there is an error while reading the file
|
||||
*/
|
||||
fun readFavorites(): List<ControlInfo> {
|
||||
if (!file.exists()) {
|
||||
Log.d(TAG, "No favorites, returning empty list")
|
||||
@@ -95,7 +117,7 @@ class ControlsFavoritePersistenceWrapper(
|
||||
val reader = try {
|
||||
FileInputStream(file)
|
||||
} catch (fnfe: FileNotFoundException) {
|
||||
Slog.i(TAG, "No file found")
|
||||
Log.i(TAG, "No file found")
|
||||
return emptyList()
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -25,7 +25,7 @@ import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import android.os.UserHandle
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.ControlsProviderService
|
||||
import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
|
||||
import android.service.controls.ControlsProviderService.CALLBACK_TOKEN
|
||||
import android.service.controls.IControlsActionCallback
|
||||
@@ -40,7 +40,23 @@ import com.android.internal.annotations.GuardedBy
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
typealias LoadCallback = (List<Control>) -> Unit
|
||||
/**
|
||||
* Manager for the lifecycle of the connection to a given [ControlsProviderService].
|
||||
*
|
||||
* This class handles binding and unbinding and requests to the service. The class will queue
|
||||
* requests until the service is connected and dispatch them then.
|
||||
*
|
||||
* @property context A SystemUI context for binding to the services
|
||||
* @property executor A delayable executor for posting timeouts
|
||||
* @property loadCallbackService a callback interface to hand the remote service for loading
|
||||
* controls
|
||||
* @property actionCallbackService a callback interface to hand the remote service for sending
|
||||
* action responses
|
||||
* @property subscriberService an "subscriber" interface for requesting and accepting updates for
|
||||
* controls from the service.
|
||||
* @property user the user for whose this service should be bound.
|
||||
* @property componentName the name of the component for the service.
|
||||
*/
|
||||
class ControlsProviderLifecycleManager(
|
||||
private val context: Context,
|
||||
private val executor: DelayableExecutor,
|
||||
@@ -51,7 +67,7 @@ class ControlsProviderLifecycleManager(
|
||||
val componentName: ComponentName
|
||||
) : IBinder.DeathRecipient {
|
||||
|
||||
var lastLoadCallback: LoadCallback? = null
|
||||
var lastLoadCallback: ControlsBindingController.LoadCallback? = null
|
||||
private set
|
||||
val token: IBinder = Binder()
|
||||
@GuardedBy("subscriptions")
|
||||
@@ -187,7 +203,7 @@ class ControlsProviderLifecycleManager(
|
||||
}
|
||||
}
|
||||
|
||||
private fun invokeOrQueue(f: () -> Unit, msg: Message) {
|
||||
private inline fun invokeOrQueue(f: () -> Unit, msg: Message) {
|
||||
wrapper?.run {
|
||||
f()
|
||||
} ?: run {
|
||||
@@ -196,18 +212,37 @@ class ControlsProviderLifecycleManager(
|
||||
}
|
||||
}
|
||||
|
||||
fun maybeBindAndLoad(callback: LoadCallback) {
|
||||
/**
|
||||
* Request a call to [ControlsProviderService.loadAvailableControls].
|
||||
*
|
||||
* If the service is not bound, the call will be queued and the service will be bound first.
|
||||
* The service will be bound after the controls are returned or the call times out.
|
||||
*
|
||||
* @param callback a callback in which to return the result back. If the call times out
|
||||
* [ControlsBindingController.LoadCallback.error] will be called instead.
|
||||
*/
|
||||
fun maybeBindAndLoad(callback: ControlsBindingController.LoadCallback) {
|
||||
unqueueMessage(Message.Unbind)
|
||||
lastLoadCallback = callback
|
||||
onLoadCanceller = executor.executeDelayed({
|
||||
// Didn't receive a response in time, log and send back empty list
|
||||
// Didn't receive a response in time, log and send back error
|
||||
Log.d(TAG, "Timeout waiting onLoad for $componentName")
|
||||
loadCallbackService.accept(token, emptyList())
|
||||
callback.error("Timeout waiting onLoad")
|
||||
// Don't accept load callbacks after this
|
||||
lastLoadCallback = null
|
||||
unbindService()
|
||||
}, LOAD_TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
|
||||
invokeOrQueue(::load, Message.Load)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a subscription to the [Publisher] returned by [ControlsProviderService.publisherFor]
|
||||
*
|
||||
* If the service is not bound, the call will be queued and the service will be bound first.
|
||||
*
|
||||
* @param controlIds a list of the ids of controls to send status back.
|
||||
*/
|
||||
fun maybeBindAndSubscribe(controlIds: List<String>) {
|
||||
invokeOrQueue({ subscribe(controlIds) }, Message.Subscribe(controlIds))
|
||||
}
|
||||
@@ -222,6 +257,14 @@ class ControlsProviderLifecycleManager(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a call to [ControlsProviderService.performControlAction].
|
||||
*
|
||||
* If the service is not bound, the call will be queued and the service will be bound first.
|
||||
*
|
||||
* @param controlId the id of the [Control] the action is performed on
|
||||
* @param action the action performed
|
||||
*/
|
||||
fun maybeBindAndSendAction(controlId: String, action: ControlAction) {
|
||||
invokeOrQueue({ action(controlId, action) }, Message.Action(controlId, action))
|
||||
}
|
||||
@@ -236,6 +279,12 @@ class ControlsProviderLifecycleManager(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the subscription to the [ControlsProviderService] and requests status of controls.
|
||||
*
|
||||
* @param subscription the subscriber to use to request controls
|
||||
* @see maybeBindAndLoad
|
||||
*/
|
||||
fun startSubscription(subscription: IControlsSubscription) {
|
||||
synchronized(subscriptions) {
|
||||
subscriptions.add(subscription)
|
||||
@@ -243,6 +292,9 @@ class ControlsProviderLifecycleManager(
|
||||
wrapper?.request(subscription, MAX_CONTROLS_REQUEST)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from this service, cancelling all status requests.
|
||||
*/
|
||||
fun unsubscribe() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "unsubscribe $componentName")
|
||||
@@ -260,11 +312,17 @@ class ControlsProviderLifecycleManager(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request bind to the service.
|
||||
*/
|
||||
fun bindService() {
|
||||
unqueueMessage(Message.Unbind)
|
||||
bindService(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request unbind from the service.
|
||||
*/
|
||||
fun unbindService() {
|
||||
lastLoadCallback = null
|
||||
onLoadCanceller?.run()
|
||||
@@ -281,6 +339,9 @@ class ControlsProviderLifecycleManager(
|
||||
}.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Messages for the internal queue.
|
||||
*/
|
||||
sealed class Message {
|
||||
abstract val type: Int
|
||||
object Load : Message() {
|
||||
|
||||
@@ -25,12 +25,17 @@ import android.service.controls.IControlsSubscription
|
||||
import android.service.controls.actions.ControlActionWrapper
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
* Wrapper for the service calls.
|
||||
*
|
||||
* Calling all [IControlsProvider] methods through here will wrap them in a try/catch block.
|
||||
*/
|
||||
class ServiceWrapper(val service: IControlsProvider) {
|
||||
companion object {
|
||||
private const val TAG = "ServiceWrapper"
|
||||
}
|
||||
|
||||
private fun callThroughService(block: () -> Unit): Boolean {
|
||||
private inline fun callThroughService(block: () -> Unit): Boolean {
|
||||
try {
|
||||
block()
|
||||
return true
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewStub
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
@@ -34,6 +35,7 @@ import com.android.systemui.controls.controller.ControlsControllerImpl
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.settings.CurrentUserTracker
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.function.Consumer
|
||||
import javax.inject.Inject
|
||||
|
||||
class ControlsFavoritingActivity @Inject constructor(
|
||||
@@ -51,6 +53,7 @@ class ControlsFavoritingActivity @Inject constructor(
|
||||
private lateinit var adapterAll: ControlAdapter
|
||||
private lateinit var recyclerViewFavorites: RecyclerView
|
||||
private lateinit var adapterFavorites: ControlAdapter
|
||||
private lateinit var errorText: TextView
|
||||
private var component: ComponentName? = null
|
||||
|
||||
private var currentModel: FavoriteModel? = null
|
||||
@@ -96,6 +99,7 @@ class ControlsFavoritingActivity @Inject constructor(
|
||||
|
||||
val app = intent.getCharSequenceExtra(EXTRA_APP)
|
||||
component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
|
||||
errorText = requireViewById(R.id.error_message)
|
||||
|
||||
setUpRecyclerViews()
|
||||
|
||||
@@ -118,7 +122,10 @@ class ControlsFavoritingActivity @Inject constructor(
|
||||
}
|
||||
|
||||
component?.let {
|
||||
controller.loadForComponent(it) { allControls, favoriteKeys ->
|
||||
controller.loadForComponent(it, Consumer { data ->
|
||||
val allControls = data.allControls
|
||||
val favoriteKeys = data.favoritesIds
|
||||
val error = data.errorOnLoad
|
||||
executor.execute {
|
||||
val favoriteModel = FavoriteModel(
|
||||
allControls,
|
||||
@@ -128,8 +135,9 @@ class ControlsFavoritingActivity @Inject constructor(
|
||||
adapterAll.changeFavoritesModel(favoriteModel)
|
||||
adapterFavorites.changeFavoritesModel(favoriteModel)
|
||||
currentModel = favoriteModel
|
||||
errorText.visibility = if (error) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
currentUserTracker.startTracking()
|
||||
|
||||
@@ -21,11 +21,26 @@ import com.android.settingslib.widget.CandidateInfo
|
||||
import com.android.systemui.controls.UserAwareController
|
||||
import com.android.systemui.statusbar.policy.CallbackController
|
||||
|
||||
/**
|
||||
* Controller for keeping track of services that can be bound given a particular [ServiceListing].
|
||||
*/
|
||||
interface ControlsListingController :
|
||||
CallbackController<ControlsListingController.ControlsListingCallback>,
|
||||
UserAwareController {
|
||||
|
||||
/**
|
||||
* @return the current list of services that satisfies the [ServiceListing].
|
||||
*/
|
||||
fun getCurrentServices(): List<CandidateInfo>
|
||||
|
||||
/**
|
||||
* Get the app label for a given component.
|
||||
*
|
||||
* This call may do Binder calls (to [PackageManager])
|
||||
*
|
||||
* @param name the component name to retrieve the label
|
||||
* @return the label for the component
|
||||
*/
|
||||
fun getAppLabel(name: ComponentName): CharSequence? = ""
|
||||
|
||||
@FunctionalInterface
|
||||
|
||||
@@ -86,7 +86,11 @@ class ControlsBindingControllerTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testBindAndLoad() {
|
||||
val callback: (List<Control>) -> Unit = {}
|
||||
val callback = object : ControlsBindingController.LoadCallback {
|
||||
override fun error(message: String) {}
|
||||
|
||||
override fun accept(t: List<Control>) {}
|
||||
}
|
||||
controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
|
||||
|
||||
assertEquals(1, providers.size)
|
||||
|
||||
@@ -56,6 +56,7 @@ import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.util.Optional
|
||||
import java.util.function.Consumer
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@@ -79,7 +80,8 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
@Captor
|
||||
private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>>
|
||||
@Captor
|
||||
private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit>
|
||||
private lateinit var controlLoadCallbackCaptor:
|
||||
ArgumentCaptor<ControlsBindingController.LoadCallback>
|
||||
@Captor
|
||||
private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
|
||||
|
||||
@@ -226,13 +228,13 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
|
||||
val control = builderFromInfo(newControlInfo).build()
|
||||
|
||||
controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit }
|
||||
controller.loadForComponent(TEST_COMPONENT, Consumer {})
|
||||
|
||||
reset(persistenceWrapper)
|
||||
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
|
||||
capture(controlLoadCallbackCaptor))
|
||||
|
||||
controlLoadCallbackCaptor.value.invoke(listOf(control))
|
||||
controlLoadCallbackCaptor.value.accept(listOf(control))
|
||||
|
||||
verify(persistenceWrapper).storeFavorites(listOf(newControlInfo))
|
||||
}
|
||||
@@ -294,19 +296,22 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
var loaded = false
|
||||
val control = builderFromInfo(TEST_CONTROL_INFO).build()
|
||||
|
||||
controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
|
||||
controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
|
||||
val controls = data.allControls
|
||||
val favorites = data.favoritesIds
|
||||
loaded = true
|
||||
assertEquals(1, controls.size)
|
||||
val controlStatus = controls[0]
|
||||
assertEquals(ControlStatus(control, false), controlStatus)
|
||||
|
||||
assertTrue(favorites.isEmpty())
|
||||
}
|
||||
assertFalse(data.errorOnLoad)
|
||||
})
|
||||
|
||||
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
|
||||
capture(controlLoadCallbackCaptor))
|
||||
|
||||
controlLoadCallbackCaptor.value.invoke(listOf(control))
|
||||
controlLoadCallbackCaptor.value.accept(listOf(control))
|
||||
|
||||
assertTrue(loaded)
|
||||
}
|
||||
@@ -318,7 +323,9 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build()
|
||||
controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
|
||||
|
||||
controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
|
||||
controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
|
||||
val controls = data.allControls
|
||||
val favorites = data.favoritesIds
|
||||
loaded = true
|
||||
assertEquals(2, controls.size)
|
||||
val controlStatus = controls.first { it.control.controlId == TEST_CONTROL_ID }
|
||||
@@ -329,12 +336,13 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
|
||||
assertEquals(1, favorites.size)
|
||||
assertEquals(TEST_CONTROL_ID, favorites[0])
|
||||
}
|
||||
assertFalse(data.errorOnLoad)
|
||||
})
|
||||
|
||||
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
|
||||
capture(controlLoadCallbackCaptor))
|
||||
|
||||
controlLoadCallbackCaptor.value.invoke(listOf(control, control2))
|
||||
controlLoadCallbackCaptor.value.accept(listOf(control, control2))
|
||||
|
||||
assertTrue(loaded)
|
||||
}
|
||||
@@ -344,7 +352,9 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
var loaded = false
|
||||
controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
|
||||
|
||||
controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
|
||||
controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
|
||||
val controls = data.allControls
|
||||
val favorites = data.favoritesIds
|
||||
loaded = true
|
||||
assertEquals(1, controls.size)
|
||||
val controlStatus = controls[0]
|
||||
@@ -354,12 +364,41 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
|
||||
assertEquals(1, favorites.size)
|
||||
assertEquals(TEST_CONTROL_ID, favorites[0])
|
||||
}
|
||||
assertFalse(data.errorOnLoad)
|
||||
})
|
||||
|
||||
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
|
||||
capture(controlLoadCallbackCaptor))
|
||||
|
||||
controlLoadCallbackCaptor.value.invoke(emptyList())
|
||||
controlLoadCallbackCaptor.value.accept(emptyList())
|
||||
|
||||
assertTrue(loaded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testErrorOnLoad_notRemoved() {
|
||||
var loaded = false
|
||||
controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
|
||||
|
||||
controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
|
||||
val controls = data.allControls
|
||||
val favorites = data.favoritesIds
|
||||
loaded = true
|
||||
assertEquals(1, controls.size)
|
||||
val controlStatus = controls[0]
|
||||
assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
|
||||
assertTrue(controlStatus.favorite)
|
||||
assertFalse(controlStatus.removed)
|
||||
|
||||
assertEquals(1, favorites.size)
|
||||
assertEquals(TEST_CONTROL_ID, favorites[0])
|
||||
assertTrue(data.errorOnLoad)
|
||||
})
|
||||
|
||||
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
|
||||
capture(controlLoadCallbackCaptor))
|
||||
|
||||
controlLoadCallbackCaptor.value.error("")
|
||||
|
||||
assertTrue(loaded)
|
||||
}
|
||||
@@ -370,12 +409,12 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
|
||||
val control = builderFromInfo(newControlInfo).build()
|
||||
|
||||
controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit }
|
||||
controller.loadForComponent(TEST_COMPONENT, Consumer {})
|
||||
|
||||
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
|
||||
capture(controlLoadCallbackCaptor))
|
||||
|
||||
controlLoadCallbackCaptor.value.invoke(listOf(control))
|
||||
controlLoadCallbackCaptor.value.accept(listOf(control))
|
||||
|
||||
val favorites = controller.getFavoriteControls()
|
||||
assertEquals(1, favorites.size)
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.android.systemui.controls.controller
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.os.UserHandle
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.IControlsActionCallback
|
||||
import android.service.controls.IControlsLoadCallback
|
||||
import android.service.controls.IControlsProvider
|
||||
@@ -28,7 +27,6 @@ import android.service.controls.actions.ControlActionWrapper
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import org.junit.After
|
||||
@@ -41,6 +39,7 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.ArgumentMatchers.anyString
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
@@ -53,20 +52,22 @@ import org.mockito.MockitoAnnotations
|
||||
class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
|
||||
|
||||
@Mock
|
||||
private lateinit var actionCallback: IControlsActionCallback.Stub
|
||||
private lateinit var actionCallbackService: IControlsActionCallback.Stub
|
||||
@Mock
|
||||
private lateinit var loadCallback: IControlsLoadCallback.Stub
|
||||
private lateinit var loadCallbackService: IControlsLoadCallback.Stub
|
||||
@Mock
|
||||
private lateinit var subscriber: IControlsSubscriber.Stub
|
||||
private lateinit var subscriberService: IControlsSubscriber.Stub
|
||||
@Mock
|
||||
private lateinit var service: IControlsProvider.Stub
|
||||
@Mock
|
||||
private lateinit var loadCallback: ControlsBindingController.LoadCallback
|
||||
|
||||
@Captor
|
||||
private lateinit var wrapperCaptor: ArgumentCaptor<ControlActionWrapper>
|
||||
|
||||
private val componentName = ComponentName("test.pkg", "test.cls")
|
||||
private lateinit var manager: ControlsProviderLifecycleManager
|
||||
private lateinit var executor: DelayableExecutor
|
||||
private lateinit var executor: FakeExecutor
|
||||
|
||||
companion object {
|
||||
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
||||
@@ -84,9 +85,9 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
|
||||
manager = ControlsProviderLifecycleManager(
|
||||
context,
|
||||
executor,
|
||||
loadCallback,
|
||||
actionCallback,
|
||||
subscriber,
|
||||
loadCallbackService,
|
||||
actionCallbackService,
|
||||
subscriberService,
|
||||
UserHandle.of(0),
|
||||
componentName
|
||||
)
|
||||
@@ -112,31 +113,40 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testMaybeBindAndLoad() {
|
||||
val callback: (List<Control>) -> Unit = {}
|
||||
manager.maybeBindAndLoad(callback)
|
||||
manager.maybeBindAndLoad(loadCallback)
|
||||
|
||||
verify(service).load(loadCallback)
|
||||
verify(service).load(loadCallbackService)
|
||||
|
||||
assertTrue(mContext.isBound(componentName))
|
||||
assertEquals(callback, manager.lastLoadCallback)
|
||||
assertEquals(loadCallback, manager.lastLoadCallback)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMaybeUnbind_bindingAndCallback() {
|
||||
manager.maybeBindAndLoad {}
|
||||
manager.maybeBindAndLoad(loadCallback)
|
||||
|
||||
manager.unbindService()
|
||||
assertFalse(mContext.isBound(componentName))
|
||||
assertNull(manager.lastLoadCallback)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMaybeBindAndLoad_timeout() {
|
||||
manager.maybeBindAndLoad(loadCallback)
|
||||
|
||||
executor.advanceClockToLast()
|
||||
executor.runAllReady()
|
||||
|
||||
verify(loadCallback).error(anyString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMaybeBindAndSubscribe() {
|
||||
val list = listOf("TEST_ID")
|
||||
manager.maybeBindAndSubscribe(list)
|
||||
|
||||
assertTrue(mContext.isBound(componentName))
|
||||
verify(service).subscribe(list, subscriber)
|
||||
verify(service).subscribe(list, subscriberService)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -147,7 +157,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
|
||||
|
||||
assertTrue(mContext.isBound(componentName))
|
||||
verify(service).action(eq(controlId), capture(wrapperCaptor),
|
||||
eq(actionCallback))
|
||||
eq(actionCallbackService))
|
||||
assertEquals(action, wrapperCaptor.getValue().getWrappedAction())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user