En Android 11 y versiones posteriores, la función Controles de dispositivos de Acceso rápido permite al usuario ver y controlar rápidamente dispositivos externos, como luces, termostatos y cámaras, desde el menú de encendido de Android. Los agregadores de dispositivos (por ejemplo, Google Home) y las apps de terceros pueden proporcionar dispositivos para mostrar en este espacio. En esta guía, se explica cómo mostrar los controles del dispositivo en este espacio y vincularlos a tu app de control.
Para agregar esta compatibilidad, crea y declara un ControlsProviderService
, crea los controles que tu app admite según 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 diferentes disponibles:
![]() |
![]() |
![]() |
![]() |
![]() |

Si mantienes presionado un widget, accederás a la app para obtener un mayor control. Puedes personalizar el ícono y el color de cada widget; pero, para obtener la mejor experiencia del usuario, debes usar el ícono y el color predeterminados, a menos que el conjunto predeterminado no coincida 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 deberían aparecer en el área Controles de dispositivos de la IU de Android.
La API de ControlsProviderService
asume que conoces el funcionamiento de las transmisiones reactivas, como se define en el proyecto de GitHub sobre transmisiones reactivas y se implementa en las interfaces de Java 9 Flow.
La API se basa en estos conceptos:
- Publicador: Tu aplicación es el publicador.
- Suscriptor: La IU del sistema es el suscriptor y puede solicitar varios controles al publicador.
- Suscripción: Un período en el que el publicador puede enviar actualizaciones a la IU del sistema. El publicador o el suscriptor pueden cerrar esta ventana.
Cómo declarar el servicio
Tu app debe declarar un servicio en su manifiesto. Asegúrate de incluir el permiso BIND_CONTROLS
.
El servicio debe incluir un filtro de intents para ControlsProviderService
. Este filtro permite que las aplicaciones aporten controles a la IU del sistema.
<!-- New signature permission to ensure only systemui can bind to these services -->
<service android:name="YOUR-SERVICE-NAME" android:label="YOUR-SERVICE-LABEL"
android:permission="android.permission.BIND_CONTROLS">
<intent-filter>
<action android:name="android.service.controls.ControlsProviderService" />
</intent-filter>
</service>
Cómo seleccionar el tipo de control correcto
La API proporciona métodos de compilación para crear los controles. Si quieres propagar el compilador, debes determinar el dispositivo que deseas controlar y el modo en que el usuario debería interactuar con él. En particular, debes hacer lo siguiente:
- Elige el tipo de dispositivo que el control represente. La clase
DeviceTypes
es una enumeración de todos los dispositivos compatibles actualmente. Se usa el tipo para determinar los íconos y los colores del dispositivo en la IU. - Determina el nombre que verá 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 una
ControlTemplate
desde la aplicación. Esta plantilla le muestra directamente el estado de control al usuario, además de los métodos de entrada disponibles (es decir,ControlAction
). 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 esta plantilla para transmitir información sobre el control, pero el usuario no puede interactuar con ella. |
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 toca el control.
|
RangeTemplate
|
FloatAction
|
Representa un widget de control deslizante con valores mínimos, máximos y de pasos. Cuando el usuario interactúe con el control deslizante, se debería enviar un objeto FloatAction nuevo 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, por ejemplo, un control para luces atenuables.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Además de encapsular una de las acciones anteriores, esta plantilla le permite al usuario configurar un modo, como calor, frío, calor/frío, ecológico o apagado. |
StatelessTemplate
|
CommandAction
|
Se usa para representar un control que proporciona capacidad táctil, pero del que no se puede determinar su estado, como un control remoto de televisión IR. 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, ya puedes crear el control:
- Usa la clase de compilador
Control.StatelessBuilder
cuando no se conozca el estado del control. - Usa la clase de compilador
Control.StatefulBuilder
cuando se conozca el estado del control.
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 unPublisher
para todos los controles disponibles en tu app. UsaControl.StatelessBuilder()
a fin de compilarControls
para este publicador.createPublisherFor()
: Crea unPublisher
para una lista de controles determinados, reconocidos por sus identificadores de string. UsaControl.StatefulBuilder
para compilar estosControls
, 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 lenta que implique muchos saltos en la red del proveedor de dispositivos. Usa el método createPublisherForAllAvailable()
para anunciar al sistema los controles disponibles. Ten en cuenta que 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, el usuario puede seleccionar los controles que le interesan (es decir, elegir favoritos).
Kotlin
/* If you choose to use Reactive Streams API, you will need to put the following * into your module's build.gradle file: * implementation 'org.reactivestreams:reactive-streams:1.0.3' * implementation 'io.reactivex.rxjava2:rxjava:2.2.0' */ class MyCustomControlService : ControlsProviderService() { override fun createPublisherForAllAvailable(): Flow.Publisher{ val context: Context = baseContext val i = Intent() val pi = PendingIntent.getActivity( context, CONTROL_REQUEST_CODE, i, PendingIntent.FLAG_UPDATE_CURRENT ) val controls = mutableListOf () val control = Control.StatelessBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT .build() controls.add(control) // Create more controls here if needed and add it to the ArrayList // Uses the RxJava 2 library return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls)) } }
Java
/* If you choose to use Reactive Streams API, you will need to put the following * into your module's build.gradle file: * implementation 'org.reactivestreams:reactive-streams:1.0.3' * implementation 'io.reactivex.rxjava2:rxjava:2.2.0' */ public class MyCustomControlService extends ControlsProviderService { @Override public PublishercreatePublisherForAllAvailable() { Context context = getBaseContext(); Intent i = new Intent(); PendingIntent pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); List controls = new ArrayList<>(); Control control = new Control.StatelessBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT .build(); controls.add(control); // Create more controls here if needed and add it to the ArrayList // Uses the RxJava 2 library return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls)); } }
Una vez que el usuario haya seleccionado un conjunto de controles, crea un publicador exclusivo para esos controles. Utiliza el método createPublisherFor()
, ya que usa la clase de compilador Control.StatefulBuilder
, que proporciona el estado actual de cada control (por ejemplo, activado o desactivado).
Kotlin
class MyCustomControlService : ControlsProviderService() { private lateinit var updatePublisher: ReplayProcessoroverride fun createPublisherFor(controlIds: MutableList ): Flow.Publisher { val context: Context = baseContext /* Fill in details for the activity related to this device. On long press, * this Intent will be launched in a bottomsheet. Please design the activity * accordingly to fit a more limited space (about 2/3 screen height). */ val i = Intent(this, CustomSettingsActivity::class.java) val pi = PendingIntent.getActivity(context, CONTROL_REQUEST_CODE, i, PendingIntent.FLAG_UPDATE_CURRENT) updatePublisher = ReplayProcessor.create() if (controlIds.contains(MY-UNIQUE-DEVICE-ID)) { val control = Control.StatefulBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY -CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT // Required: Current status of the device .setStatus(Control.CURRENT-STATUS) // For example, Control.STATUS_OK .build() updatePublisher.onNext(control) } // If you have other controls, check that they have been selected here // Uses the Reactive Streams API updatePublisher.onNext(control) } }
Java
private ReplayProcessorupdatePublisher; @Override public Publisher createPublisherFor(List controlIds) { Context context = getBaseContext(); /* Fill in details for the activity related to this device. On long press, * this Intent will be launched in a bottomsheet. Please design the activity * accordingly to fit a more limited space (about 2/3 screen height). */ Intent i = new Intent(); PendingIntent pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); updatePublisher = ReplayProcessor.create(); // For each controlId in controlIds if (controlIds.contains(MY-UNIQUE-DEVICE-ID)) { Control control = new Control.StatefulBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT // Required: Current status of the device .setStatus(Control.CURRENT-STATUS) // For example, Control.STATUS_OK .build(); updatePublisher.onNext(control); } // Uses the Reactive Streams API return FlowAdapters.toFlowPublisher(updatePublisher); }
Cómo controlar acciones
El método performControlAction()
indica que el usuario interactuó con un control publicado. Se determina la acción según el tipo de ControlAction
que se envió. Realiza la acción adecuada para el control específico y, luego, actualiza el estado del dispositivo en la IU de Android.
Kotlin
class MyCustomControlService : ControlsProviderService() { override fun performControlAction( controlId: String, action: ControlAction, consumer: Consumer) { /* First, locate the control identified by the controlId. Once it is located, you can * interpret the action appropriately for that specific device. For instance, the following * assumes that the controlId is associated with a light, and the light can be turned on * or off. */ if (action is BooleanAction) { // Inform SystemUI that the action has been received and is being processed consumer.accept(ControlAction.RESPONSE_OK) // In this example, action.getNewState() will have the requested action: true for “On”, // false for “Off”. /* This is where application logic/network requests would be invoked to update the state of * the device. * After updating, the application should use the publisher to update SystemUI with the new * state. */ Control control = Control.StatefulBuilder (MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT // Required: Current status of the device .setStatus(Control.CURRENT-STATUS) // For example, Control.STATUS_OK .build() // This is the publisher the application created during the call to createPublisherFor() updatePublisher.onNext(control) } } }
Java
@Override public void performControlAction(String controlId, ControlAction action, Consumerconsumer) { /* First, locate the control identified by the controlId. Once it is located, you can * interpret the action appropriately for that specific device. For instance, the following * assumes that the controlId is associated with a light, and the light can be turned on * or off. */ if (action instanceof BooleanAction) { // Inform SystemUI that the action has been received and is being processed consumer.accept(ControlAction.RESPONSE_OK); BooleanAction action = (BooleanAction) action; // In this example, action.getNewState() will have the requested action: true for “On”, // false for “Off”. /* This is where application logic/network requests would be invoked to update the state of * the device. * After updating, the application should use the publisher to update SystemUI with the new * state. */ Control control = new Control.StatefulBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT // Required: Current status of the device .setStatus(Control.CURRENT-STATUS) // For example, Control.STATUS_OK .build(); // This is the publisher the application created during the call to createPublisherFor() updatePublisher.onNext(control); } }