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 según una indicación visual del usuario en un plazo de tres interacciones desde una selector predeterminado. El OEM del dispositivo elige el selector que usará. Dispositivo agregadores (por ejemplo, Google Home) y apps de proveedores externos proporcionar dispositivos para mostrar en este espacio. En esta página, se muestra cómo mostrar controles de dispositivos en este espacio y vincularlos a tu app de control.
Para agregar esta compatibilidad, crea y declara un ControlsProviderService
. Crea el
controla tu app en función de tipos de controles predefinidos y, luego,
a los publicadores
para estos controles.
Interfaz de usuario
Se muestran los dispositivos en Controles de dispositivos como widgets con plantilla. Cinco Hay widgets de control de dispositivos disponibles, como se muestra en la siguiente figura:
|
||
Tocar y Si mantienes presionado un widget, accederás a la app para tener un mayor control. Puedes personalizar el ícono y el color de cada widget, pero, para brindar la mejor experiencia del usuario, usar el ícono y el color predeterminados si el conjunto predeterminado coincide con el del dispositivo.
Cómo crear el servicio
En esta sección, se muestra cómo crear
ControlsProviderService
Este servicio le indica a la IU del sistema Android que tu app contiene controles de dispositivos.
que debe aparecer en el área Controles de dispositivos de la IU de Android.
La API de ControlsProviderService
asume que conoces los flujos reactivos, como
definido en el repositorio Reactive Streams GitHub
proyecto
e implementarse en el flujo de Java 9
interfaces.
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 un número. de controles del publicador.
- Suscripción: El período durante el cual el editor puede enviar actualizaciones. a la IU del sistema. El publicador o el suscriptor pueden cerrar esto. en la ventana modal.
Cómo declarar el servicio
Tu app debe declarar un servicio, como MyCustomControlService
, en
el manifiesto de su app.
El servicio debe incluir un filtro de intents para ControlsProviderService
. Esta
permite que las aplicaciones aporten controles a la IU del sistema.
También necesitas un label
que se muestra 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 Kotlin llamado MyCustomControlService.kt
y haz que sea
extender ControlsProviderService()
:
Kotlin
class MyCustomControlService : ControlsProviderService() { ... }
Java
public class MyCustomJavaControlService extends ControlsProviderService { ... }
Cómo seleccionar el tipo de control correcto
La API proporciona métodos de compilación para crear los controles. Para completar el del sitio web, determina el dispositivo que quieres controlar y cómo interactúa el usuario con él. Completa los pasos siguientes:
- Elige el tipo de dispositivo que el control represente. El
La clase
DeviceTypes
es una enumeración de todos los dispositivos compatibles. El tipo se usa para determinar la los íconos y los colores del dispositivo en la IU. - Determina el nombre que verá el usuario, la ubicación del dispositivo, por ejemplo, cocina y otros elementos textuales 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 muestra directamente el estado de control al usuario y los métodos de entrada disponibles, es decir,ControlAction
En la siguiente tabla, se describen algunas de las plantillas disponibles y las acciones que se deben realizar admiten:
Plantilla | Acción | Descripción |
ControlTemplate.getNoTemplateObject()
|
None
|
La aplicación podría usarla 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 pasos especificados. Cuándo
el usuario interactúa con el control deslizante y envía un nuevo elemento 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 las luces atenuables.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Además de encapsular las acciones anteriores, esta plantilla permite el usuario configura 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 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, 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.
Por ejemplo, para controlar una bombilla inteligente y un termostato, agrega lo siguiente:
constantes a tu MyCustomControlService
:
Kotlin
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() { ... }
Java
public class MyCustomJavaControlService extends ControlsProviderService { private final int LIGHT_ID = 1337; private final String LIGHT_TITLE = "My fancy light"; private final int LIGHT_TYPE = DeviceTypes.TYPE_LIGHT; private final int THERMOSTAT_ID = 1338; private final String THERMOSTAT_TITLE = "My fancy thermostat"; private final int THERMOSTAT_TYPE = DeviceTypes.TYPE_THERMOSTAT; ... }
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()
. para compilar objetosControl
para este publicador.createPublisherFor()
: Crea unPublisher
para una lista de controles determinados. según sus identificadores de cadenas. UsaControl.StatefulBuilder
para 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, la app no sabe
el estado de cada control. Obtener el estado puede llevar mucho tiempo
que incluyen muchos saltos
en la red del proveedor de dispositivos. Usa el método createPublisherForAllAvailable()
para anunciar al sistema los controles disponibles. Este método usa
Clase de compilador Control.StatelessBuilder
, ya que el estado de cada control es
desconocidos.
Una vez que los controles aparecen en la IU de Android , el usuario puede seleccionar controles de seguridad.
Si quieres usar corrutinas de Kotlin para crear un ControlsProviderService
, agrega un nuevo elemento
dependencia 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") }
Una vez que sincronices los archivos Gradle, agrega el siguiente fragmento a tu Service
para
implementa createPublisherForAllAvailable()
:
Kotlin
class MyCustomControlService : ControlsProviderService() { override fun createPublisherForAllAvailable(): Flow.Publisher= 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 ): Flow.Publisher { TODO() } override fun performControlAction( controlId: String, action: ControlAction, consumer: Consumer ) { TODO() } }
Java
public class MyCustomJavaControlService extends ControlsProviderService { private final int LIGHT_ID = 1337; private final String LIGHT_TITLE = "My fancy light"; private final int LIGHT_TYPE = DeviceTypes.TYPE_LIGHT; private final int THERMOSTAT_ID = 1338; private final String THERMOSTAT_TITLE = "My fancy thermostat"; private final int THERMOSTAT_TYPE = DeviceTypes.TYPE_THERMOSTAT; private boolean toggleState = false; private float rangeState = 18f; private final Map> controlFlows = new HashMap<>(); @NonNull @Override public Flow.Publisher createPublisherForAllAvailable() { List controls = new ArrayList<>(); controls.add(createStatelessControl(LIGHT_ID, LIGHT_TITLE, LIGHT_TYPE)); controls.add(createStatelessControl(THERMOSTAT_ID, THERMOSTAT_TITLE, THERMOSTAT_TYPE)); return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls)); } @NonNull @Override public Flow.Publisher createPublisherFor(@NonNull List controlIds) { ReplayProcessor updatePublisher = ReplayProcessor.create(); controlIds.forEach(control -> { controlFlows.put(control, updatePublisher); updatePublisher.onNext(createLight()); updatePublisher.onNext(createThermostat()); }); return FlowAdapters.toFlowPublisher(updatePublisher); } }
Desliza hacia abajo en el menú del sistema y busca el botón Device controls, que aparece en Figura 4:
Si presionas Controles del dispositivo, se navegará a una segunda pantalla en la que puedes seleccionar tu app. Una vez que selecciones tu app, podrás ver cómo el fragmento anterior crea una menú personalizado del sistema que muestra los controles nuevos, como se muestra en la figura 5.
Ahora, implementa el método createPublisherFor()
y agrega lo siguiente a tu
Service
Kotlin
private val job = SupervisorJob() private val scope = CoroutineScope(Dispatchers.IO + job) private val controlFlows = mutableMapOf>() private var toggleState = false private var rangeState = 18f override fun createPublisherFor(controlIds: List ): Flow.Publisher { val flow = MutableSharedFlow (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 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() }
Java
@NonNull @Override public Flow.PublishercreatePublisherFor(@NonNull List controlIds) { ReplayProcessor updatePublisher = ReplayProcessor.create(); controlIds.forEach(control -> { controlFlows.put(control, updatePublisher); updatePublisher.onNext(createLight()); updatePublisher.onNext(createThermostat()); }); return FlowAdapters.toFlowPublisher(updatePublisher); } private Control createStatelessControl(int id, String title, int type) { Intent intent = new Intent(this, MainActivity.class) .putExtra(EXTRA_MESSAGE, title) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent action = PendingIntent.getActivity( this, id, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); return new Control.StatelessBuilder(id + "", action) .setTitle(title) .setDeviceType(type) .build(); } private Control createLight() { return createStatefulControl( LIGHT_ID, LIGHT_TITLE, LIGHT_TYPE, toggleState, new ToggleTemplate( LIGHT_ID + "", new ControlButton( toggleState, String.valueOf(toggleState).toUpperCase(Locale.getDefault()) ) ) ); } private Control createThermostat() { return createStatefulControl( THERMOSTAT_ID, THERMOSTAT_TITLE, THERMOSTAT_TYPE, rangeState, new RangeTemplate( THERMOSTAT_ID + "", 15f, 25f, rangeState, 0.1f, "%1.1f" ) ); } private Control createStatefulControl(int id, String title, int type, T state, ControlTemplate template) { Intent intent = new Intent(this, MainActivity.class) .putExtra(EXTRA_MESSAGE, "$title $state") .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent action = PendingIntent.getActivity( this, id, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); return new Control.StatefulBuilder(id + "", action) .setTitle(title) .setDeviceType(type) .setStatus(Control.STATUS_OK) .setControlTemplate(template) .build(); }
En este ejemplo, el método createPublisherFor()
contiene un valor falso
implementación de lo que debe hacer tu app: comunicarse con tu 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 necesaria si haces lo siguiente:
- 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
el control publicado. El tipo de ControlAction
enviado determina la acción.
Realiza la acción adecuada para el control dado y, luego, actualiza el estado
del dispositivo en la IU de Android.
Para completar el ejemplo, agrega lo siguiente a tu Service
:
Kotlin
override fun performControlAction( controlId: String, action: ControlAction, consumer: Consumer) { 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) }
Java
@Override public void performControlAction(@NonNull String controlId, @NonNull ControlAction action, @NonNull Consumerconsumer) { ReplayProcessor processor = controlFlows.get(controlId); if (processor == null) return; if (controlId.equals(LIGHT_ID + "")) { consumer.accept(ControlAction.RESPONSE_OK); if (action instanceof BooleanAction) toggleState = ((BooleanAction) action).getNewState(); processor.onNext(createLight()); } if (controlId.equals(THERMOSTAT_ID + "")) { consumer.accept(ControlAction.RESPONSE_OK); if (action instanceof FloatAction) rangeState = ((FloatAction) action).getNewValue() processor.onNext(createThermostat()); } }
Ejecuta la app, accede al menú Device controls, y observa las luces y controles del termostato.