W Androidzie 11 i nowszych wersjach funkcja Szybki dostęp do sterowania urządzeniami umożliwia użytkownikom szybkie wyświetlanie zewnętrznych urządzeń, takich jak oświetlenie, termostaty i kamery, oraz sterowanie nimi za pomocą 3 interakcji z domyślnym programem uruchamiającym. Producent urządzenia wybiera, którego launchera używać. Agregatory urządzeń, np. Google Home, i aplikacje dostawców zewnętrznych mogą udostępniać urządzenia do wyświetlania w tym miejscu. Na tej stronie dowiesz się, jak wyświetlać elementy sterujące urządzeniami w tym miejscu i łączyć je z aplikacją sterującą.
Aby dodać tę obsługę, utwórz i zadeklaruj ControlsProviderService. Utwórz elementy sterujące obsługiwane przez aplikację na podstawie predefiniowanych typów elementów sterujących, a następnie utwórz wydawców tych elementów sterujących.
Interfejs użytkownika
Urządzenia są wyświetlane w sekcji Sterowanie urządzeniami jako widżety szablonowe. Dostępnych jest 5 widżetów sterowania urządzeniami, jak pokazano na ilustracji poniżej:
|
|
|
|
|
Naciśnięcie i przytrzymanie widżetu powoduje przejście do aplikacji, w której możesz go dokładniej kontrolować. Możesz dostosować ikonę i kolor każdego widżetu, ale dla wygody użytkowników używaj domyślnej ikony i koloru, jeśli domyślny zestaw pasuje do urządzenia.
Tworzenie usługi
W tej sekcji dowiesz się, jak utworzyć ControlsProviderService. Ta usługa informuje interfejs systemu Android, że aplikacja zawiera elementy sterujące urządzeniami, które muszą być wyświetlane w obszarze Elementy sterujące urządzeniami interfejsu Androida.
Interfejs ControlsProviderService API zakłada znajomość strumieni reaktywnych zdefiniowanych w projekcie Reactive Streams w GitHubie i zaimplementowanych w interfejsach Java 9 Flow.
Interfejs API opiera się na tych koncepcjach:
- Wydawca: Twoja aplikacja jest wydawcą.
- Subskrybent: interfejs systemu jest subskrybentem i może wysyłać do wydawcy prośby o różne elementy sterujące.
- Subskrypcja: okres, w którym wydawca może wysyłać aktualizacje do interfejsu systemu. Wydawca lub subskrybent może zamknąć to okno.
Deklarowanie usługi
Aplikacja musi deklarować usługę, np. MyCustomControlService, w swoim manifeście aplikacji.
Usługa musi zawierać filtr intencji dla ControlsProviderService. Ten filtr umożliwia aplikacjom dodawanie elementów sterujących do interfejsu systemu.
Potrzebujesz też label, który będzie wyświetlany w elementach sterujących w interfejsie systemu.
Poniższy przykład pokazuje, jak zadeklarować usługę:
<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>
Następnie utwórz nowy plik Kotlin o nazwie MyCustomControlService.kt i spraw, aby rozszerzał ControlsProviderService:
class MyCustomControlService : ControlsProviderService() { // ... }
Wybierz odpowiedni typ elementu sterującego
Interfejs API udostępnia metody tworzenia elementów sterujących. Aby wypełnić narzędzie do tworzenia, określ urządzenie, którym chcesz sterować, i sposób, w jaki użytkownik wchodzi z nim w interakcję. Wykonaj te czynności:
- Wybierz typ urządzenia, które reprezentuje element sterujący. Klasa
DeviceTypesto wyliczenie wszystkich obsługiwanych urządzeń. Typ służy do określania ikon i kolorów urządzenia w interfejsie. - Określ nazwę widoczną dla użytkownika, lokalizację urządzenia (np. kuchnia) i inne elementy tekstowe interfejsu powiązane z elementem sterującym.
- Wybierz najlepszy szablon, który ułatwi interakcję z użytkownikami. Elementom sterującym przypisywany jest identyfikator
ControlTemplatez aplikacji. Ten szablon bezpośrednio pokazuje użytkownikowi stan elementu sterującego oraz dostępne metody wprowadzania, czyliControlAction. W tabeli poniżej znajdziesz niektóre dostępne szablony i działania, które obsługują:
| Szablon | Działanie | Opis |
ControlTemplate.getNoTemplateObject()
|
None
|
Aplikacja może używać tego elementu do przekazywania informacji o kontrolce, ale użytkownik nie może z nim wchodzić w interakcje. |
ToggleTemplate
|
BooleanAction
|
Reprezentuje element sterujący, który można przełączać między stanami włączonym i wyłączonym. Obiekt BooleanAction zawiera pole, które zmienia się
w odpowiedzi na kliknięcie przez użytkownika elementu sterującego, aby odzwierciedlić nowy stan.
|
RangeTemplate
|
FloatAction
|
Reprezentuje widżet suwaka z określonymi wartościami minimalną, maksymalną i krokową. Gdy użytkownik wejdzie w interakcję z suwakiem, wyślij do aplikacji nowy obiekt FloatAction z aktualną wartością.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Ten szablon jest połączeniem szablonów ToggleTemplate i RangeTemplate. Obsługuje zdarzenia dotyku oraz suwak, np. do sterowania ściemnianym oświetleniem.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Oprócz wykonywania powyższych działań ten szablon umożliwia użytkownikowi ustawienie trybu, np. ogrzewania, chłodzenia, ogrzewania/chłodzenia, trybu Eko lub wyłączenia. |
StatelessTemplate
|
CommandAction
|
Używany do wskazywania elementu sterującego, który ma funkcję dotykową, ale którego stanu nie można określić, np. pilota do telewizora na podczerwień. Za pomocą tego szablonu możesz zdefiniować procedurę lub makro, czyli zbiór zmian stanu i ustawień. |
Na podstawie tych informacji możesz utworzyć kontrolę:
- Użyj klasy narzędzia do tworzenia
Control.StatelessBuilder, gdy stan elementu sterującego jest nieznany. - Użyj klasy narzędzia do tworzenia
Control.StatefulBuilder, gdy stan elementu sterującego jest znany.
Aby na przykład sterować inteligentnym oświetleniem i termostatem, dodaj do pliku MyCustomControlService te stałe:
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() { // ... }
Tworzenie wydawców dla elementów sterujących
Po utworzeniu kontroli musi ona mieć wydawcę. Wydawca informuje interfejs systemu o istnieniu elementu sterującego. Klasa ControlsProviderService ma 2 metody wydawcy, które musisz zastąpić w kodzie aplikacji:
createPublisherForAllAvailable: tworzyPublisherdla wszystkich elementów sterujących dostępnych w aplikacji. UżyjControl.StatelessBuilder, aby utworzyć obiektyControldla tego wydawcy.createPublisherFor: tworzyPublisherdla listy podanych elementów sterujących, zidentyfikowanych za pomocą identyfikatorów tekstowych. UżywajControl.StatefulBuilder, aby tworzyć te obiektyControl, ponieważ wydawca musi przypisać stan do każdego elementu sterującego.
Tworzenie wydawcy
Gdy aplikacja po raz pierwszy publikuje elementy sterujące w interfejsie systemu, nie zna stanu poszczególnych elementów. Pobieranie stanu może być czasochłonną operacją, która wymaga wielu przeskoków w sieci dostawcy urządzenia. Użyj metody createPublisherForAllAvailable, aby poinformować system o dostępnych elementach sterujących. Ta metoda korzysta z klasy narzędzia do tworzenia Control.StatelessBuilder, ponieważ stan każdego elementu sterującego jest nieznany.
Gdy elementy sterujące pojawią się w interfejsie Androida, użytkownicy mogą wybrać ulubione elementy sterujące.
Aby użyć współprogramów Kotlin do utworzenia ControlsProviderService, dodaj nową zależność do pliku build.gradle:
Dynamiczny
dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4" }
Kotlin
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4") }
Po zsynchronizowaniu plików Gradle dodaj do pliku Service ten fragment kodu, aby zaimplementować createPublisherForAllAvailable:
class MyCustomControlService : ControlsProviderService() { override fun createPublisherForAllAvailable(): Flow.Publisher<Control> = 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<String>): Flow.Publisher<Control> { TODO() } override fun performControlAction( controlId: String, action: ControlAction, consumer: Consumer<Int>, ) { TODO() } }
Przesuń palcem w dół menu systemowego i znajdź przycisk Sterowanie urządzeniem, jak pokazano na rysunku 4:
Kliknięcie Sterowanie urządzeniem powoduje przejście do drugiego ekranu, na którym możesz wybrać aplikację. Po wybraniu aplikacji zobaczysz, jak poprzedni fragment kodu tworzy niestandardowe menu systemowe z nowymi elementami sterującymi, jak pokazano na ilustracji 5:
Teraz zaimplementuj metodę createPublisherFor, dodając do Service ten kod:
private val job = SupervisorJob() private val scope = CoroutineScope(Dispatchers.IO + job) private val controlFlows = mutableMapOf<String, MutableSharedFlow<Control>>() private var toggleState = false private var rangeState = 18f override fun createPublisherFor(controlIds: List<String>): Flow.Publisher<Control> { val flow = MutableSharedFlow<Control>(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 <T> 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() }
W tym przykładzie metoda createPublisherFor zawiera fałszywą implementację tego, co musi robić Twoja aplikacja: komunikować się z urządzeniem, aby pobrać jego stan, i przekazywać ten stan do systemu.
Metoda createPublisherFor korzysta z korutyn i przepływów Kotlin, aby spełnić wymagania interfejsu Reactive Streams API. W tym celu wykonuje te czynności:
- Tworzy
Flow. - Czeka przez sekundę.
- Tworzy i emituje stan inteligentnego oświetlenia.
- Czeka kolejną sekundę.
- Tworzy i emituje stan termostatu.
Obsługa działań
Metoda performControlAction sygnalizuje, kiedy użytkownik wchodzi w interakcję z opublikowanym elementem sterującym. Typ wysłanego ControlAction określa działanie.
Wykonaj odpowiednie działanie dla danego elementu sterującego, a następnie zaktualizuj stan urządzenia w interfejsie Androida.
Aby dokończyć przykład, dodaj do pliku Service te informacje:
override fun performControlAction( controlId: String, action: ControlAction, consumer: Consumer<Int>, ) { 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) }
Uruchom aplikację, otwórz menu Sterowanie urządzeniami i wyświetl elementy sterujące oświetleniem i termostatem.