Externe Geräte steuern

Unter Android 11 und höher: Die Funktion „Schnellzugriff für Gerätesteuerung“ können Nutzer schnell externe Geräte wie Lampen, Lampen, Thermostate und Kameras aus dem Angebot eines Nutzers mit drei Interaktionen über eine Standard-Launcher. Der Geräte-OEM entscheidet, welchen Launcher er verwendet. Gerät können Dienstleister wie Google Home und Drittanbieter-Apps Geräte für die Anzeige in diesem Bereich bereitstellen. Auf dieser Seite erfahren Sie, wie Sie Gerätesteuerung in diesem Gruppenbereich und verknüpfen Sie sie mit Ihrer Kontroll-App.

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder"> <ph type="x-smartling-placeholder">
</ph> Abbildung 1: Gerätesteuerungsbereich in der Android-Benutzeroberfläche

Erstellen und deklarieren Sie eine ControlsProviderService, um diese Unterstützung hinzuzufügen. Erstellen Sie die auf Basis vordefinierter Steuerelementtypen unterstützt, und erstellen Sie dann Publisher für diese Steuerelemente.

Benutzeroberfläche

Geräte werden unter Gerätesteuerung als Widgets angezeigt. Fünf Gerätesteuerungs-Widgets sind verfügbar, wie in der folgenden Abbildung dargestellt:

<ph type="x-smartling-placeholder">
</ph> Widget ein-/ausblenden
<ph type="x-smartling-placeholder">
</ph> Wechseln
<ph type="x-smartling-placeholder">
</ph> Mit Schieberegler-Widget umschalten
<ph type="x-smartling-placeholder">
</ph> Mit Schieberegler umschalten
<ph type="x-smartling-placeholder">
</ph> Bereichs-Widget
<ph type="x-smartling-placeholder">
</ph> Bereich (kann nicht aktiviert oder deaktiviert werden)
<ph type="x-smartling-placeholder">
</ph> Zustandsloses Ein/Aus-Widget
<ph type="x-smartling-placeholder">
</ph> Statuslos ein-/ausschalten
<ph type="x-smartling-placeholder">
</ph> Widget für Temperaturbereich (geschlossen)
<ph type="x-smartling-placeholder">
</ph> Temperaturbereich (geschlossen)
<ph type="x-smartling-placeholder">
</ph> Abbildung 2: Sammlung von Widget-Vorlagen.

Berühren und Wenn du ein Widget hältst, gelangst du zur App, wo du noch mehr Kontrolle hast. Sie können das Symbol und die Farbe jedes Widgets anzupassen. Für eine optimale Nutzererfahrung Standardsymbol und -farbe verwenden, wenn das Standardsymbol und die Standardfarbe dem Gerät entsprechen.

<ph type="x-smartling-placeholder">
</ph> Ein Bild, auf dem das Temperatursteuerfeld-Widget zu sehen ist (geöffnet) <ph type="x-smartling-placeholder">
</ph> Abbildung 3: Widget „Temperatursteuerfeld öffnen“ geöffnet.

Dienst erstellen

In diesem Abschnitt wird beschrieben, wie Sie die ControlsProviderService Dieser Dienst teilt der Android-System-UI mit, dass deine App Gerätesteuerung enthält die im Bereich Gerätesteuerung der Android-Benutzeroberfläche angezeigt werden müssen.

Die ControlsProviderService API geht davon aus, dass Sie mit reaktiven Streams vertraut sind, da die im GitHub-Artikel zu Reactive Streams und im Java 9-Ablauf implementiert. Benutzeroberflächen. Die API basiert auf folgenden Konzepten:

  • Publisher:Ihre Anwendung ist der Publisher.
  • Abonnent:Die System-UI ist der Abonnent und kann eine Nummer anfordern. seitens des Publishers.
  • Abo:Der Zeitraum, in dem der Verlag oder Webpublisher Updates senden kann. mit der System-UI. Der Publisher oder der Abonnent kann dieses .

Dienst deklarieren

Für Ihre App muss ein Dienst wie MyCustomControlService in folgenden Ländern deklariert werden: in seinem App-Manifest.

Der Dienst muss einen Intent-Filter für ControlsProviderService enthalten. Dieses Filter ermöglicht Anwendungen, Steuerelemente zur System-UI hinzuzufügen.

