No Android 11 e em versões mais recentes, o recurso controles de dispositivo do acesso rápido permite ao usuário ver e controlar rapidamente dispositivos externos, como luzes, termostatos e câmeras, no menu liga/desliga do Android. Agregadores de dispositivos, como o Google Home, e apps de fornecedores terceirizados podem oferecer dispositivos para exibição nesse espaço. Este guia mostra como usar 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 compatíveis com seu app com base em tipos de controle predefinidos e 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 dispositivos diferentes estão disponíveis:
![]() |
![]() |
![]() |
![]() |
![]() |

Manter um widget pressionado leva você ao app para ter um controle mais profundo. Você pode 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, a menos que o conjunto padrão não corresponda ao dispositivo.
Criar o serviço
Esta seção mostra como criar o ControlsProviderService
.
Esse serviço informa à IU do sistema Android que seu app contém controles de dispositivo que precisam ser exibidos na área Controles do dispositivo da IU do Android.
A API ControlsProviderService
pressupõe familiaridade com streams reativos, conforme definido no projeto GitHub de streams reativos e implementado nas interfaces de fluxo do Java 9.
A API foi criada com base nestes conceitos:
- Editor: seu aplicativo é o editor
- Assinante: a IU do sistema é o assinante e pode solicitar vários controles do editor
- Assinatura: uma janela de tempo em que o editor pode enviar atualizações para a IU do sistema. Esta janela pode ser fechada pelo editor ou pelo assinante
Declarar o serviço
Seu app precisa declarar um serviço no manifesto do app. Inclua a permissão BIND_CONTROLS
.
O serviço precisa incluir um filtro de intent para ControlsProviderService
. Esse filtro permite que os apps contribuam com controles para a IU do 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>
Selecione o tipo de controle correto
A API fornece métodos do builder para criar os controles. Para preencher o builder, você precisa determinar o dispositivo que quer controlar e como o usuário precisa interagir com ele. Em particular, você precisa fazer o seguinte:
- 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 IU. - Determine o nome do usuário, a localização do dispositivo (por exemplo, cozinha) e outros elementos textuais da IU 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 de controle para o usuário, bem como os métodos de entrada disponíveis (ou seja, oControlAction
). A tabela a seguir descreve alguns dos modelos disponíveis e as ações compatíveis:
Modelo | Ação | Descrição |
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 interage com o controle deslizante, um novo objeto FloatAction precisa ser enviado de volta ao aplicativo com o valor atualizado.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Esse modelo é uma combinação de ToggleTemplate e RangeTemplate . Ele é compatível com eventos de toque, bem como um controle deslizante, por exemplo, em um controle de luzes reguláveis.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Além de encapsular uma das ações acima, esse modelo permite que o usuário defina um modo, como calor, frio, calor/frio, ecológico ou desativado. |
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 de 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, agora 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.
Criar editores para os controles
Depois que o controle for 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 criarControls
para este editor.createPublisherFor()
: cria umPublisher
para uma lista de determinados controles, conforme identificado pelos identificadores de string. UseControl.StatefulBuilder
para criar essesControls
, já que o editor precisa atribuir um estado a cada controle.
Criar o editor
Quando o app publica controles pela primeira vez na IU do sistema, o app desconhece o estado de cada controle. Reconhecer 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. Observe que esse método usa a classe de builder Control.StatelessBuilder
, porque o estado de cada controle é desconhecido.
Quando os controles aparecerem na IU do Android, o usuário poderá selecionar controles (ou seja, escolher favoritos) de interesse.
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)); } }
Depois que o usuário selecionar um conjunto de controles, crie um editor apenas para esses controles. Use o método createPublisherFor()
, já que esse método usa a classe de builder Control.StatefulBuilder
, que fornece o estado atual de cada controle (por exemplo, ativado ou desativado).
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); }
Processar ações
O método performControlAction()
indica que o usuário interagiu com um controle publicado. A ação é ditada pelo tipo de ControlAction
enviado. Execute a ação apropriada para o controle fornecido e atualize o estado do dispositivo na IU do 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); } }