In Android 11 und höher können Nutzer über die Funktion „Schnellzugriff auf Gerätesteuerung“ externe Geräte wie Lampen, Thermostate und Kameras innerhalb von drei Interaktionen über einen Standard-Launcher schnell aufrufen und steuern. Der OEM des Geräts wählt den Launcher aus. Geräteaggregatoren wie Google Home und Apps von Drittanbietern können Geräte für die Anzeige in diesem Bereich bereitstellen. Auf dieser Seite erfahren Sie, wie Sie Gerätesteuerungen in diesem Bereich anzeigen und mit Ihrer Steuer-App verknüpfen.
Erstellen und deklarieren Sie eine ControlsProviderService
, um diese Unterstützung hinzuzufügen. Erstellen Sie die Steuerelemente, die von Ihrer App unterstützt werden, anhand vordefinierter Steuerelementtypen und erstellen Sie dann Publisher für diese Steuerelemente.
Benutzeroberfläche
Geräte werden unter Gerätesteuerung als Vorlagen-Widgets angezeigt. Fünf Gerätesteuerungs-Widgets sind verfügbar, wie in der folgenden Abbildung dargestellt:
|
|
|
|
|
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 für jedes Widget anpassen. Für eine optimale Nutzerfreundlichkeit sollten Sie jedoch das Standardsymbol und die Standardfarbe verwenden, wenn die Standardeinstellungen zum Gerät passen.
Dienst erstellen
In diesem Abschnitt wird gezeigt, wie Sie die ControlsProviderService
erstellen.
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. Dieses Fenster kann entweder vom Publisher oder vom Abonnenten geschlossen werden.
Dienst deklarieren
Ihre App muss in ihrem App-Manifest einen Dienst wie MyCustomControlService
deklarieren.
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 erweitern Sie sie um ControlsProviderService()
:
Kotlin
class MyCustomControlService : ControlsProviderService() { ... }
Java
public class MyCustomJavaControlService extends ControlsProviderService { ... }
Den richtigen Steuerelementtyp 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ühren Sie die folgenden Schritte aus:
- 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. - Legen Sie den für Nutzer sichtbaren Namen, den Gerätestandort (z. B. Küche) und andere Textelemente der Benutzeroberfläche fest, die mit dem Steuerelement verknüpft sind.
- Wählen Sie die beste Vorlage für die Nutzerinteraktion aus. Steuerelementen wird von der Anwendung eine
ControlTemplate
zugewiesen. Diese Vorlage zeigt den Steuerelementstatus direkt sowie die verfügbaren Eingabemethoden, d. h. dieControlAction
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 . Es unterstützt Touch-Ereignisse sowie einen Schieberegler, z. B. zum Dimmen von Lampen.
|
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. Mit dieser Vorlage können Sie einen Ablauf oder ein Makro definieren, also eine Aggregation von Steuer- und Statusänderungen. |
Anhand dieser Informationen können Sie das Steuerelement erstellen:
- Verwenden Sie die Methode
Control.StatelessBuilder
Builder-Klasse, 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 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 das Steuerelement erstellt haben, ist ein Publisher erforderlich. 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 einenPublisher
für alle in Ihrer App verfügbaren Steuerelemente.Control.StatelessBuilder()
verwenden umControl
-Objekte für diesen Publisher zu erstellen.createPublisherFor()
: Erstellt einePublisher
für eine Liste von Steuerelementen, die anhand ihrer String-IDs identifiziert werden. VerwendeControl.StatefulBuilder
, um dieseControl
-Objekte zu erstellen, da der Publisher jedem Steuerelement einen Status zuweisen muss.
Publisher erstellen
Wenn Ihre App zum ersten Mal Steuerelemente in der System-UI veröffentlicht, kennt die App den Status der einzelnen Steuerelemente nicht. Das Abrufen des Status kann ein zeitaufwendiger Vorgang sein, der viele Hops im Netzwerk des Geräteanbieters umfasst. 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
:
Groovy
dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4" }
Kotlin
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4") }
Nachdem Sie Ihre Gradle-Dateien synchronisiert haben, fügen Sie Service
das folgende Snippet hinzu, 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<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 im Systemmenü nach unten und suchen Sie die Schaltfläche Gerätesteuerung (siehe Abbildung 4):
Wenn Sie auf Gerätesteuerung tippen, gelangen Sie zu einem zweiten Bildschirm, 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, wie in Abbildung 5 dargestellt:
Implementieren Sie jetzt die Methode createPublisherFor()
und fügen Sie Ihrem Service
Folgendes hinzu:
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.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 createPublisherFor()
-Methode eine gefälschte Implementierung dessen, was Ihre App tun muss: mit Ihrem Gerät kommunizieren, um den Status abzurufen und diesen Status an das System auszugeben.
Die Methode createPublisherFor()
verwendet Kotlin-Koroutinen und -Datenflüsse, um
die erforderliche Reactive Streams API. Gehen Sie dazu so vor:
- Erstellt ein
Flow
. - Wartet eine Sekunde.
- Erstellt und sendet den Status der intelligenten Lampe.
- Wartet eine weitere Sekunde.
- Erstellt und sendet den Status des Thermostats.
Aktionen verarbeiten
Mit der Methode performControlAction()
wird signalisiert, wann der Nutzer mit einem
veröffentlichtes Steuerelement. Die Aktion wird durch den Typ der gesendeten ControlAction
bestimmt.
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 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()); } }
Öffnen Sie die App, rufen Sie das Menü Gerätesteuerung auf und sehen Sie sich die Steuerelemente für Lampen und Thermostate an.