En Android 11 y versiones posteriores, la función Controles de acceso rápido a dispositivos permite que los usuarios vean y controlen rápidamente dispositivos externos, como luces, termostatos y cámaras, desde una affordance del usuario en tres interacciones desde un selector predeterminado. El OEM del dispositivo elige qué selector usar. Los agregadores de dispositivos, como Google Home, y las apps de proveedores externos pueden proporcionar dispositivos para que se muestren en este espacio. En esta página, se muestra cómo mostrar los controles de dispositivos en este espacio y vincularlos a tu app de control.
Para agregar esta compatibilidad, crea y declara un ControlsProviderService. Crea los controles que admite tu app en función de los tipos de controles predefinidos y, luego, crea publicadores para estos controles.
Interfaz de usuario
Se muestran los dispositivos en Controles de dispositivos como widgets con plantilla. Hay cinco widgets de control de dispositivos disponibles, como se muestra en la siguiente figura:
|
|
|
|
|
Si mantienes presionado un widget, se abrirá la app para que puedas controlarla con más detalle. Puedes personalizar el ícono y el color de cada widget, pero, para obtener la mejor experiencia del usuario, usa el ícono y el color predeterminados si el conjunto predeterminado coincide con el dispositivo.
Cómo crear el servicio
En esta sección, se muestra cómo crear el
ControlsProviderService. Este servicio le indica a la IU del sistema Android que tu app contiene controles de dispositivos que se deben mostrar en el área de Controles de dispositivos de la IU de Android.
La API de ControlsProviderService supone que conoces los flujos reactivos, como se definen en el proyecto de GitHub de flujos reactivos y se implementan en las interfaces de flujo de Java 9.
La API se basa en los siguientes conceptos:
- Publicador: Tu aplicación es el publicador.
- Suscriptor: La IU del sistema es el suscriptor y puede solicitar una cantidad de controles al publicador.
- Suscripción: Es el período durante el cual el publicador puede enviar actualizaciones a la IU del sistema. Tanto el publicador como el suscriptor pueden cerrar esta ventana.
Cómo declarar el servicio
Tu app debe declarar un servicio, como MyCustomControlService, en el manifiesto de la app.
El servicio debe incluir un filtro de intents para ControlsProviderService. Este filtro permite que las aplicaciones contribuyan con controles a la IU del sistema.
También necesitas un label que se muestre en los controles de la IU del sistema.
En el siguiente ejemplo, se muestra cómo declarar un servicio:
<service
android:name="MyCustomControlService"
android:label="My Custom Controls"
android:permission="android.permission.BIND_CONTROLS"
android:exported="true"
>
<intent-filter>
<action android:name="android.service.controls.ControlsProviderService" />
</intent-filter>
</service>
A continuación, crea un nuevo archivo de Kotlin llamado MyCustomControlService.kt y haz que
extienda ControlsProviderService:
class MyCustomControlService : ControlsProviderService() { // ... }
Cómo seleccionar el tipo de control correcto
La API proporciona métodos de compilación para crear los controles. Para propagar el compilador, determina el dispositivo que deseas controlar y cómo interactúa el usuario con él. Completa los pasos siguientes:
- Elige el tipo de dispositivo que el control represente. La clase
DeviceTypeses una enumeración de todos los dispositivos compatibles. El tipo se usa para determinar los íconos y los colores del dispositivo en la IU. - Determina el nombre para el usuario, la ubicación del dispositivo (por ejemplo, la cocina) y otros elementos de texto de la IU asociados con el control.
- Elige la mejor plantilla para admitir la interacción del usuario. A los controles se les asigna un
ControlTemplatedesde la aplicación. Esta plantilla muestra directamente el estado de control al usuario, así como los métodos de entrada disponibles, es decir, elControlAction. En la siguiente tabla, se describen algunas de las plantillas disponibles y las acciones que admiten:
| Plantilla | Acción | Descripción |
ControlTemplate.getNoTemplateObject()
|
None
|
La aplicación puede usar este estado para transmitir información sobre el control, pero el usuario no puede interactuar con él. |
ToggleTemplate
|
BooleanAction
|
Representa un control que se puede pasar de estado habilitado a inhabilitado. El objeto BooleanAction contiene un campo que cambia para representar el nuevo estado solicitado cuando el usuario presiona el control.
|
RangeTemplate
|
FloatAction
|
Representa un widget de control deslizante con valores mínimos, máximos y de paso especificados. Cuando el usuario interactúe con el control deslizante, envía un nuevo objeto FloatAction a la aplicación con el valor actualizado.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Esta plantilla es una combinación de ToggleTemplate y RangeTemplate. Admite eventos táctiles y un control deslizante, como para controlar luces regulables.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Además de encapsular las acciones anteriores, esta plantilla permite que el usuario establezca un modo, como calor, frío, calor/frío, eco o apagado. |
StatelessTemplate
|
CommandAction
|
Se usa para indicar un control que proporciona capacidad táctil, pero cuyo estado no se puede determinar, como un control remoto de televisión por infrarrojos. Puedes usar esta plantilla para definir una rutina o macro, que es una agregación de cambios de control y de estado. |
Con esta información, puedes crear el control:
- Usa la clase de compilador
Control.StatelessBuildercuando se desconozca el estado del control. - Usa la clase de compilador
Control.StatefulBuildercuando se conozca el estado del control.
Por ejemplo, para controlar una lámpara inteligente y un termostato, agrega las siguientes constantes a tu MyCustomControlService:
private const val LIGHT_ID = 1234 private const val LIGHT_TITLE = "My fancy light" private const val LIGHT_TYPE = DeviceTypes.TYPE_LIGHT private const val THERMOSTAT_ID = 5678 private const val THERMOSTAT_TITLE = "My fancy thermostat" private const val THERMOSTAT_TYPE = DeviceTypes.TYPE_THERMOSTAT class MyCustomControlService : ControlsProviderService() { // ... }
Cómo crear publicadores para los controles
Después de crear el control, este necesita un publicador. El publicador informa a la IU del sistema que hay un control. La clase ControlsProviderService tiene dos métodos de publicador que debes anular en el código de tu aplicación:
createPublisherForAllAvailable: Crea unPublisherpara todos los controles disponibles en tu app. UsaControl.StatelessBuilderpara compilar objetosControlpara este publicador.createPublisherFor: Crea unPublisherpara una lista de controles determinados, según se identifican por sus identificadores de cadena. UsaControl.StatefulBuilderpara compilar estos objetosControl, ya que el publicador debe asignar un estado a cada control.
Cómo crear el publicador
Cuando tu app publica controles por primera vez en la IU del sistema, no conoce
el estado de cada control. Obtener el estado puede ser una operación que lleve mucho tiempo,
ya que implica muchos saltos en la red del proveedor del dispositivo. Usa el método createPublisherForAllAvailable para anunciar los controles disponibles al sistema. Este método usa la clase de compilador Control.StatelessBuilder, ya que se desconoce el estado de cada control.
Una vez que los controles aparecen en la IU de Android, los usuarios pueden seleccionar sus controles favoritos.
Para usar corrutinas de Kotlin con el objetivo de crear un ControlsProviderService, agrega una dependencia nueva a tu build.gradle:
Groovy
dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4" }
Kotlin
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4") }
Después de sincronizar tus archivos de Gradle, agrega el siguiente fragmento a tu Service para
implementar createPublisherForAllAvailable:
class MyCustomControlService : ControlsProviderService() { override fun createPublisherForAllAvailable(): Flow.Publisher<Control> = flowPublish { send(createStatelessControl(LIGHT_ID, LIGHT_TITLE, LIGHT_TYPE)) send(createStatelessControl(THERMOSTAT_ID, THERMOSTAT_TITLE, THERMOSTAT_TYPE)) } private fun createStatelessControl(id: Int, title: String, type: Int): Control { val intent = Intent(this, MainActivity::class.java) .putExtra(EXTRA_MESSAGE, title) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val action = PendingIntent.getActivity( this, id, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, ) return Control.StatelessBuilder(id.toString(), action) .setTitle(title) .setDeviceType(type) .build() } override fun createPublisherFor(controlIds: List<String>): Flow.Publisher<Control> { TODO() } override fun performControlAction( controlId: String, action: ControlAction, consumer: Consumer<Int>, ) { TODO() } }
Desliza el dedo hacia abajo en el menú del sistema y busca el botón Controles del dispositivo, que se muestra en la figura 4:
Si presionas Controles del dispositivo, se abrirá una segunda pantalla en la que podrás seleccionar tu app. Una vez que lo hagas, verás cómo el fragmento anterior crea un menú del sistema personalizado que muestra tus controles nuevos, como se indica en la figura 5:
Ahora, implementa el método createPublisherFor y agrega lo siguiente a tu
Service:
private val job = SupervisorJob() private val scope = CoroutineScope(Dispatchers.IO + job) private val controlFlows = mutableMapOf<String, MutableSharedFlow<Control>>() private var toggleState = false private var rangeState = 18f override fun createPublisherFor(controlIds: List<String>): Flow.Publisher<Control> { val flow = MutableSharedFlow<Control>(replay = 2, extraBufferCapacity = 2) controlIds.forEach { controlFlows[it] = flow } scope.launch { delay(1000) // Retrieving the toggle state. flow.tryEmit(createLight()) delay(1000) // Retrieving the range state. flow.tryEmit(createThermostat()) } return flow.asPublisher() } private fun createLight() = createStatefulControl( LIGHT_ID, LIGHT_TITLE, LIGHT_TYPE, toggleState, ToggleTemplate( LIGHT_ID.toString(), ControlButton( toggleState, toggleState.toString().uppercase(Locale.getDefault()), ), ), ) private fun createThermostat() = createStatefulControl( THERMOSTAT_ID, THERMOSTAT_TITLE, THERMOSTAT_TYPE, rangeState, RangeTemplate( THERMOSTAT_ID.toString(), 15f, 25f, rangeState, 0.1f, "%1.1f", ), ) private fun <T> createStatefulControl( id: Int, title: String, type: Int, state: T, template: ControlTemplate, ): Control { val intent = Intent(this, MainActivity::class.java) .putExtra(EXTRA_MESSAGE, "$title $state") .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val action = PendingIntent.getActivity( this, id, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, ) return Control.StatefulBuilder(id.toString(), action) .setTitle(title) .setDeviceType(type) .setStatus(Control.STATUS_OK) .setControlTemplate(template) .build() } override fun onDestroy() { super.onDestroy() job.cancel() }
En este ejemplo, el método createPublisherFor contiene una implementación falsa de lo que debe hacer tu app: comunicarse con el dispositivo para recuperar su estado y emitirlo al sistema.
El método createPublisherFor usa corrutinas y flujos de Kotlin para satisfacer la API de Reactive Streams requerida de la siguiente manera:
- Crea un
Flow. - Espera un segundo.
- Crea y emite el estado de la lámpara inteligente.
- Espera otro segundo.
- Crea y emite el estado del termostato.
Cómo controlar acciones
El método performControlAction indica cuando el usuario interactúa con un control publicado. El tipo de ControlAction que se envía determina la acción.
Realiza la acción adecuada para el control determinado y, luego, actualiza el estado del dispositivo en la IU de Android.
Para completar el ejemplo, agrega lo siguiente a tu Service:
override fun performControlAction( controlId: String, action: ControlAction, consumer: Consumer<Int>, ) { controlFlows[controlId]?.let { flow -> when (controlId) { LIGHT_ID.toString() -> { consumer.accept(ControlAction.RESPONSE_OK) if (action is BooleanAction) toggleState = action.newState flow.tryEmit(createLight()) } THERMOSTAT_ID.toString() -> { consumer.accept(ControlAction.RESPONSE_OK) if (action is FloatAction) rangeState = action.newValue flow.tryEmit(createThermostat()) } else -> consumer.accept(ControlAction.RESPONSE_FAIL) } } ?: consumer.accept(ControlAction.RESPONSE_FAIL) }
Ejecuta la app, accede al menú Controles del dispositivo y consulta los controles de la luz y el termostato.