Unter Android 11 und höher können Nutzer mit der Schnellzugriffssteuerung für Geräte schnell externe Geräte wie Lampen, Thermostate und Kameras sehen und steuern. Das ist in drei Interaktionen über einen Standard-Launcher möglich. Der Geräte-OEM entscheidet, welchen Launcher er verwendet. Geräte-Aggregatoren wie Google Home und Apps von Drittanbietern können Geräte für die Anzeige in diesem Bereich bereitstellen. Auf dieser Seite erfährst du, wie du die Gerätesteuerung in diesem Bereich einblenden und mit deiner Steuerungs-App verknüpfen kannst.
Erstellen und deklarieren Sie eine ControlsProviderService
, um diese Unterstützung hinzuzufügen. Erstellen Sie die Steuerelemente, die Ihre App basierend auf vordefinierten Steuerelementtypen unterstützt, und erstellen Sie dann Publisher für diese Steuerelemente.
Benutzeroberfläche
Geräte werden unter Gerätesteuerung als Widgets angezeigt. Es sind fünf Widgets zur Gerätesteuerung verfügbar, wie in der folgenden Abbildung dargestellt:
![]() |
![]() |
![]() |
![]() |
![]() |
Durch Gedrückthalten eines Widgets gelangst du zur App, wo du noch mehr Kontrolle über sie hast. Du kannst das Symbol und die Farbe jedes Widgets anpassen. Für eine optimale Nutzererfahrung solltest du jedoch das Standardsymbol und die Standardfarbe verwenden, wenn sie zu dem Gerät passen.
![Ein Bild, auf dem das Temperatursteuerfeld-Widget zu sehen ist (geöffnet)](https://developer.android.com/static/images/device-control/device-control-panel2.png?authuser=6&hl=de)
Dienst erstellen
In diesem Abschnitt wird beschrieben, wie Sie die ControlsProviderService
erstellen.
Dieser Dienst teilt der Android-System-UI mit, dass Ihre App Gerätesteuerelemente enthält, die im Bereich Gerätesteuerung der Android-UI angezeigt werden müssen.
Die ControlsProviderService
API setzt voraus, dass Sie mit reaktiven Streams vertraut sind, wie im GitHub-Projekt zu Reactive Streams definiert und in den Java 9 Flow-Schnittstellen implementiert.
Die API basiert auf folgenden Konzepten:
- Publisher:Ihre Anwendung ist der Publisher.
- Abonnent:Die System-UI ist der Abonnent und kann eine Reihe von Steuerelementen vom Publisher anfordern.
- Abo:Der Zeitraum, in dem der Verlag oder Webpublisher Updates an die System-UI senden kann. Der Verlag oder Webpublisher oder der Abonnent kann dieses Fenster schließen.
Dienst deklarieren
Deine App muss in ihrem App-Manifest einen Dienst wie MyCustomControlService
deklarieren.
Der Dienst muss einen Intent-Filter für ControlsProviderService
enthalten. Mit diesem Filter können Anwendungen Steuerelemente zur System-UI hinzufü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 erweitern Sie sie 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 Builder zu befüllen, müssen Sie das Gerät festlegen, das Sie steuern möchten und wie der Nutzer damit interagiert. Führe die folgenden Schritte aus:
- Wählen Sie den Gerätetyp aus, den das Steuerelement repräsentiert. Die Klasse
DeviceTypes
ist eine Aufzählung aller unterstützten Geräte. Der Typ wird verwendet, um die Symbole und Farben für das Gerät in der UI festzulegen. - Bestimmen Sie den für den Nutzer sichtbaren Namen, den Gerätestandort (z. B. die Küche) und andere UI-Textelemente, die mit dem Steuerelement verknüpft sind.
- Wählen Sie die beste Vorlage für die Nutzerinteraktion aus. Steuerelementen wird ein
ControlTemplate
aus der Anwendung zugewiesen. Diese Vorlage zeigt dem Nutzer direkt den Steuerstatus sowie die verfügbaren Eingabemethoden, also denControlAction
. In der folgenden Tabelle sind einige der verfügbaren Vorlagen und die unterstützten Aktionen aufgeführt:
Vorlage | Aktion | Beschreibung |
ControlTemplate.getNoTemplateObject()
|
None
|
Die Anwendung kann dies verwenden, um Informationen zum Steuerelement zu übermitteln, aber der Nutzer kann nicht damit interagieren. |
ToggleTemplate
|
BooleanAction
|
Stellt ein Steuerelement dar, das zwischen dem Status „Aktiviert“ und „Deaktiviert“ gewechselt werden kann. Das BooleanAction -Objekt enthält ein Feld, das sich entsprechend dem angeforderten neuen Status ändert, wenn der Nutzer auf das Steuerelement tippt.
|
RangeTemplate
|
FloatAction
|
Stellt ein Schieberegler-Widget mit angegebenen Mindest-, Höchst- und Schrittwerten dar. Wenn der Nutzer mit dem Schieberegler interagiert, wird ein neues FloatAction -Objekt mit dem aktualisierten Wert an die Anwendung zurückgesendet.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Diese Vorlage ist eine Kombination aus ToggleTemplate und RangeTemplate . Sie unterstützt Touch-Ereignisse und einen Schieberegler, z. B. zum Steuern dimmbarer Lampen.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Mit dieser Vorlage können Nutzer nicht nur die vorherigen Aktionen kapseln, sondern auch einen Modus wie „Heizen“, „Kühlen“, „Heizen/Kühlen“, „Eco“ oder „Aus“ festlegen. |
StatelessTemplate
|
CommandAction
|
Wird verwendet, um ein Steuerelement anzugeben, das Touchfunktionen bietet, aber dessen Status nicht ermittelt werden kann, z. B. eine IR-Fernsehfernbedienung. Sie können diese Vorlage verwenden, um eine Routine oder ein Makro zu definieren, also eine Zusammenfassung von Kontroll- und Statusänderungen. |
Anhand dieser Informationen können Sie das Steuerelement erstellen:
- Verwenden Sie die Builder-Klasse
Control.StatelessBuilder
, wenn der Status des Steuerelements unbekannt ist. - Verwenden Sie die Builder-Klasse
Control.StatefulBuilder
, wenn der Status des Steuerelements bekannt ist.
Wenn Sie beispielsweise eine intelligente Glühbirne und einen Thermostat steuern möchten, fügen Sie Ihrem MyCustomControlService
die folgenden Konstanten hinzu:
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 die System-UI über das Vorhandensein des Steuerelements. Die Klasse ControlsProviderService
hat zwei Publisher-Methoden, die Sie in Ihrem Anwendungscode überschreiben müssen:
createPublisherForAllAvailable()
: Erstellt einPublisher
für alle in deiner App verfügbaren Steuerelemente. VerwendeControl.StatelessBuilder()
, umControl
-Objekte für diesen Publisher zu erstellen.createPublisherFor()
: Erstellt einPublisher
für eine Liste von Steuerelementen, die anhand ihrer String-IDs identifiziert werden. Verwenden SieControl.StatefulBuilder
, um dieseControl
-Objekte zu erstellen, da der Verlag oder Webpublisher jeder Steuerung einen Status zuweisen muss.
Publisher erstellen
Wenn Ihre App zum ersten Mal Steuerelemente an die System-UI veröffentlicht, kennt sie den Status der einzelnen Steuerelemente nicht. Das Abrufen des Status kann ein zeitaufwendiger Vorgang sein, der viele Hops im Netzwerk des Geräteherstellers erfordert. Verwenden Sie die Methode createPublisherForAllAvailable()
, um dem System die verfügbaren Steuerelemente anzubieten. Diese Methode verwendet die Builder-Klasse Control.StatelessBuilder
, da der Status der einzelnen Steuerelemente unbekannt ist.
Sobald die Steuerelemente in der Android-UI angezeigt werden , können Nutzer bevorzugte Steuerelemente auswählen.
Wenn Sie Kotlin-Koroutinen zum Erstellen eines ControlsProviderService
verwenden möchten, fügen Sie Ihrer build.gradle
eine neue Abhängigkeit hinzu:
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 Ihre Service
ein, um createPublisherForAllAvailable()
zu 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> 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 die Schaltfläche Gerätesteuerung (siehe Abbildung 4):
![Ein Bild, auf dem die System-UI für die Gerätesteuerung zu sehen ist](https://developer.android.com/static/images/ui/device_controls_ui.png?authuser=6&hl=de)
Wenn Sie auf Gerätesteuerung tippen, wird ein zweiter Bildschirm geöffnet, auf dem Sie Ihre App auswählen können. Nachdem Sie Ihre App ausgewählt haben, sehen Sie, wie mit dem vorherigen Snippet ein benutzerdefiniertes Systemmenü mit Ihren neuen Steuerelementen erstellt wird (siehe Abbildung 5):
![Ein Bild, das das Systemmenü mit einer Lampe und einer Thermostatsteuerung zeigt](https://developer.android.com/static/images/ui/device_controls_example_1.png?authuser=6&hl=de)
Implementieren Sie nun die Methode createPublisherFor()
und fügen Sie Folgendes in die Datei Service
ein:
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 diesem Beispiel enthält die Methode createPublisherFor()
eine fiktive Implementierung dessen, was Ihre App tun muss: Sie kommunizieren mit Ihrem Gerät, um seinen Status abzurufen, und senden diesen Status an das System.
Die createPublisherFor()
-Methode verwendet Kotlin-Koroutinen und -Abläufe, um die erforderliche Reactive Streams API zu erfüllen. Dazu führt sie Folgendes aus:
- Erstellt ein
Flow
. - Wartet eine Sekunde.
- Erstellt und sendet den Status der intelligenten Lampe.
- Wartet noch eine Sekunde.
- Erstellt und sendet den Status des Thermostats.
Aktionen verarbeiten
Die Methode performControlAction()
signalisiert, wenn der Nutzer mit einem veröffentlichten Steuerelement interagiert. Der Typ der gesendeten ControlAction
bestimmt die Aktion.
Führe die entsprechende Aktion für das angegebene Steuerelement aus und aktualisiere dann den Status des Geräts in der Android-UI.
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 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()); } }
Führen Sie die App aus, greifen Sie auf das Menü Gerätesteuerung zu und sehen Sie sich die Licht- und Thermostatsteuerung an.
![Ein Bild, das eine Lampe und eine Thermostatsteuerung zeigt](https://developer.android.com/static/images/ui/device_controls_example_2.png?authuser=6&hl=de)