Merge "Added fallback for load failed"
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"
|
||||
|
||||
@@ -2595,4 +2595,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