W Androidzie 11 i nowszych funkcja szybkiego dostępu do sterowania urządzeniami umożliwia użytkownikom szybkie wyświetlanie urządzeń zewnętrznych, takich jak światła, termostaty i kamery, oraz sterowanie nimi za pomocą interfejsu użytkownika w ramach 3 interakcji z domyślnego programu uruchamiającego. Producent OEM urządzenia wybiera, którego launchera użyć. Urządzenia mogą być wyświetlane w tym miejscu przez agregatory urządzeń, takie jak Google Home, oraz aplikacje innych firm. Na tej stronie dowiesz się, jak wyświetlać sterowanie urządzeniami w tym miejscu i łączyć je z aplikacją do sterowania.
Aby dodać tę obsługę, utwórz i zadeklaruj ControlsProviderService. Utwórz elementy sterujące obsługiwane przez aplikację na podstawie wstępnie zdefiniowanych typów elementów sterujących, a następnie utwórz dla nich wydawców.
Interfejs użytkownika
Urządzenia są wyświetlane w sekcji Sterowanie urządzeniami jako widżety szablonów. 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, co umożliwia dokładniejsze sterowanie. Możesz dostosować ikonę i kolor każdego widżetu, ale dla wygody użytkowników użyj 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 sterowania urządzeniem, które muszą być wyświetlane w obszarze Sterowanie urządzeniem w interfejsie Androida.
Interfejs API ControlsProviderService zakłada znajomość strumieni reaktywnych, zgodnie z definicją w projekcie Reactive Streams na GitHubie i implementacją w interfejsach Flow w języku Java 9.
Interfejs API opiera się na tych pojęciach:
- Wydawca: Twoja aplikacja jest wydawcą.
- Subskrybent: interfejs systemu jest subskrybentem i może żądać od wydawcy określonej liczby elementów sterujących.
- Subskrypcja: okres, w którym wydawca może wysyłać aktualizacje do interfejsu systemu. Okno może zamknąć wydawca lub subskrybent.
Deklarowanie usługi
Aplikacja musi deklarować usługę, np. MyCustomControlService, w pliku manifestu aplikacji.
Usługa musi zawierać filtr intencji dla ControlsProviderService. Ten filtr umożliwia aplikacjom dodawanie elementów sterujących do interfejsu systemu.
Potrzebujesz też elementu label, który jest 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 rozszerz go o ControlsProviderService:
class MyCustomControlService : ControlsProviderService() { // ... }
Wybierz odpowiedni typ elementu sterującego
Interfejs API udostępnia metody kreatora do tworzenia elementów sterujących. Aby wypełnić kreator, określ urządzenie, którym chcesz sterować, i sposób, w jaki użytkownik będzie z nim wchodzić w interakcje. 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 szablon, który najlepiej wspiera interakcję z użytkownikiem. Elementom sterującym przypisuje się
ControlTemplatez aplikacji. Ten szablon bezpośrednio pokazuje użytkownikowi stan elementu sterującego oraz dostępne metody wprowadzania danych, czyliControlAction. W tabeli poniżej znajdziesz niektóre dostępne szablony i obsługiwane przez nie działania:
| Szablon | Działanie | Opis |
ControlTemplate.getNoTemplateObject()
|
None
|
Aplikacja może używać tego elementu do przekazywania informacji o elemencie sterującym, 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 stanem włączonym i wyłączonym. Obiekt BooleanAction zawiera pole, które zmienia się, aby odzwierciedlić żądany nowy stan, gdy użytkownik kliknie element sterujący.
|
RangeTemplate
|
FloatAction
|
Reprezentuje widżet suwaka z określonymi wartościami minimalnymi, maksymalnymi i krokowymi. Gdy użytkownik wejdzie w interakcję z suwakiem, wyślij do aplikacji nowy obiekt FloatAction ze zaktualizowaną wartością.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Ten szablon jest połączeniem ToggleTemplate i RangeTemplate. Obsługuje zdarzenia dotknięcia oraz suwak, np. do sterowania oświetleniem z możliwością ściemniania.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Oprócz powyższych działań ten szablon umożliwia użytkownikowi ustawienie trybu, takiego jak ogrzewanie, chłodzenie, ogrzewanie/chłodzenie, eko lub wyłączone. |
StatelessTemplate
|
CommandAction
|
Służy do wskazywania elementu sterującego, który umożliwia sterowanie dotykowe, ale którego stanu nie można określić, np. pilota do telewizora na podczerwień. Możesz użyć tego szablonu, aby zdefiniować rutynę lub makro, które jest agregacją zmian stanu i sterowania. |
Mając te informacje, możesz utworzyć element sterujący:
- Jeśli stan elementu sterującego jest nieznany, użyj klasy kreatora
Control.StatelessBuilder. - Gdy stan elementu sterującego jest znany, użyj klasy kreatora
Control.StatefulBuilder.
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 na potrzeby ustawień
Po utworzeniu elementu sterującego musisz dodać do niego 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: tworzy obiektPublisherdla wszystkich elementów sterujących dostępnych w aplikacji. UżyjControl.StatelessBuilder, aby utworzyć obiektyControldla tego wydawcy.createPublisherFor: tworzyPublisherdla listy podanych ustawień, które są identyfikowane przez ich identyfikatory ciągów znaków. UżyjControl.StatefulBuilder, aby utworzyć 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 używa klasy kreatora 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 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ół po menu systemowym i znajdź przycisk Sterowanie urządzeniami, 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 rysunku 5:
Teraz zaimplementuj metodę createPublisherFor, dodając do pliku 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 zrobić aplikacja: komunikować się z urządzeniem, aby pobrać jego stan i przekazać go do systemu.
Metoda createPublisherFor używa współprogramów i przepływów Kotlin, aby spełnić wymagania interfejsu Reactive Streams API, wykonując te czynności:
- Tworzy
Flow. - Odczekuje 1 sekundę.
- Tworzy i emituje stan inteligentnego oświetlenia.
- Odczekaj kolejną sekundę.
- Tworzy i emituje stan termostatu.
Działania związane z nickami
Metoda performControlAction sygnalizuje, gdy 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 elementy:
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ądzeniem i sprawdź elementy sterujące oświetleniem i termostatem.