Sie benötigen auch eine label, die in den Steuerelementen der System-UI angezeigt wird.

Das folgende Beispiel zeigt, wie ein Dienst deklariert wird:

<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>

Erstellen Sie als Nächstes eine neue Kotlin-Datei mit dem Namen MyCustomControlService.kt und erstellen Sie sie erweitern ControlsProviderService():

Kotlin

    class MyCustomControlService : ControlsProviderService() {
        ...
    }
    

Java

    public class MyCustomJavaControlService extends ControlsProviderService {
        ...
    }
    

Richtigen Einstellungstyp auswählen

Die API bietet Builder-Methoden zum Erstellen der Steuerelemente. Um den Bereich das Gerät festlegen, das Sie steuern möchten und wie die Nutzer interagieren. damit verbunden. Führe die folgenden Schritte aus:

  1. Wählen Sie den Gerätetyp aus, den das Steuerelement repräsentiert. Die Die Klasse DeviceTypes ist eine eine Aufzählung aller unterstützten Geräte. Mit dem Typ wird bestimmt, für das Gerät in der Benutzeroberfläche.
  2. Bestimmen Sie den für den Nutzer sichtbaren Namen und den Gerätestandort, z. B. Kitchen- und andere UI-Textelemente, die mit dem Steuerelement verknüpft sind.
  3. Wählen Sie die beste Vorlage für die Nutzerinteraktion aus. Steuerelemente werden einem ControlTemplate aus der Anwendung. Diese Vorlage zeigt den Steuerelementstatus direkt sowie die verfügbaren Eingabemethoden, d. h. die ControlAction In der folgenden Tabelle sind einige der verfügbaren Vorlagen und ihre Aktionen aufgeführt. die sie unterstützen:
Vorlage Aktion Beschreibung
ControlTemplate.getNoTemplateObject() None Die Anwendung kann damit Informationen zum Steuerelement aber die Nutzenden können nicht damit interagieren.
ToggleTemplate BooleanAction Stellt ein Steuerelement dar, das zwischen „Aktiviert“ und „Deaktiviert“ umgeschaltet werden kann Bundesländer. Das BooleanAction-Objekt enthält ein Feld, das , um den angeforderten neuen Status darzustellen, wenn der Nutzer auf das Steuerelement tippt.
RangeTemplate FloatAction Stellt ein Schieberegler-Widget mit angegebenen Mindest-, Höchst- und Schrittwerten dar. Wann? wenn der Nutzer mit dem Schieberegler interagiert, senden Sie eine neue FloatAction. mit dem aktualisierten Wert an die Anwendung zurück.
ToggleRangeTemplate BooleanAction, FloatAction Diese Vorlage ist eine Kombination aus ToggleTemplate und RangeTemplate. Touch-Ereignisse sowie Slider, z. B. um dimmbares Licht zu steuern.
TemperatureControlTemplate ModeAction, BooleanAction, FloatAction Diese Vorlage enthält nicht nur die vorherigen Aktionen, der Nutzer einen Modus festgelegt hat, z. B. „Heizen“, „Kühlen“, „Heizen/Kühlen“, „Eco“ oder „Aus“.
StatelessTemplate CommandAction Wird verwendet, um ein Steuerelement anzugeben, das Touchfunktionen bietet, aber dessen Status nicht ermittelt werden kann, z. B. bei einer IR-Fernsehfernbedienung. Sie können diese um eine Routine oder ein Makro zu definieren. und Statusänderungen.

Anhand dieser Informationen können Sie das Steuerelement erstellen:

Wenn Sie beispielsweise eine intelligente Glühbirne und einen Thermostat steuern möchten, fügen Sie Folgendes hinzu: Konstanten zu Ihrem 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;
 
    ...
    }
    

Publisher für die Steuerelemente erstellen

Nachdem Sie die Einstellung erstellt haben, benötigen Sie einen Publisher. Der Verlag oder Webpublisher informiert den System-UI der Existenz des Steuerelements angezeigt. Klasse ControlsProviderService zwei Publisher-Methoden, die Sie in Ihrem Anwendungscode überschreiben müssen:

  • createPublisherForAllAvailable(): Erstellt einen Publisher für alle in Ihrer App verfügbaren Steuerelemente. Control.StatelessBuilder() verwenden um Control-Objekte für diesen Publisher zu erstellen.
  • createPublisherFor(): Erstellt ein Publisher für eine Liste der angegebenen Steuerelemente, die anhand ihrer String-ID identifiziert werden. Control.StatefulBuilder für Folgendes verwenden: diese Control-Objekte erstellen, da der Publisher für jedes Steuerelement.

