No Android 11 e versões mais recentes, o recurso de controles de dispositivo de acesso rápido permite que o usuário visualize e controle rapidamente dispositivos externos, como luzes, termostatos e câmeras, em uma affordance do usuário em até três interações em uma tela de início padrão. O OEM do dispositivo escolhe qual tela de início usar. Agregadores de dispositivos, como o Google Home, e apps de fornecedores terceirizados podem fornecer dispositivos para exibição nesse espaço. Esta página mostra como exibir os controles do dispositivo nesse espaço e vinculá-los ao app de controle.
Para adicionar esse suporte, crie e declare um ControlsProviderService
. Crie os
controles que seu app aceita com base em tipos de controle predefinidos e, em seguida, crie
editores para esses controles.
Interface do usuário
Os dispositivos são exibidos em Controles do dispositivo como widgets com modelo. Cinco widgets de controle de dispositivo estão disponíveis, conforme mostrado na figura a seguir:
![]() |
![]() |
![]() |
![]() |
![]() |
Tocar e segurar um widget leva você ao aplicativo para ter um controle mais profundo. É possível personalizar o ícone e a cor em cada widget, mas, para ter a melhor experiência do usuário, use o ícone e a cor padrão se o conjunto padrão corresponder ao dispositivo.
![Uma imagem mostrando o widget do painel de temperatura (aberto)](https://developer.android.google.cn/static/images/device-control/device-control-panel2.png?authuser=7&hl=pt)
Criar o serviço
Nesta seção, mostramos como criar o
ControlsProviderService
.
Esse serviço informa à interface do sistema Android que seu app contém controles de dispositivo
que precisam ser exibidos na área Controles do dispositivo da interface do Android.
A API ControlsProviderService
pressupõe familiaridade com fluxos reativos, conforme
definido no projeto de fluxos reativos do GitHub
e implementado nas interfaces de fluxo
Java 9.
A API foi criada com base nos seguintes conceitos:
- Editor:seu aplicativo é o editor.
- Assinante:a interface do sistema é o assinante e pode solicitar vários controles do editor.
- Assinatura:o período em que o editor pode enviar atualizações à interface do sistema. O editor ou o assinante pode fechar essa janela.
Declarar o serviço
Seu app precisa declarar um serviço, por exemplo, MyCustomControlService
, no
manifesto do app.
O serviço precisa incluir um filtro de intent para ControlsProviderService
. Esse
filtro permite que os aplicativos contribuam com controles para a interface do sistema.
Você também precisa de um label
que seja mostrado nos controles da interface do sistema.
O exemplo a seguir mostra como declarar um serviço:
<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>
Em seguida, crie um novo arquivo Kotlin chamado MyCustomControlService.kt
e faça ele
estender ControlsProviderService()
:
Kotlin
class MyCustomControlService : ControlsProviderService() { ... }
Java
public class MyCustomJavaControlService extends ControlsProviderService { ... }
Selecione o tipo de controle correto
A API fornece métodos do builder para criar os controles. Para preencher o builder, determine o dispositivo que você quer controlar e como o usuário interage com ele. Siga estas etapas:
- Escolha o tipo de dispositivo que o controle representa. A classe
DeviceTypes
é uma enumeração de todos os dispositivos compatíveis. O tipo é usado para determinar os ícones e as cores do dispositivo na interface. - Determine o nome do usuário, a localização do dispositivo (por exemplo, cozinha) e outros elementos textuais da interface associados ao controle.
- Escolha o melhor modelo para oferecer suporte à interação do usuário. Os controles recebem um
ControlTemplate
do aplicativo. Esse modelo mostra diretamente o estado do controle para o usuário, bem como os métodos de entrada disponíveis, ou seja, aControlAction
. A tabela a seguir descreve alguns dos modelos disponíveis e as ações compatíveis:
Template | Ação | Description |
ControlTemplate.getNoTemplateObject()
|
None
|
O aplicativo pode usar isso para transmitir informações sobre o controle, mas o usuário não pode interagir com ele. |
ToggleTemplate
|
BooleanAction
|
Representa um controle que pode ser alternado entre estados ativados e desativados. O objeto BooleanAction contém um campo que muda para representar o novo estado solicitado quando o usuário toca no controle.
|
RangeTemplate
|
FloatAction
|
Representa um widget de controle deslizante com os valores mínimo, máximo e de etapa especificados. Quando
o usuário interagir com o controle deslizante, envie um novo objeto FloatAction
de volta para o aplicativo com o valor atualizado.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Esse modelo é uma combinação de ToggleTemplate e RangeTemplate . Ele é compatível com eventos de toque e com um controle deslizante, por exemplo, para controlar luzes reguláveis.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Além de encapsular as ações anteriores, esse modelo permite que o usuário defina um modo, como Aquecer, Resfriar, Aquecer/Resfriar, Eco ou Desligado. |
StatelessTemplate
|
CommandAction
|
Usado para indicar um controle que fornece a capacidade de toque, mas cujo estado não pode ser determinado, como um controle remoto de televisão infravermelho. Você pode usar esse modelo para definir uma rotina ou macro, que é uma agregação de mudanças de controle e estado. |
Com essas informações, você pode criar o controle:
- Use a
classe de builder
Control.StatelessBuilder
quando o estado do controle for desconhecido. - Use a
classe de builder
Control.StatefulBuilder
quando o estado do controle for conhecido.
Por exemplo, para controlar uma lâmpada inteligente e um termostato, adicione as seguintes
constantes a 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; ... }
Criar editores para os controles
Depois de criado, ele precisa de um editor. O editor informa a IU do sistema sobre a existência do controle. A classe ControlsProviderService
tem dois métodos de editor que você precisa modificar no código do aplicativo:
createPublisherForAllAvailable()
: cria umPublisher
para todos os controles disponíveis no seu app. UseControl.StatelessBuilder()
para criar objetosControl
para esse editor.createPublisherFor()
: cria umPublisher
para uma lista de determinados controles, conforme identificado pelos identificadores de string. UseControl.StatefulBuilder
para criar esses objetosControl
, já que o editor precisa atribuir um estado a cada controle.
Criar o editor
Quando o app publica controles pela primeira vez na interface do sistema, ele não sabe
o estado de cada controle. Conseguir o estado pode ser uma operação demorada que envolve muitos saltos na rede do provedor de dispositivos. Use o método createPublisherForAllAvailable()
para anunciar os controles disponíveis para o sistema. Esse método usa a
classe builder Control.StatelessBuilder
, já que o estado de cada controle é
desconhecido.
Quando os controles aparecem na interface do Android , o usuário pode selecionar os favoritos.
Para usar corrotinas do Kotlin para criar um ControlsProviderService
, adicione uma nova
dependência ao 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") }
Depois de sincronizar os arquivos do Gradle, adicione o seguinte snippet ao seu Service
para
implementar 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); } }
Deslize o menu do sistema para baixo e localize o botão Controles do dispositivo, mostrado na Figura 4:
![Uma imagem mostrando a interface do sistema para controles de dispositivos](https://developer.android.google.cn/static/images/ui/device_controls_ui.png?authuser=7&hl=pt)
Tocar em Controles do dispositivo navega para uma segunda tela em que você pode selecionar seu app. Depois de selecionar o app, observe como o snippet anterior cria um menu de sistema personalizado mostrando seus novos controles, conforme mostrado na Figura 5:
![Imagem que mostra o menu do sistema com um controle de luz e termostato](https://developer.android.google.cn/static/images/ui/device_controls_example_1.png?authuser=7&hl=pt)
Agora, implemente o método createPublisherFor()
, adicionando o seguinte ao seu
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(); }
Nesse exemplo, o método createPublisherFor()
contém uma implementação fictícia
do que o app precisa fazer: comunicar-se com o dispositivo para
acessar o status e emitir esse status para o sistema.
O método createPublisherFor()
usa corrotinas e fluxos do Kotlin para atender
à API Reactive Streams necessária fazendo o seguinte:
- Cria um
Flow
. - Aguarda um segundo.
- Cria e emite o estado da iluminação inteligente.
- Espera mais um segundo.
- Cria e emite o estado do termostato.
Processar ações
O método performControlAction()
sinaliza quando o usuário interage com um
controle publicado. O tipo de ControlAction
enviado determina a ação.
Realize a ação adequada para determinado controle e atualize o estado
do dispositivo na interface do Android.
Para concluir o exemplo, adicione o seguinte ao seu 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()); } }
Execute o app, acesse o menu Controles do dispositivo e confira os controles de luz e do termostato.
![Uma imagem mostrando um controle de luz e termostato](https://developer.android.google.cn/static/images/ui/device_controls_example_2.png?authuser=7&hl=pt)