Controllare i dispositivi esterni

In Android 11 e versioni successive, la funzionalità Controlli dei dispositivi ad accesso rapido consente all'utente di visualizzare e controllare rapidamente i dispositivi esterni, come luci, termostati e videocamere, da un'interfaccia utente in tre interazioni da un launcher predefinito. L'OEM del dispositivo sceglie il launcher da 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.

Figura 1. Spazio di controllo dei dispositivi nell'UI di Android.

Per aggiungere questo supporto, crea e dichiara un ControlsProviderService. Crea i controlli supportati dalla tua app in base ai tipi di controllo predefiniti, quindi crea i publisher per questi controlli.

Interfaccia utente

I dispositivi vengono visualizzati in Controlli dei dispositivi come widget basati su modelli. Sono disponibili cinque widget di controllo dei dispositivi, come mostrato nella figura seguente:

Attiva/disattiva widget
Attiva/disattiva
Attivazione/disattivazione con widget cursore
Attiva/disattiva con dispositivo di scorrimento
Widget intervallo
Intervallo (non può essere attivato o disattivato)
Widget di attivazione/disattivazione senza stato
Attiva/disattiva stateless
Widget del riquadro della temperatura (chiuso)
Riquadro della temperatura (chiuso)
Figura 2. Raccolta di widget basati su modelli.

Se tocchi e tieni premuto un widget, viene visualizzata 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.

Un'immagine che mostra il widget del pannello della temperatura (aperto)
Figura 3. Widget del riquadro della temperatura aperto.

Crea il servizio

Questa sezione mostra come creare il ControlsProviderService. Questo servizio indica all'UI di sistema di Android che la tua app contiene controlli dei dispositivi che devono essere visualizzati nell'area Controlli dei dispositivi dell'UI di Android.

L'ControlsProviderService API presuppone la conoscenza degli stream reattivi, come definiti nel progetto GitHub Reactive Streams e implementati nelle interfacce Java 9 Flow. L'API si basa sui seguenti concetti:

  • Publisher: la tua applicazione è il publisher.
  • Subscriber: l'UI di sistema è il subscriber e può richiedere un numero di controlli al publisher.
  • Subscription: il periodo di tempo durante il quale il publisher può inviare aggiornamenti all'UI di sistema. Il publisher o il subscriber possono chiudere questa finestra.

Dichiara il servizio

La tua app deve dichiarare un servizio, ad esempio MyCustomControlService, nel relativo manifest dell'app.

Il servizio deve includere un filtro per intent per ControlsProviderService. Questo filtro consente alle applicazioni di contribuire con i controlli all'UI di sistema.

Ti serve anche un label visualizzato nei controlli nell'UI 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 fallo estendere ControlsProviderService():

Kotlin

    class MyCustomControlService : ControlsProviderService() {
        ...
    }
    

Java

    public class MyCustomJavaControlService extends ControlsProviderService {
        ...
    }
    

Seleziona il tipo di controllo corretto

L'API fornisce metodi di creazione per creare i controlli. Per popolare il builder, determina il dispositivo che vuoi controllare e il modo in cui l'utente interagisce con esso. Esegui i seguenti passaggi:

  1. Scegli il tipo di dispositivo rappresentato dal controllo. La DeviceTypes classe è un' enumerazione di tutti i dispositivi supportati. Il tipo viene utilizzato per determinare le icone e i colori del dispositivo nell'UI.
  2. Determina il nome visibile all'utente, la posizione del dispositivo, ad esempio la cucina, e altri elementi di testo dell'UI associati al controllo.
  3. Scegli 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 e i metodi di input disponibili, ovvero the ControlAction. La tabella seguente descrive alcuni dei modelli disponibili e le azioni che supportano:
Modello Azione Descrizione
ControlTemplate.getNoTemplateObject() None L'applicazione potrebbe utilizzare questo modello per trasmettere informazioni sul controllo, ma l'utente non può interagire con esso.
ToggleTemplate BooleanAction Rappresenta un controllo che può essere attivato o disattivato stati. 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 con dispositivo di scorrimento con valori min, max e step specificati. Quando l'utente interagisce con il dispositivo di scorrimento, invia un nuovo FloatAction oggetto all'applicazione con il valore aggiornato.
ToggleRangeTemplate BooleanAction, FloatAction Questo modello è una combinazione di ToggleTemplate e RangeTemplate. Supporta eventi touch e un dispositivo di scorrimento, ad esempio per controllare le luci dimmerabili.
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 fornisce funzionalità touch ma il cui stato non può essere determinato, ad esempio un telecomando a infrarossi per la TV. Puoi utilizzare questo modello per definire una routine o una macro, ovvero un'aggregazione di modifiche di controllo e stato.

Con queste informazioni, puoi creare il controllo:

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;
 
    ...
    }
    

Crea i publisher per i controlli

Dopo aver creato il controllo, è necessario un publisher. Il publisher informa l'UI di sistema dell'esistenza del controllo. La classe ControlsProviderService ha due metodi publisher che devi sostituire nel codice dell'applicazione:

  • createPublisherForAllAvailable(): crea un Publisher per tutti i controlli disponibili nella tua app. Utilizza Control.StatelessBuilder() per creare oggetti Control per questo publisher.
  • createPublisherFor(): crea un Publisher per un elenco di controlli specificati, identificati dai relativi identificatori stringa. Utilizza Control.StatefulBuilder per creare questi oggetti Control, poiché il publisher deve assegnare uno stato a ogni controllo.

Crea il publisher

Quando la tua app pubblica per la prima volta i controlli nell'UI di sistema, non conosce lo stato di ogni controllo. L'ottenimento dello stato può essere un'operazione che richiede tempo e che comporta molti hop nella rete del fornitore del dispositivo. Utilizza il createPublisherForAllAvailable() metodo per pubblicizzare i controlli disponibili al sistema. Questo metodo utilizza la classe di creazione Control.StatelessBuilder, poiché lo stato di ogni controllo è sconosciuto.

Una volta visualizzati i controlli nell'UI 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<String, ReplayProcessor> 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 il menu di sistema e individua il pulsante Controlli dei dispositivi, mostrato nella figura 4:

Un&#39;immagine che mostra l&#39;interfaccia utente di sistema per i controlli del dispositivo
Figura 4. Controlli dei dispositivi nel menu di sistema.

Se tocchi Controlli dei dispositivi , viene visualizzata 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:

Un&#39;immagine che mostra il menu di sistema contenente un controllo della luce e del termostato
Figura 5. Controlli di luci e termostati da aggiungere.

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<String, MutableSharedFlow>()
 
    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.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);
    }
 
    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 fittizia di ciò che deve fare la tua app: comunicare con il dispositivo per recuperarne lo stato ed emetterlo al sistema.

Il metodo createPublisherFor() utilizza le coroutine e i flussi Kotlin per soddisfare l'API Reactive Streams richiesta eseguendo le seguenti operazioni:

  1. Crea un Flow.
  2. Attende un secondo.
  3. Crea ed emette lo stato della luce smart.
  4. Attende un altro secondo.
  5. Crea ed emette lo stato del termostato.

Gestisci 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'UI di 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 Consumer consumer) {
        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 di luci e termostati.

Un&#39;immagine che mostra un controllo della luce e del termostato
Figura 6. Controlli di luci e termostati.