Publisher erstellen

Wenn Ihre App zum ersten Mal Steuerelemente an die System-UI veröffentlicht, weiß die App nicht, den Status der einzelnen Steuerelemente. Das Abrufen des Status kann ein zeitaufwendiger Vorgang sein mit vielen Hops im Netzwerk des Anbieters. Verwenden Sie die Methode createPublisherForAllAvailable() Methode zum Bewerben der verfügbaren Steuerelemente im System. Diese Methode verwendet die Methode Control.StatelessBuilder-Builder-Klasse, da der Status jedes Steuerelements ist unbekannt.

Sobald die Steuerelemente in der Android-Benutzeroberfläche angezeigt werden , kann der Nutzer Favoriten auswählen. Steuerelementen.

Wenn Sie Kotlin-Koroutinen zum Erstellen eines ControlsProviderService verwenden möchten, fügen Sie einen neuen Abhängigkeit von build.gradle:

Cool

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4"
}

Kotlin

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4")
}

Fügen Sie nach der Synchronisierung der Gradle-Dateien das folgende Snippet in die Datei Service ein: createPublisherForAllAvailable() implementieren:

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

Wischen Sie das Systemmenü nach unten und suchen Sie nach der Schaltfläche Gerätesteuerung, die in Abbildung 4:

<ph type="x-smartling-placeholder">
</ph> Ein Bild, auf dem die System-UI für die Gerätesteuerung zu sehen ist <ph type="x-smartling-placeholder">
</ph> Abbildung 4: Gerätesteuerung im Systemmenü.

Durch Tippen auf Gerätesteuerung wird ein zweiter Bildschirm geöffnet, auf dem Sie für Ihre App. Nachdem Sie Ihre App ausgewählt haben, sehen Sie, wie mit dem vorherigen Snippet ein benutzerdefiniertes Systemmenü mit Ihren neuen Steuerelementen, wie in Abbildung 5 dargestellt:

<ph type="x-smartling-placeholder">
</ph> Ein Bild, das das Systemmenü mit einer Lampe und einer Thermostatsteuerung zeigt <ph type="x-smartling-placeholder">
</ph> Abbildung 5: Licht- und Thermostateinstellungen, die hinzugefügt werden können.

Implementieren Sie nun die Methode createPublisherFor() und fügen Sie Folgendes in Ihr 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 diesem Beispiel enthält die Methode createPublisherFor() eine fiktive die Anforderungen deiner App zu implementieren: Kommunizieren Sie mit Ihrem Gerät, abrufen und an das System senden.

Die Methode createPublisherFor() verwendet Kotlin-Koroutinen und -Datenflüsse, um die erforderliche Reactive Streams API. Gehen Sie dazu so vor:

  1. Erstellt ein Flow.
  2. Wartet eine Sekunde.
  3. Erstellt und sendet den Status der intelligenten Lampe.
  4. Wartet noch eine Sekunde.
  5. Erstellt und sendet den Status des Thermostats.

Aktionen verarbeiten

Mit der Methode performControlAction() wird signalisiert, wann der Nutzer mit einem veröffentlichtes Steuerelement. Der Typ der gesendeten ControlAction bestimmt die Aktion. Die entsprechende Aktion für das angegebene Steuerelement ausführen und dann den Status aktualisieren des Geräts in der Android-Benutzeroberfläche.

Fügen Sie Ihrem Service Folgendes hinzu, um das Beispiel zu vervollständigen:

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());
        }
    }
    

Starten Sie die App, greifen Sie auf das Menü Gerätesteuerung zu und sehen Sie sich die Licht- und für die Thermostatsteuerung.

<ph type="x-smartling-placeholder">
</ph> Ein Bild, das eine Lampe und eine Thermostatsteuerung zeigt <ph type="x-smartling-placeholder">
</ph> Abbildung 6: Licht- und Thermostatsteuerung.