In Android 11 e versioni successive, la funzionalità Controlli dei dispositivi di accesso rapido consente all'utente di visualizzare e controllare rapidamente i dispositivi esterni come luci, termostati e videocamere da un'invito dell'utente entro tre interazioni da un Avvio app predefinito. L'OEM del dispositivo sceglie quale app Avvio app utilizzare. Gli aggregatori di dispositivi, ad esempio Google Home, e le app di fornitori di terze parti possono fornire dispositivi da visualizzare in questo spazio. Questa pagina mostra come visualizzare i controlli dei dispositivi in questo spazio e collegarli all'app di controllo.
Per aggiungere questo supporto, crea e dichiara un ControlsProviderService
. Crea i controlli supportati dalla tua app in base ai tipi di controlli predefiniti, quindi crea dei publisher per questi controlli.
Interfaccia utente
I dispositivi vengono visualizzati in Controllo dispositivi come widget basati su modelli. Sono disponibili cinque widget per il controllo dei dispositivi, come mostrato nella figura seguente:
![]() |
![]() |
![]() |
![]() |
![]() |
Se tocchi e tieni premuto un widget, vai all'app per un controllo più approfondito. Puoi personalizzare l'icona e il colore di ogni widget, ma per un'esperienza utente ottimale, utilizza l'icona e il colore predefiniti, se quelli predefiniti corrispondono al dispositivo.
![Un'immagine che mostra il widget del riquadro della temperatura (aperto)](https://developer.android.com/static/images/device-control/device-control-panel2.png?authuser=19&hl=it)
Crea il servizio
Questa sezione mostra come creare ControlsProviderService
.
Questo servizio comunica all'interfaccia utente del sistema Android che la tua app contiene controlli dei dispositivi
che devono essere visualizzati nell'area Controlli dei dispositivi della UI Android.
L'API ControlsProviderService
presuppone familiarità con i flussi reattivi, come
definito nel progetto GitHub
Reactive Streams
e implementato nelle interfacce Java 9 Flow.
L'API si basa sui seguenti concetti:
- Publisher:la tua applicazione è il publisher.
- Sottoscrittore:l'interfaccia utente di sistema corrisponde all'abbonato e può richiedere una serie di controlli all'editore.
- Abbonamento: il periodo di tempo durante il quale l'editore può inviare aggiornamenti all'UI di sistema. Sia l'editore che il sottoscrittore possono chiudere questa finestra.
Dichiara il servizio
La tua app deve dichiarare un servizio, ad esempio MyCustomControlService
, nel
file manifest dell'app.
Il servizio deve includere un filtro per intent per ControlsProviderService
. Questo
filtro consente alle applicazioni di contribuire ai controlli all'interfaccia utente di sistema.
È necessario anche un label
che venga visualizzato nei controlli dell'interfaccia utente di sistema.
L'esempio seguente mostra come dichiarare un servizio:
<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>
Poi, crea un nuovo file Kotlin denominato MyCustomControlService.kt
e fai in modo che espanda ControlsProviderService()
:
Kotlin
class MyCustomControlService : ControlsProviderService() { ... }
Java
public class MyCustomJavaControlService extends ControlsProviderService { ... }
Seleziona il tipo di controllo corretto
L'API fornisce metodi del builder per creare i controlli. Per compilare il builder, determina il dispositivo che vuoi controllare e la modalità di interazione dell'utente con quest'ultimo. Procedi nel seguente modo:
- Scegli il tipo di dispositivo rappresentato dal controllo. La classe
DeviceTypes
è un elenco di tutti i dispositivi supportati. Il tipo viene utilizzato per determinare le icone e i colori del dispositivo nell'interfaccia utente. - Determina il nome rivolto all'utente, la posizione del dispositivo, ad esempio cucina, e altri elementi testuali dell'interfaccia utente associati al controllo.
- Scegliere il modello migliore per supportare l'interazione dell'utente. Ai controlli viene assegnato un
ControlTemplate
dall'applicazione. Questo modello mostra direttamente all'utente lo stato del controllo, nonché i metodi di input disponibili, ovveroControlAction
. La seguente tabella illustra alcuni dei modelli disponibili e le azioni che supportano:
Modello | Azione | Description |
ControlTemplate.getNoTemplateObject()
|
None
|
L'applicazione potrebbe utilizzarlo per trasmettere informazioni sul controllo, ma l'utente non può interagire con quest'ultimo. |
ToggleTemplate
|
BooleanAction
|
Rappresenta un controllo che può essere commutato tra lo stato attivato e disattivato. L'oggetto BooleanAction contiene un campo che cambia
per rappresentare il nuovo stato richiesto quando l'utente tocca il controllo.
|
RangeTemplate
|
FloatAction
|
Rappresenta un widget di scorrimento con valori min, max e passi specificati. Quando
l'utente interagisce con il dispositivo di scorrimento, invia un nuovo oggetto FloatAction
all'applicazione con il valore aggiornato.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Questo modello è una combinazione di ToggleTemplate e
RangeTemplate . Supporta eventi touch e un cursore per controllare le luci regolabili.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Oltre a incapsulare le azioni precedenti, questo modello consente all'utente di impostare una modalità, ad esempio Caldo, Freddo, Caldo/Freddo, Eco o Spento. |
StatelessTemplate
|
CommandAction
|
Utilizzato per indicare un controllo che offre funzionalità touch, ma il cui stato non può essere determinato, ad esempio un telecomando a infrarossi di un televisore. Puoi utilizzare questo modello per definire una routine o una macro, che è un'aggregazione del controllo e delle modifiche di stato. |
Con queste informazioni, puoi creare il controllo:
- Utilizza la classe builder
Control.StatelessBuilder
quando lo stato del controllo è sconosciuto. - Utilizza la classe builder
Control.StatefulBuilder
quando lo stato del controllo è noto.
Ad esempio, per controllare una lampadina smart e un termostato, aggiungi le seguenti
costanti 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; ... }
Creare publisher per i controlli
Dopo aver creato il controllo, hai bisogno di un publisher. L'editore informa l'interfaccia utente
del sistema dell'esistenza del controllo. La classe ControlsProviderService
ha due metodi del publisher che devi sostituire nel codice dell'applicazione:
createPublisherForAllAvailable()
: crea unPublisher
per tutti i controlli disponibili nella tua app. UtilizzaControl.StatelessBuilder()
per creare oggettiControl
per questo publisher.createPublisherFor()
: crea unPublisher
per un elenco di controlli specifici, come identificati dai relativi identificatori di stringa. UtilizzaControl.StatefulBuilder
per creare questi oggettiControl
, poiché l'editore deve assegnare uno stato a ciascun controllo.
Crea il publisher
Quando la tua app pubblica per la prima volta controlli nell'UI di sistema, l'app non conosce
lo stato di ciascun controllo. Ottenere lo stato può essere un'operazione
dispendiosa in termini di tempo che prevede molti hop nella rete. Utilizza il metodo createPublisherForAllAvailable()
per pubblicizzare i controlli disponibili al sistema. Questo metodo utilizza la
classe del builder Control.StatelessBuilder
, poiché lo stato di ogni controllo è
sconosciuto.
Quando i controlli vengono visualizzati nell'interfaccia utente di Android , l'utente può selezionare i controlli preferiti.
Per utilizzare le coroutine Kotlin per creare un ControlsProviderService
, aggiungi una nuova
dipendenza a build.gradle
:
Alla moda
dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4" }
Kotlin
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4") }
Dopo aver sincronizzato i file Gradle, aggiungi il seguente snippet a Service
per
implementare 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); } }
Scorri verso il basso nel menu di sistema e individua il pulsante Controlli dispositivo, mostrato nella Figura 4:
![Un'immagine che mostra l'UI di sistema per i controlli dei dispositivi](https://developer.android.com/static/images/ui/device_controls_ui.png?authuser=19&hl=it)
Se tocchi Controlli dispositivi, accedi a una seconda schermata in cui puoi selezionare la tua app. Dopo aver selezionato l'app, vedrai in che modo lo snippet precedente crea un menu di sistema personalizzato con i nuovi controlli, come mostrato nella Figura 5:
![Un'immagine che mostra il menu di sistema contenente un controllo per la luce e il termostato](https://developer.android.com/static/images/ui/device_controls_example_1.png?authuser=19&hl=it)
Ora implementa il metodo createPublisherFor()
, aggiungendo quanto segue a 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(); }
In questo esempio, il metodo createPublisherFor()
contiene un'implementazione falsa di ciò che la tua app deve fare: comunicare con il tuo dispositivo per recuperarne lo stato e trasmetterlo al sistema.
Il metodo createPublisherFor()
utilizza le coroutine e i flussi Kotlin per soddisfare
l'API Reactive Streams richiesta:
- Crea un
Flow
. - Aspetta un secondo.
- Crea ed emette lo stato della luce smart.
- Aspetta un altro secondo.
- Crea ed emette lo stato del termostato.
Azioni dell'handle
Il metodo performControlAction()
segnala quando l'utente interagisce con un
controllo pubblicato. L'azione dipende dal tipo di invio di ControlAction
.
Esegui l'azione appropriata per il controllo in questione, quindi aggiorna lo stato del dispositivo nella UI Android.
Per completare l'esempio, aggiungi quanto segue a 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()); } }
Esegui l'app, accedi al menu Controlli dei dispositivi e visualizza i controlli della lampadina e del termostato.
![Un'immagine che mostra una luce e un controllo del termostato](https://developer.android.com/static/images/ui/device_controls_example_2.png?authuser=19&hl=it)