Cómo controlar dispositivos externos

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

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.

Espacio de control de dispositivos en la IU de Android

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:

Widget de activación o desactivación
Activar o desactivar
Widget de activación o desactivación con el control deslizante
Activar o desactivar con el control deslizante
Widget de rango
Rango (no se puede activar ni desactivar)
Widget de activación o desactivación sin estado
Activar o desactivar sin estado
Widget del panel de temperatura (cerrado)
Panel de temperatura (cerrado)
Widget del panel de temperatura (abierto)
Panel de temperatura (abierto)

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:

  1. 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.
  2. 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.
  3. 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:

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 un Publisher para todos los controles disponibles en tu app. Usa Control.StatelessBuilder() a fin de compilar Controls para este publicador.
  • createPublisherFor(): Crea un Publisher para una lista de controles determinados, reconocidos por sus identificadores de string. Usa Control.StatefulBuilder para compilar estos Controls, 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 Publisher createPublisherForAllAvailable() {
        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: ReplayProcessor

    override 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 ReplayProcessor updatePublisher;

@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,
    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 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);
  }
}