W Androidzie 11 i nowszych funkcja Szybki dostęp Pozwala szybko przeglądać i sterować urządzeniami zewnętrznymi, takimi jak oświetlenie, termostatów i kamer z oferty użytkownika w ciągu trzech interakcji domyślny program uruchamiający. Program uruchamiający określa producent urządzenia. Urządzenie (np. Google Home) i aplikacje innych firm mogą pozwala udostępniać urządzenia do wyświetlania w tym miejscu. Z tej strony dowiesz się, jak sterowanie urządzeniami w tym pokoju i połącz je ze swoją aplikacją do sterowania urządzeniami.
Aby dodać tę obsługę, utwórz i zadeklaruj ControlsProviderService
. Utwórz
obsługuje Twoją aplikację na podstawie wstępnie zdefiniowanych typów elementów sterujących, a następnie utwórz
wydawców.
Interfejs użytkownika
Urządzenia są wyświetlane w sekcji Sterowanie urządzeniami jako widżety szablonowe. Pięć są dostępne widżety sterowania urządzeniami, które przedstawiono na tej ilustracji:
|
|
|
|
|
Dotykanie i przytrzymanie widżetu powoduje przejście do aplikacji, co zapewnia pełniejszą kontrolę. Dostępne opcje dostosować ikonę i kolor każdego widżetu, ale dla wygody użytkowników użyć domyślnej ikony i koloru, jeśli zestaw domyślny pasuje do urządzenia.
Tworzenie usługi
W tej sekcji dowiesz się, jak utworzyć
ControlsProviderService
Ta usługa informuje interfejs systemu Android, że Twoja aplikacja zawiera elementy sterujące urządzenia
które trzeba umieścić w obszarze Sterowanie urządzeniami w interfejsie Androida.
Interfejs API ControlsProviderService
zakłada znajomość strumieni reaktywnych,
zdefiniowane w strumieniach reaktywnych na GitHubie
projekt
i zaimplementowano w przepływie języka Java 9
.
Działanie interfejsu API opiera się na następujących koncepcjach:
- Wydawca: aplikacja jest wydawcą.
- Subskrybent: interfejs systemu to aplikacja subskrybująca, która może żądać liczby. kontroli ze strony wydawcy.
- Subskrypcja:okres, w którym wydawca może przesyłać aktualizacje. do UI systemu. Wydawca lub subskrybent może zamknąć to okno okno.
Deklarowanie usługi
Aplikacja musi zadeklarować usługę – np. MyCustomControlService
– w
o manifeście aplikacji.
Usługa musi zawierać filtr intencji dla: ControlsProviderService
. Ten
umożliwia aplikacjom współtworzenie elementów sterujących w interfejsie systemu.
Potrzebujesz też elementu label
wyświetlanego w elementach sterujących w interfejsie systemu.
Przykład poniżej 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
przedłuż ControlsProviderService()
:
Kotlin
class MyCustomControlService : ControlsProviderService() { ... }
Java
public class MyCustomJavaControlService extends ControlsProviderService { ... }
Wybierz odpowiedni typ elementu sterującego
Interfejs API udostępnia metody kreatora do tworzenia elementów sterujących. Aby wypełnić pole jak określić urządzenie, którym chcesz sterować, i sposób interakcji użytkownika z nim. Wykonaj te czynności:
- Wybierz typ urządzenia reprezentowanego przez element sterujący.
Zajęcia
DeviceTypes
to listę wszystkich obsługiwanych urządzeń. Typ jest używany do określania ikony i kolory urządzenia w interfejsie. - Określ nazwę widoczną dla użytkownika lub lokalizację urządzenia, na przykład Kitchen i inne elementy tekstowe interfejsu powiązane z elementem sterującym.
- Wybrać najlepszy szablon do interakcji z użytkownikami. Elementy sterujące mają przypisany
ControlTemplate
z aplikacji. Ten szablon bezpośrednio pokazuje stan kontroli oraz dostępnych metod wprowadzania, czyliControlAction
W tabeli poniżej znajdziesz niektóre dostępne szablony oraz działania obsługują:
Szablon | Działanie | Opis |
ControlTemplate.getNoTemplateObject()
|
None
|
Aplikacja może użyć tego parametru, aby przekazać informacje o elemencie sterującym, ale nie może wchodzić z nią w interakcję. |
ToggleTemplate
|
BooleanAction
|
Reprezentuje element sterujący, który może być włączony lub wyłączony
stanów. Obiekt BooleanAction zawiera pole, które zmienia się
reprezentujący nowy stan po kliknięciu elementu sterującego przez użytkownika.
|
RangeTemplate
|
FloatAction
|
Reprezentuje widżet z określonymi wartościami minimalną, maksymalną i liczbą kroków. Kiedy
użytkownik wchodzi w interakcję z suwakiem, wyślij nowy FloatAction
z powrotem do aplikacji ze zaktualizowaną wartością.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Ten szablon stanowi kombinację atrybutów ToggleTemplate i
RangeTemplate Obsługuje zdarzenia dotyku oraz suwak,
np. do sterowania przyciemnianym światłem.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Oprócz hermetyzacji poprzednich działań ten szablon umożliwia użytkownik ustawił tryb na ogrzewanie, chłodzenie, ogrzewanie/chłodzenie, eko lub wyłączenie. |
StatelessTemplate
|
CommandAction
|
Określa element sterujący, który obsługuje dotyk, ale którego stan których nie da się określić, np. za pomocą pilota do telewizora na podczerwień. Możesz użyć tej szablon do zdefiniowania rutyny lub makra, które stanowią agregację wartości kontrolnych i stanu. |
Dzięki tym informacjom możesz utworzyć element sterujący:
- Użyj
Control.StatelessBuilder
klasą konstruktora, gdy stan elementu sterującego jest nieznany. - Użyj
Control.StatefulBuilder
klasą konstruktora, gdy znany jest stan elementu sterującego.
Aby na przykład sterować inteligentną żarówką i termostatem, dodaj te elementy:
stałe do urządzenia 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; ... }
Tworzenie wydawców na potrzeby ustawień
Gdy utworzysz element sterujący, potrzebny będzie wydawca. Wydawca informuje
z interfejsem użytkownika z istnieniem elementu sterującego. Zajęcia: ControlsProviderService
udostępnia dwie metody wydawcy, które musisz zastąpić w kodzie aplikacji:
createPublisherForAllAvailable()
: tworzyPublisher
. dla wszystkich elementów sterujących dostępnych w aplikacji. UżyjControl.StatelessBuilder()
aby utworzyć obiektyControl
dla tego wydawcy.createPublisherFor()
: tworzyPublisher
dla listy podanych elementów sterujących, co jest określane przez identyfikatory ciągu znaków.Control.StatefulBuilder
– do tych obiektówControl
, bo wydawca musi przypisać stan każdego elementu sterującego.
Tworzenie wydawcy
Kiedy aplikacja po raz pierwszy opublikuje elementy sterujące w interfejsie systemu, aplikacja nie wie
stan każdego elementu sterującego. Uzyskanie stanu może być czasochłonne
wiele przeskoków w sieci dostawcy urządzeń. Użyj
createPublisherForAllAvailable()
aby zareklamować systemowi dostępne elementy sterujące. Ta metoda korzysta z metody
Control.StatelessBuilder
klasę konstruktora, ponieważ stan każdego elementu sterującego to
nieznane.
Gdy w interfejsie Androida pojawią się elementy sterujące , użytkownik może wybrać ulubione elementów sterujących.
Aby użyć współrzędnych Kotlina do utworzenia elementu ControlsProviderService
, dodaj nowy
zależność od build.gradle
:
Odlotowe
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 Service
ten fragment kodu
zastosuj 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> 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ądzeniem widoczny w rysunek 4.
Kliknij Sterowanie urządzeniami, aby przejść do drugiego ekranu, na którym możesz wybrać do aplikacji. Po wybraniu aplikacji zobaczysz, jak poprzedni fragment tworzy niestandardowe menu systemowe z nowymi elementami sterującymi, jak na ilustracji 5:
Teraz zaimplementuj metodę createPublisherFor()
, dodając poniższy kod do
Service
:
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(); }
W tym przykładzie metoda createPublisherFor()
zawiera fałszywe
określić, co musi zrobić aplikacja: komunikować się z urządzeniem,
pobrać jej stan i przesłać go do systemu.
Metoda createPublisherFor()
wykorzystuje współrzędne i przepływy Kotlin w celu spełnienia
wymagany interfejs Reactive Streams API. W tym celu wykonaj te czynności:
- Tworzy
Flow
. - Poczekaj 1 sekundę.
- Tworzy i emituje stan inteligentnego oświetlenia.
- Czekam jeszcze sekundę.
- Tworzy i wysyła stan termostatu.
Obsługa działań
Metoda performControlAction()
sygnalizuje, kiedy użytkownik wchodzi w interakcję z tagiem
opublikowany element sterujący. Działanie zależy od typu wysłanego pliku ControlAction
.
Wykonaj odpowiednie działanie dla danego elementu sterującego, a następnie zaktualizuj stan
w interfejsie Androida.
Aby zakończyć przykład, dodaj do Service
:
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 Sterowanie urządzeniem i zobacz, jak świecą się diody sterowanie termostatem.