Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 313f37de authored by Matt Pietal's avatar Matt Pietal
Browse files

Controls - Structure persistence

Allow structure to be persisted and restored. Assume structure can be
null in XML, to ease migration. Null values from Control will be
mapped to an empty string in ControlInfo. Reduce binding controller
to a single binding at a time.

Bug: 148207527
Test: atest ControlsFavoritePersistenceWrapperTest
ControlsControllerImplTest ControlsBindingControllerImplTest

Change-Id: I220212666258311bd58b498d4d579977c11d3fae
parent 35886c0d
Loading
Loading
Loading
Loading
+6 −40
Original line number Diff line number Diff line
@@ -16,59 +16,27 @@

package com.android.systemui.controls.controller

import android.content.ComponentName
import android.service.controls.DeviceTypes
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
 * The identifier of this [Control] is the [controlId], and is only unique per app. The other
 * 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 controlId unique 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,
    val controlId: String,
    val controlTitle: CharSequence,
    @DeviceTypes.DeviceType val deviceType: Int
) {

    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])
            if (parts.size != 4 || component == null) {
                Log.e(TAG, "Cannot parse ControlInfo from $string")
                return null
            }
            val type = try {
                parts[3].toInt()
            } catch (e: Exception) {
                Log.e(TAG, "Cannot parse deviceType from ${parts[3]}")
                return null
            }
            return ControlInfo(
                    component,
                    parts[1],
                    parts[2],
                    if (DeviceTypes.validDeviceType(type)) type else DeviceTypes.TYPE_UNKNOWN)
        }
    }

    /**
@@ -77,16 +45,14 @@ data class ControlInfo(
     * @return a [String] representation of `this`
     */
    override fun toString(): String {
        return component.flattenToString() +
                "$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType"
        return "$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType"
    }

    class Builder {
        lateinit var componentName: ComponentName
        lateinit var controlId: String
        lateinit var controlTitle: CharSequence
        var deviceType: Int = DeviceTypes.TYPE_UNKNOWN

        fun build() = ControlInfo(componentName, controlId, controlTitle, deviceType)
        fun build() = ControlInfo(controlId, controlTitle, deviceType)
    }
}
+9 −12
Original line number Diff line number Diff line
@@ -42,30 +42,27 @@ interface ControlsBindingController : UserAwareController {
    fun bindAndLoad(component: ComponentName, callback: LoadCallback)

    /**
     * Request to bind to the given services.
     * Request to bind to the given service.
     *
     * @param components a list of [ComponentName] of the services to bind
     * @param component The [ComponentName] of the service to bind
     */
    fun bindServices(components: List<ComponentName>)
    fun bindService(component: 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
     * @param structureInfo structure containing the controls to update
     */
    fun subscribe(controls: List<ControlInfo>)
    fun subscribe(structureInfo: StructureInfo)

    /**
     * Send an action performed on a [Control].
     *
     * @param controlInfo information about the actioned control, including the [ComponentName]
     * @param componentName name of the component
     * @param controlInfo information about the actioned control
     * @param action the action performed on the control
     */
    fun action(controlInfo: ControlInfo, action: ControlAction)
    fun action(componentName: ComponentName, controlInfo: ControlInfo, action: ControlAction)

    /**
     * Unsubscribe from all services to stop status updates.
+35 −72
Original line number Diff line number Diff line
@@ -26,9 +26,7 @@ import android.service.controls.IControlsActionCallback
import android.service.controls.IControlsSubscriber
import android.service.controls.IControlsSubscription
import android.service.controls.actions.ControlAction
import android.util.ArrayMap
import android.util.Log
import com.android.internal.annotations.GuardedBy
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -56,12 +54,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
    override val currentUserId: Int
        get() = currentUser.identifier

    @GuardedBy("componentMap")
    private val tokenMap: MutableMap<IBinder, ControlsProviderLifecycleManager> =
            ArrayMap<IBinder, ControlsProviderLifecycleManager>()
    @GuardedBy("componentMap")
    private val componentMap: MutableMap<Key, ControlsProviderLifecycleManager> =
            ArrayMap<Key, ControlsProviderLifecycleManager>()
    private var currentProvider: ControlsProviderLifecycleManager? = null

    private val actionCallbackService = object : IControlsActionCallback.Stub() {
        override fun accept(
@@ -108,83 +101,63 @@ open class ControlsBindingControllerImpl @Inject constructor(
    }

    private fun retrieveLifecycleManager(component: ComponentName):
            ControlsProviderLifecycleManager {
        synchronized(componentMap) {
            val provider = componentMap.getOrPut(Key(component, currentUser)) {
                createProviderManager(component)
            ControlsProviderLifecycleManager? {
        if (currentProvider != null && currentProvider?.componentName != component) {
            unbind()
        }
            tokenMap.putIfAbsent(provider.token, provider)
            return provider

        if (currentProvider == null) {
            currentProvider = createProviderManager(component)
        }

        return currentProvider
    }

    override fun bindAndLoad(
        component: ComponentName,
        callback: ControlsBindingController.LoadCallback
    ) {
        val provider = retrieveLifecycleManager(component)
        provider.maybeBindAndLoad(LoadSubscriber(callback))
        retrieveLifecycleManager(component)?.maybeBindAndLoad(LoadSubscriber(callback))
    }

    override fun subscribe(controls: List<ControlInfo>) {
        val controlsByComponentName = controls.groupBy { it.component }
    override fun subscribe(structureInfo: StructureInfo) {
        if (refreshing.compareAndSet(false, true)) {
            controlsByComponentName.forEach {
                val provider = retrieveLifecycleManager(it.key)
                backgroundExecutor.execute {
                    provider.maybeBindAndSubscribe(it.value.map { it.controlId })
                }
            }
        }
        // Unbind unneeded providers
        val providersWithFavorites = controlsByComponentName.keys
        synchronized(componentMap) {
            componentMap.forEach {
                if (it.key.component !in providersWithFavorites) {
                    backgroundExecutor.execute { it.value.unbindService() }
                }
            }
            val provider = retrieveLifecycleManager(structureInfo.componentName)
            provider?.maybeBindAndSubscribe(structureInfo.controls.map { it.controlId })
        }
    }

    override fun unsubscribe() {
        if (refreshing.compareAndSet(true, false)) {
            val providers = synchronized(componentMap) {
                componentMap.values.toList()
            }
            providers.forEach {
                backgroundExecutor.execute { it.unsubscribe() }
            }
            currentProvider?.unsubscribe()
        }
    }

    override fun action(controlInfo: ControlInfo, action: ControlAction) {
        val provider = retrieveLifecycleManager(controlInfo.component)
        provider.maybeBindAndSendAction(controlInfo.controlId, action)
    override fun action(
        componentName: ComponentName,
        controlInfo: ControlInfo,
        action: ControlAction
    ) {
        retrieveLifecycleManager(componentName)
            ?.maybeBindAndSendAction(controlInfo.controlId, action)
    }

    override fun bindServices(components: List<ComponentName>) {
        components.forEach {
            val provider = retrieveLifecycleManager(it)
            backgroundExecutor.execute { provider.bindService() }
        }
    override fun bindService(component: ComponentName) {
        retrieveLifecycleManager(component)?.bindService()
    }

    override fun changeUser(newUser: UserHandle) {
        if (newUser == currentUser) return
        synchronized(componentMap) {
            unbindAllProvidersLocked() // unbind all providers from the old user
        }

        unbind()

        refreshing.set(false)
        currentUser = newUser
    }

    private fun unbindAllProvidersLocked() {
        componentMap.values.forEach {
            if (it.user == currentUser) {
                it.unbindService()
            }
        }
    private fun unbind() {
        currentProvider?.unbindService()
        currentProvider = null
    }

    override fun onComponentRemoved(componentName: ComponentName) {
@@ -203,20 +176,12 @@ open class ControlsBindingControllerImpl @Inject constructor(
        return StringBuilder("  ControlsBindingController:\n").apply {
            append("    refreshing=${refreshing.get()}\n")
            append("    currentUser=$currentUser\n")
            append("    Providers:\n")
            synchronized(componentMap) {
                componentMap.values.forEach {
                    append("      $it\n")
                }
            }
            append("    Providers=$currentProvider\n")
        }.toString()
    }

    private abstract inner class CallbackRunnable(val token: IBinder) : Runnable {
        protected val provider: ControlsProviderLifecycleManager? =
                synchronized(componentMap) {
                    tokenMap.get(token)
                }
        protected val provider: ControlsProviderLifecycleManager? = currentProvider
    }

    private inner class OnLoadRunnable(
@@ -233,12 +198,10 @@ open class ControlsBindingControllerImpl @Inject constructor(
                Log.e(TAG, "User ${provider.user} is not current user")
                return
            }
            synchronized(componentMap) {
                if (token !in tokenMap.keys) {
            if (token != provider.token) {
                Log.e(TAG, "Provider for token:$token does not exist anymore")
                return
            }
            }
            callback.accept(list)
            provider.unbindService()
        }
+15 −40
Original line number Diff line number Diff line
@@ -59,14 +59,15 @@ interface ControlsController : UserAwareController {
    )

    /**
     * Request to subscribe for all favorite controls.
     * Request to subscribe for favorited controls per structure
     *
     * @param structureInfo structure to limit the subscription to
     * @see [ControlsBindingController.subscribe]
     */
    fun subscribeToFavorites()
    fun subscribeToFavorites(structureInfo: StructureInfo)

    /**
     * Request to unsubscribe to all providers.
     * Request to unsubscribe to the current provider.
     *
     * @see [ControlsBindingController.unsubscribe]
     */
@@ -75,11 +76,12 @@ interface ControlsController : UserAwareController {
    /**
     * Notify a [ControlsProviderService] that an action has been performed on a [Control].
     *
     * @param componentName the name of the service that provides the [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)
    fun action(componentName: ComponentName, controlInfo: ControlInfo, action: ControlAction)

    /**
     * Refresh the status of a [Control] with information provided from the service.
@@ -107,48 +109,29 @@ interface ControlsController : UserAwareController {
    // FAVORITE MANAGEMENT

    /**
     * Get a list of all favorite controls.
     * Get all the favorites.
     *
     * @return a list of [ControlInfo] with persistent information about the controls, including
     *         their corresponding [ComponentName].
     * @return a list of the structures that have at least one favorited control
     */
    fun getFavoriteControls(): List<ControlInfo>
    fun getFavorites(): List<StructureInfo>

    /**
     * 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.
     * @param componentName the name of the service that provides the [Control]
     * @return a list of the structures that have at least one favorited control
     */
    fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo>
    fun getFavoritesForComponent(componentName: ComponentName): List<StructureInfo>

    /**
     * Replaces the favorites for the given component.
     * Replaces the favorites for the given structure.
     *
     * 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.
     * @param structureInfo common structure for all of the favorited controls
     */
    fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
    fun replaceFavoritesForStructure(structureInfo: StructureInfo)

    /**
     * Return the number of favorites for a given component.
@@ -160,14 +143,6 @@ interface ControlsController : UserAwareController {
     */
    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].
     */
+162 −184

File changed.

Preview size limit exceeded, changes collapsed.

Loading