W Androidzie 11 i nowszych funkcja Szybkie sterowanie urządzeniami umożliwia użytkownikowi szybkie wyświetlanie i sterowanie urządzeniami zewnętrznymi, takimi jak światła, termostaty i kamery, w ramach 3 interakcji z domyślnym menu. Producent OEM urządzenia wybiera, z jakiego menu korzystać. Agregatory urządzeń, takie jak Google Home, oraz aplikacje innych firm mogą wyświetlać urządzenia na tym ekranie. Na tej stronie dowiesz się, jak wyświetlać elementy sterujące urządzeniami w tej przestrzeni i łączyć je z aplikacją sterującą.
Aby dodać tę pomoc, utwórz i ogłoś ControlsProviderService
. Utwórz elementy sterujące, które obsługuje Twoja aplikacja, na podstawie wstępnie zdefiniowanych typów elementów sterujących, a następnie utwórz wydawców dla tych elementów.
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ądzeniem, jak pokazano na rysunku poniżej:
![]() |
![]() |
![]() |
![]() |
![]() |
Naciśnięcie i przytrzymanie widżetu powoduje otwarcie aplikacji, w której możesz uzyskać większą kontrolę. 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.
![Obraz pokazujący widżet panelu temperatury (otwarty)](https://developer.android.google.cn/static/images/device-control/device-control-panel2.png?authuser=19&hl=pl)
Tworzenie usługi
W tej sekcji dowiesz się, jak utworzyć ControlsProviderService
.
Ta usługa informuje interfejs użytkownika systemu Android, że Twoja aplikacja zawiera elementy sterujące urządzeniem, które muszą być wyświetlane w obszarze Elementy sterujące urządzeniami w interfejsie Androida.
Interfejs API ControlsProviderService
zakłada znajomość strumieni reaktywnych zdefiniowanych w projekcie Reaktywne strumienie na GitHubie i wdrożonych w interfejsach Java 9 Flow.
Interfejs API opiera się na tych koncepcjach:
- Wydawca: wydawcą jest Twoja aplikacja.
- Subskrybent: interfejs użytkownika jest subskrybentem i może prosić wydawcę o różne opcje.
- Subskrypcja: okres, w którym wydawca może wysyłać aktualizacje do interfejsu systemu. Okno to może zamknąć zarówno wydawca, jak i subskrybent.
Oświadczenie o usługach
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 użytkownika systemu.
Musisz też mieć label
wyświetlany w elementach sterujących w interfejsie systemu.
Przykład deklarowania usługi:
<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 Kotlina o nazwie MyCustomControlService.kt
i rozszerz go o plik ControlsProviderService()
:
Kotlin
class MyCustomControlService : ControlsProviderService() { ... }
Java
public class MyCustomJavaControlService extends ControlsProviderService { ... }
Wybierz odpowiedni typ elementu sterującego
Interfejs API udostępnia metody konstruktora do tworzenia elementów sterujących. Aby wypełnić kreator, określ urządzenie, którym chcesz sterować, oraz sposób interakcji użytkownika z tym urządzeniem. Wykonaj te czynności:
- Wybierz typ urządzenia, które reprezentuje element sterujący. Klasa
DeviceTypes
to 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 umożliwi interakcję z użytkownikiem. Elementy sterujące są przypisywane do aplikacji
ControlTemplate
. Ten szablon bezpośrednio pokazuje użytkownikowi stan elementu sterującego oraz dostępne metody wprowadzania, czyliControlAction
. W tabeli poniżej znajdziesz kilka dostępnych szablonów 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 elementach sterujących, ale użytkownik nie może z nimi wchodzić w interakcje. |
ToggleTemplate
|
BooleanAction
|
Reprezentuje element sterujący, który może być przełączany między stanem włączonym i wyłączonym. Obiekt BooleanAction zawiera pole, które zmienia się, aby reprezentować żądany nowy stan, gdy użytkownik kliknie element sterujący.
|
RangeTemplate
|
FloatAction
|
Reprezentuje suwak z określonymi wartościami minimalną, maksymalną i kroku. Gdy użytkownik wejdzie w interakcję z suwak, prześlij do aplikacji nowy obiekt FloatAction z zaktualizowaną wartością.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Ten szablon jest kombinacją szablonów ToggleTemplate i RangeTemplate . Obsługuje zdarzenia dotykowe, a także suwak,
na przykład do sterowania oświetleniem z możliwością przyciemniania.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Oprócz opakowania poprzednich działań ten szablon umożliwia użytkownikowi wybór trybu, takiego jak ogrzewanie, chłodzenie, ogrzewanie/chłodzenie, tryb eko lub wyłączenie. |
StatelessTemplate
|
CommandAction
|
Służy do wskazania elementu sterującego, który umożliwia obsługę dotykiem, ale którego stanu nie można określić, np. pilota do telewizora z podczerwienią. Za pomocą tego szablonu możesz zdefiniować rutynę lub makro, które jest zbiorem zmian stanu i sterowania. |
Dzięki tym informacjom możesz utworzyć element sterujący:
- Jeśli stan elementu sterującego jest nieznany, użyj klasy twórczej
Control.StatelessBuilder
. - Użyj klasy
Control.StatefulBuilder
builder, gdy stan elementu sterującego jest znany.
Aby na przykład sterować inteligentną żarówką i termostatem, dodaj do MyCustomControlService
te stałe:
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; ... }
Tworzenie wydawców dla ustawień
Po utworzeniu grupy kontrolnej musisz dodać do niej wydawcę. Wydawca informuje użytkownika o istnieniu elementu sterującego w interfejsie systemu. Klasa ControlsProviderService
ma 2 metody wydawcy, które musisz zastąpić w kodzie aplikacji:
createPublisherForAllAvailable()
: tworzy obiektPublisher
dla wszystkich elementów sterujących dostępnych w aplikacji. Aby tworzyć obiektyControl
dla tego wydawcy, użyj obiektuControl.StatelessBuilder()
.createPublisherFor()
: tworzyPublisher
dla listy podanych elementów sterujących, zidentyfikowanych na podstawie ich identyfikatorów ciągu znaków. Aby tworzyć te obiektyControl
, używaj elementuControl.StatefulBuilder
, 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 skoków w sieci dostawcy urządzenia. Użyj metody createPublisherForAllAvailable()
, aby poinformować system o dostępnych kontrolkach. Ta metoda używa klasy konstruktora Control.StatelessBuilder
, ponieważ stan każdego elementu sterującego jest nieznany.
Gdy elementy sterujące pojawią się w interfejsie Androida , użytkownik może wybrać ulubione.
Aby użyć coroutines w Kotlinie do utworzenia ControlsProviderService
, dodaj nowe zależności do 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") }
Po zsynchronizowaniu plików Gradle dodaj do pliku Service
ten fragment kodu, aby zaimplementować createPublisherForAllAvailable()
:
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); } }
Przesuń menu systemowe w dół i znajdź przycisk Sterowanie urządzeniami, jak pokazano na rysunku 4.
![Obraz przedstawiający interfejs systemu sterowania urządzeniami](https://developer.android.google.cn/static/images/ui/device_controls_ui.png?authuser=19&hl=pl)
Po kliknięciu Elementy sterujące urządzeniami wyświetli się drugi ekran, na którym możesz wybrać swoją aplikację. Po jej wybraniu zobaczysz, jak poprzedni fragment kodu tworzy niestandardowe menu systemowe z nowymi elementami sterującymi, jak pokazano na rysunku 5:
![Ilustracja przedstawiająca menu systemu z kontrolą światła i termostatu](https://developer.android.google.cn/static/images/ui/device_controls_example_1.png?authuser=19&hl=pl)
Teraz zaimplementuj metodę createPublisherFor()
, dodając do klasy Service
te elementy:
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(); }
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 przekazywać ten stan do systemu.
Metoda createPublisherFor()
używa coroutines i flows w Kotlinie, aby spełnić wymagania interfejsu Reactive Streams API. Aby to zrobić:
- Tworzy
Flow
. - Czeka przez 1 sekundę.
- Tworzy i wysyła stan inteligentnego oświetlenia.
- Czeka jeszcze przez sekundę.
- Tworzy i wysyła stan termostatu.
Obsługa działań
Metoda performControlAction()
sygnalizuje, że użytkownik wchodzi w interakcję z opublikowanym elementem sterującym. Działanie zależy od typu wysłanego ControlAction
.
Wykonaj odpowiednie działanie w przypadku danego elementu sterującego, a następnie zaktualizuj stan urządzenia w interfejsie Androida.
Aby utworzyć przykład, dodaj do pliku Service
te elementy:
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()); } }
Uruchom aplikację, otwórz menu Urządzenia i sprawdź elementy sterujące światłem i termostatem.
![Obraz przedstawiający sterowanie światłem i termostatem](https://developer.android.google.cn/static/images/ui/device_controls_example_2.png?authuser=19&hl=pl)