In Android 11 e versioni successive, la funzionalità Controlli dei dispositivi ad accesso rapido consente agli utenti di visualizzare e controllare rapidamente dispositivi esterni come luci, termostati e videocamere da un'interfaccia utente con tre interazioni da un launcher predefinito. Il produttore OEM del dispositivo sceglie quale launcher 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 a tipi di controllo predefiniti, poi crea
i publisher per questi controlli.
Interfaccia utente
I dispositivi vengono visualizzati nella sezione Controlli dei dispositivi come widget basati su modelli. Sono disponibili cinque widget di controllo dei dispositivi, come mostrato nella figura seguente:
|
|
|
|
|
Se tocchi e tieni premuto un widget, si apre l'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 il set predefinito corrisponde al dispositivo.
Crea il servizio
Questa sezione mostra come creare
ControlsProviderService. Questo servizio comunica
all'interfaccia utente di sistema Android che la tua app contiene controlli del dispositivo che devono essere visualizzati
nell'area Controlli del dispositivo dell'interfaccia utente Android.
L'API ControlsProviderService presuppone la familiarità con gli stream 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.
- Abbonato: l'interfaccia utente del sistema è l'abbonato e può richiedere un numero di controlli all'editore.
- Abbonamento:il periodo di tempo durante il quale l'editore può inviare aggiornamenti all'UI di sistema. Il publisher o l'abbonato possono chiudere questa finestra.
Dichiarare il servizio
L'app deve dichiarare un servizio, ad esempio MyCustomControlService, nel
manifest dell'app.
Il servizio deve includere un filtro per intent per ControlsProviderService. Questo
filtro consente alle applicazioni di contribuire con i controlli all'interfaccia utente di sistema.
Devi anche avere un label visualizzato nei controlli nell'interfaccia utente del 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>
Successivamente, crea un nuovo file Kotlin denominato MyCustomControlService.kt e fallo
estendere ControlsProviderService:
class MyCustomControlService : ControlsProviderService() { // ... }
Seleziona il tipo di controllo corretto
L'API fornisce metodi di creazione per creare i controlli. Per compilare il generatore, determina il dispositivo che vuoi controllare e il modo in cui l'utente interagisce con esso. Segui questi passaggi:
- Scegli il tipo di dispositivo rappresentato dal controllo. La classe
DeviceTypesè un'enumerazione di tutti i dispositivi supportati. Il tipo viene utilizzato per determinare le icone e i colori del dispositivo nell'interfaccia utente. - Determina il nome visibile all'utente, la posizione del dispositivo, ad esempio cucina, e altri elementi di testo dell'interfaccia utente associati al controllo.
- Scegli il modello migliore per supportare l'interazione degli utenti. Ai controlli viene assegnato un
ControlTemplatedall'applicazione. Questo modello mostra direttamente all'utente lo stato del controllo, nonché i metodi di input disponibili, ovvero ilControlAction. La tabella seguente elenca alcuni dei modelli disponibili e le azioni che supportano:
| Modello | Azione | Descrizione |
ControlTemplate.getNoTemplateObject()
|
None
|
L'applicazione potrebbe utilizzare questo valore per trasmettere informazioni sul controllo, ma l'utente non può interagire con esso. |
ToggleTemplate
|
BooleanAction
|
Rappresenta un controllo che può essere 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 cursore con valori min, max e step specificati. Quando
l'utente interagisce con il cursore, invia un nuovo oggetto FloatAction
all'applicazione con il valore aggiornato.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Questo modello è una combinazione di ToggleTemplate e
RangeTemplate. Supporta gli eventi touch e un cursore,
ad esempio per controllare le luci dimmerabili.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Oltre a riepilogare 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 fornisce funzionalità touch ma il cui stato non può essere determinato, ad esempio un telecomando TV a infrarossi. Puoi utilizzare questo modello per definire una routine o una macro, ovvero un'aggregazione di modifiche di controllo e di stato. |
Con queste informazioni, puoi creare il controllo:
- Utilizza la classe di builder
Control.StatelessBuilderquando lo stato del controllo è sconosciuto. - Utilizza la classe di creazione
Control.StatefulBuilderquando lo stato del controllo è noto.
Ad esempio, per controllare una lampadina smart e un termostato, aggiungi le seguenti
costanti al tuo MyCustomControlService:
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() { // ... }
Creare publisher per i controlli
Dopo aver creato il controllo, è necessario un publisher. L'editore informa l'UI di sistema dell'esistenza del controllo. La classe ControlsProviderService
ha due metodi per i publisher che devi sostituire nel codice dell'applicazione:
createPublisherForAllAvailable: crea unPublisherper tutti i controlli disponibili nella tua app. UtilizzaControl.StatelessBuilderper creare oggettiControlper questo editore.createPublisherFor: crea unPublisherper un elenco di controlli specificati, identificati dai relativi identificatori stringa. UtilizzaControl.StatefulBuilderper creare questi oggettiControl, perché l'editore deve assegnare uno stato a ogni controllo.
Crea il publisher
Quando la tua app pubblica per la prima volta i controlli nell'interfaccia utente di sistema, non conosce
lo stato di ogni controllo. L'ottenimento dello stato può essere un'operazione che richiede molto tempo
e molti passaggi nella rete del fornitore del dispositivo. Utilizza il metodo
createPublisherForAllAvailable per
pubblicizzare i controlli disponibili al sistema. Questo metodo utilizza la classe di builder
Control.StatelessBuilder, perché lo stato di ogni controllo è
sconosciuto.
Una volta visualizzati i controlli nell'interfaccia utente di Android, gli utenti possono selezionare i controlli preferiti.
Per utilizzare le coroutine Kotlin per creare un ControlsProviderService, aggiungi una nuova
dipendenza al tuo 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:
class MyCustomControlService : ControlsProviderService() { override fun createPublisherForAllAvailable(): Flow.Publisher<Control> = 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<String>): Flow.Publisher<Control> { TODO() } override fun performControlAction( controlId: String, action: ControlAction, consumer: Consumer<Int>, ) { TODO() } }
Scorri verso il basso il menu di sistema e individua il pulsante Controlli del dispositivo, mostrato nella figura 4:
Se tocchi Controlli del dispositivo, si apre una seconda schermata in cui puoi selezionare la tua app. Una volta selezionata l'app, vedrai come lo snippet precedente crea un menu di sistema personalizzato che mostra i nuovi controlli, come mostrato nella figura 5:
Ora implementa il metodo createPublisherFor aggiungendo quanto segue a Service:
private val job = SupervisorJob() private val scope = CoroutineScope(Dispatchers.IO + job) private val controlFlows = mutableMapOf<String, MutableSharedFlow<Control>>() private var toggleState = false private var rangeState = 18f override fun createPublisherFor(controlIds: List<String>): Flow.Publisher<Control> { val flow = MutableSharedFlow<Control>(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 <T> 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() }
In questo esempio, il metodo createPublisherFor contiene un'implementazione
fittizia di ciò che deve fare la tua app: comunicare con il tuo dispositivo per
recuperarne lo stato ed emetterlo nel sistema.
Il metodo createPublisherFor utilizza coroutine e flussi Kotlin per soddisfare
l'API Reactive Streams richiesta nel seguente modo:
- Crea un
Flow. - Attende un secondo.
- Crea ed emette lo stato della smart light.
- Attende un altro secondo.
- Crea ed emette lo stato del termostato.
Gestire le azioni
Il metodo performControlAction segnala quando l'utente interagisce con un
controllo pubblicato. Il tipo di ControlAction inviato determina l'azione.
Esegui l'azione appropriata per il controllo specificato, quindi aggiorna lo stato
del dispositivo nell'interfaccia utente di Android.
Per completare l'esempio, aggiungi quanto segue a Service:
override fun performControlAction( controlId: String, action: ControlAction, consumer: Consumer<Int>, ) { 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) }
Esegui l'app, accedi al menu Controlli dispositivo e visualizza i controlli della luce e del termostato.