Controllare i dispositivi esterni

In Android 11 e versioni successive, la funzionalità Controllo rapido dei dispositivi consente all'utente di visualizzare e controllare rapidamente 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. Dispositivo gli aggregatori, ad esempio Google Home, e le app di fornitori di terze parti possono forniscono dispositivi per la visualizzazione in questo spazio. Questa pagina mostra come mostrare controlli dei dispositivi in questo spazio e collegali alla tua app di controllo.

Figura 1. Spazio di controllo del dispositivo nell'UI di Android.

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

Interfaccia utente

I dispositivi vengono visualizzati in Controllo dispositivi come widget basati su modelli. Cinque sono disponibili widget di controllo del dispositivo, come mostrato nella figura seguente:

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

Toccante e tieni premuto un widget per accedere 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 l'insieme predefinito corrisponde al dispositivo.

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

Crea il servizio

Questa sezione illustra come creare ControlsProviderService Questo servizio comunica all'interfaccia utente del sistema Android che la tua app contiene controlli dei dispositivi. che devono essere mostrati nell'area Controlli dei dispositivi della UI Android.

L'API ControlsProviderService presuppone la familiarità con gli stream reattivi, poiché definita nel documento Reactive Streams GitHub progetto e implementato nel Java 9 Flow di programmazione di applicazioni. L'API si basa sui seguenti concetti:

  • Publisher:la tua applicazione è il publisher.
  • Abbonato: l'interfaccia utente di sistema corrisponde all'abbonato e può richiedere un numero di controlli da parte del publisher.
  • Abbonamento: il periodo di tempo durante il quale l'editore può inviare aggiornamenti. all'interfaccia utente di sistema. Sia il publisher che l'abbonato possono chiudere finestra.

Dichiara il servizio

La tua app deve dichiarare un servizio, ad esempio MyCustomControlService, in del file manifest dell'app.

Il servizio deve includere un filtro per intent per ControlsProviderService. Questo 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>

Ora crea un nuovo file Kotlin denominato MyCustomControlService.kt e crealo estendere 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, determinano il dispositivo che vuoi controllare e il modo in cui l'utente interagisce con essa. Procedi nel seguente modo:

  1. Scegli il tipo di dispositivo rappresentato dal controllo. La Il corso DeviceTypes è un l'enumerazione di tutti i dispositivi supportati. Il tipo viene utilizzato per determinare icone e colori del dispositivo nell'interfaccia utente.
  2. Determina il nome rivolto agli utenti, la posizione del dispositivo, ad esempio cucina e altri elementi testuali dell'interfaccia utente associati al controllo.
  3. Scegliere il modello migliore per supportare l'interazione dell'utente. Ai controlli viene assegnato ControlTemplate dall'applicazione. Questo modello mostra direttamente lo stato del controllo dell'utente e ai metodi di immissione disponibili, ovvero ControlAction La tabella seguente illustra alcuni dei modelli disponibili e le relative azioni che supportano:
Modello Azione Descrizione
ControlTemplate.getNoTemplateObject() None L'applicazione potrebbe usarlo per trasmettere informazioni sul controllo, ma l'utente non può interagire.
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 di scorrimento con valori min, max e passi specificati. Quando l'utente interagisce con il cursore, invia un nuovo FloatAction all'applicazione con il valore aggiornato.
ToggleRangeTemplate BooleanAction, FloatAction Questo modello è una combinazione di ToggleTemplate e RangeTemplate. Supporta eventi touch, nonché un cursore, ad esempio per controllare le luci regolabili.
TemperatureControlTemplate ModeAction, BooleanAction, FloatAction Oltre a incorporare le azioni precedenti, questo modello ti consente l'utente ha impostato una modalità, come 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, come nel caso di un telecomando IR. Puoi utilizzare questo modello per definire una routine o una macro, che è un'aggregazione di regole e modifiche di stato.

Con queste informazioni, puoi creare il controllo:

Ad esempio, per controllare una lampadina smart e un termostato, aggiungi quanto segue costanti su 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. Il publisher informa di sistema dell'esistenza del controllo. Il corso ControlsProviderService prevede due metodi per il 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 Control oggetti per questo publisher.
  • createPublisherFor(): crea un Publisher per un elenco di controlli specificati, così come sono identificati dai rispettivi identificatori di stringa. Usa Control.StatefulBuilder per crea questi oggetti Control, poiché il publisher deve assegnare uno stato per ciascun controllo.

Crea il publisher

Quando la tua app pubblica per la prima volta i controlli nell'UI di sistema, l'app non sa lo stato di ogni controllo. Ottenere lo stato può essere un'operazione dispendiosa in termini di tempo che implica molti hop nella rete del fornitore del dispositivo. Utilizza la createPublisherForAllAvailable() per pubblicizzare i controlli disponibili al sistema. Questo metodo utilizza Classe 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 preferiti i controlli di sicurezza.

Per utilizzare le coroutine Kotlin per creare un ControlsProviderService, aggiungi una nuova della dipendenza da 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 implementa 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 nel menu di sistema e individua il pulsante Controllo dispositivo, mostrato in figura 4:

Un&#39;immagine che mostra l&#39;UI di sistema per i controlli dei dispositivi
. Figura 4. Controllo dei dispositivi nel menu di sistema.

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

Un&#39;immagine che mostra il menu di sistema contenente un controllo per la luce e il termostato
. Figura 5. Controlli di illuminazione e termostato da aggiungere.

Ora implementa il metodo createPublisherFor(), aggiungendo quanto segue alla sezione 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 falso implementazione 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 coroutine e flussi di Kotlin per soddisfare l'API Reactive Streams richiesta nel seguente modo:

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

Azioni dell'handle

Il metodo performControlAction() segnala quando l'utente interagisce con un e il controllo pubblicato. L'azione dipende dal tipo di invio di ControlAction. Esegui l'azione appropriata per il controllo specificato e poi aggiorna lo stato del dispositivo nell'interfaccia utente 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 Controllo dispositivi e controlla la lampadina e controlli del termostato.

Un&#39;immagine che mostra una luce e un controllo del termostato
. Figura 6. Controlli di illuminazione e termostato.