Управление внешними устройствами

В Android 11 и более поздних версиях функция «Быстрый доступ к элементам управления устройствами» позволяет пользователям быстро просматривать и управлять внешними устройствами, такими как освещение, термостаты и камеры, с помощью пользовательского интерфейса всего за три действия, используя стандартный лаунчер. Производитель устройства выбирает, какой лаунчер использовать. Агрегаторы устройств — например, Google Home — и приложения сторонних разработчиков могут предоставлять устройства для отображения в этом пространстве. На этой странице показано, как отображать элементы управления устройствами в этом пространстве и связывать их с вашим приложением управления.

Рисунок 1. Панель управления устройством в пользовательском интерфейсе Android.

Для добавления этой поддержки создайте и объявите ControlsProviderService . Создайте элементы управления, поддерживаемые вашим приложением, на основе предопределенных типов элементов управления, а затем создайте издателей для этих элементов управления.

Пользовательский интерфейс

Устройства отображаются в разделе «Управление устройствами» в виде шаблонных виджетов. Доступно пять виджетов управления устройствами, как показано на следующем рисунке:

Переключить виджет для управления устройством
Переключать
Переключать с помощью виджета-слайдера
Переключатель с помощью ползунка
Виджет ползунка диапазона для управления устройством
Диапазон действия (не включается и не выключается)
виджет управления переключением без сохранения состояния
переключатель без состояния
Виджет панели температуры в закрытом состоянии.
Температурная панель (закрыта)
Рисунок 2. Набор виджетов, созданных по шаблону.

При касании и удержании виджета вы переходите в приложение для более детального управления. Вы можете настроить значок и цвет каждого виджета, но для наилучшего пользовательского опыта используйте значок и цвет по умолчанию, если набор по умолчанию соответствует устройству.

Виджет панели температуры в открытом состоянии
Рисунок 3. Виджет панели «Открыть температуру».

Создайте сервис

В этом разделе показано, как создать ControlsProviderService . Этот сервис сообщает системному интерфейсу Android, что ваше приложение содержит элементы управления устройства, которые должны отображаться в области « Элементы управления устройства» пользовательского интерфейса Android.

API ControlsProviderService предполагает знакомство с реактивными потоками, как они определены в проекте Reactive Streams на GitHub и реализованы в интерфейсах Flow Java 9. API основан на следующих концепциях:

  • Издатель: ваше приложение является издателем.
  • Подписчик: пользовательский интерфейс системы является подписчиком и может запрашивать у издателя ряд элементов управления.
  • Подписка: период времени, в течение которого издатель может отправлять обновления в пользовательский интерфейс системы. Закрыть это окно может как издатель, так и подписчик.

Заявить об услуге

В вашем приложении необходимо объявить службу, например, MyCustomControlService , в манифесте приложения.

Для ControlsProviderService служба должна включать фильтр намерений. Этот фильтр позволяет приложениям добавлять элементы управления в пользовательский интерфейс системы.

Вам также потребуется label , которая будет отображаться в элементах управления в пользовательском интерфейсе системы.

В следующем примере показано, как объявить сервис:

<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>

Далее создайте новый файл Kotlin с именем MyCustomControlService.kt и настройте его так, чтобы он наследовал ControlsProviderService :

class MyCustomControlService : ControlsProviderService() {
    // ...
}

Выберите правильный тип элемента управления.

API предоставляет методы-конструкторы для создания элементов управления. Чтобы заполнить конструктор, определите устройство, которым вы хотите управлять, и способ взаимодействия пользователя с ним. Выполните следующие шаги:

  1. Выберите тип устройства, который представляет элемент управления. Класс DeviceTypes представляет собой перечисление всех поддерживаемых устройств. Этот тип используется для определения значков и цветов устройства в пользовательском интерфейсе.
  2. Определите отображаемое пользователю имя, местоположение устройства — например, кухня — и другие текстовые элементы пользовательского интерфейса, связанные с элементом управления.
  3. Выберите наиболее подходящий шаблон для поддержки взаимодействия с пользователем. Элементам управления назначается шаблон ControlTemplate из приложения. Этот шаблон напрямую отображает пользователю состояние элемента управления, а также доступные методы ввода — то есть, ControlAction . В следующей таблице приведены некоторые из доступных шаблонов и поддерживаемые ими действия:
Шаблон Действие Описание
ControlTemplate.getNoTemplateObject() None Приложение может использовать это для передачи информации об элементе управления, но пользователь не может с ним взаимодействовать.
ToggleTemplate BooleanAction Представляет собой элемент управления, который можно переключать между включенным и выключенным состояниями. Объект BooleanAction содержит поле, которое изменяется, отображая запрошенное новое состояние при касании пользователем элемента управления.
RangeTemplate FloatAction Представляет собой виджет ползунка с заданными значениями min, max и step. При взаимодействии пользователя с ползунком, в приложение отправляется новый объект FloatAction с обновленным значением.
ToggleRangeTemplate BooleanAction , FloatAction Этот шаблон представляет собой комбинацию шаблонов ToggleTemplate и RangeTemplate . Он поддерживает сенсорные события, а также ползунок, например, для управления освещением с регулируемой яркостью.
TemperatureControlTemplate ModeAction , BooleanAction , FloatAction Помимо описания вышеописанных действий, этот шаблон позволяет пользователю установить режим, например, обогрев, охлаждение, обогрев/охлаждение, экономичный режим или выключение.
StatelessTemplate CommandAction Используется для обозначения элемента управления, обладающего сенсорными возможностями, но состояние которого невозможно определить, например, ИК-пульта дистанционного управления телевизором. Этот шаблон можно использовать для определения процедуры или макроса, представляющего собой совокупность изменений состояния и управления.

Используя эту информацию, вы можете создать элемент управления:

  • Используйте класс построителя Control.StatelessBuilder , если состояние элемента управления неизвестно.
  • Используйте класс-конструктор Control.StatefulBuilder , когда состояние элемента управления известно.

Например, для управления умной лампочкой и термостатом добавьте следующие константы в ваш MyCustomControlService :

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() {
    // ...
}

Создайте издателей для элементов управления.

После создания элемента управления ему необходим издатель. Издатель сообщает пользовательскому интерфейсу системы о существовании элемента управления. Класс ControlsProviderService имеет два метода издателя, которые необходимо переопределить в коде вашего приложения:

  • createPublisherForAllAvailable : создает Publisher для всех элементов управления, доступных в вашем приложении. Используйте Control.StatelessBuilder для создания объектов Control для этого объекта Publisher.
  • createPublisherFor : создает Publisher для списка заданных элементов управления, идентифицируемых по их строковым идентификаторам. Используйте Control.StatefulBuilder для создания этих объектов Control , поскольку издатель должен присвоить состояние каждому элементу управления.

Создайте издателя

Когда ваше приложение впервые публикует элементы управления в системном пользовательском интерфейсе, оно не знает состояния каждого элемента управления. Получение состояния может быть трудоемкой операцией, включающей множество переходов в сети поставщика устройства. Используйте метод createPublisherForAllAvailable , чтобы объявить системе о доступных элементах управления. Этот метод использует класс-конструктор Control.StatelessBuilder , поскольку состояние каждого элемента управления неизвестно.

После того как элементы управления появятся в пользовательском интерфейсе Android, пользователи смогут выбрать понравившиеся элементы управления.

Чтобы использовать корутины Kotlin для создания ControlsProviderService , добавьте новую зависимость в ваш build.gradle :

Классный

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4"
}

Котлин

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4")
}

После синхронизации файлов Gradle добавьте следующий фрагмент кода в ваш Service , чтобы реализовать 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()
    }
}

Прокрутите меню системы вниз и найдите кнопку «Управление устройством» , показанную на рисунке 4:

Пользовательский интерфейс системы для управления устройством
Рисунок 4. Элементы управления устройством в системном меню.

Нажатие на « Элементы управления устройством» переводит вас на второй экран, где вы можете выбрать приложение. После выбора приложения вы увидите, как предыдущий фрагмент кода создает пользовательское системное меню, отображающее ваши новые элементы управления, как показано на рисунке 5:

В системном меню отображается управление освещением и термостатом.
Рисунок 5. Дополнительные элементы управления освещением и термостатом.

Теперь реализуйте метод createPublisherFor , добавив в ваш Service следующее:

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()
}

В этом примере метод createPublisherFor содержит фиктивную реализацию того, что должно делать ваше приложение: взаимодействовать с вашим устройством для получения его статуса и передавать этот статус в систему.

Метод createPublisherFor использует сопрограммы и потоки Kotlin для удовлетворения требований API Reactive Streams, выполняя следующие действия:

  1. Создает Flow .
  2. Ждет одну секунду.
  3. Создает и отображает состояние умного светильника.
  4. Ждет еще секунду.
  5. Создает и передает состояние термостата.

Обработка действий

Метод performControlAction сигнализирует о взаимодействии пользователя с опубликованным элементом управления. Тип отправленного ControlAction определяет действие. Выполните соответствующее действие для данного элемента управления, а затем обновите состояние устройства в пользовательском интерфейсе Android.

Для завершения примера добавьте в свой Service следующее:

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)
}

Запустите приложение, откройте меню «Управление устройством» и просмотрите элементы управления освещением и термостатом.

Элементы управления отображают информацию о освещении и термостате.
Рисунок 6. Элементы управления освещением и термостатом.
,

В Android 11 и более поздних версиях функция «Быстрый доступ к элементам управления устройствами» позволяет пользователям быстро просматривать и управлять внешними устройствами, такими как освещение, термостаты и камеры, с помощью пользовательского интерфейса всего за три действия, используя стандартный лаунчер. Производитель устройства выбирает, какой лаунчер использовать. Агрегаторы устройств — например, Google Home — и приложения сторонних разработчиков могут предоставлять устройства для отображения в этом пространстве. На этой странице показано, как отображать элементы управления устройствами в этом пространстве и связывать их с вашим приложением управления.

Рисунок 1. Панель управления устройством в пользовательском интерфейсе Android.

Для добавления этой поддержки создайте и объявите ControlsProviderService . Создайте элементы управления, поддерживаемые вашим приложением, на основе предопределенных типов элементов управления, а затем создайте издателей для этих элементов управления.

Пользовательский интерфейс

Устройства отображаются в разделе «Управление устройствами» в виде шаблонных виджетов. Доступно пять виджетов управления устройствами, как показано на следующем рисунке:

Переключить виджет для управления устройством
Переключать
Переключать с помощью виджета-слайдера
Переключатель с помощью ползунка
Виджет ползунка диапазона для управления устройством
Диапазон действия (не включается и не выключается)
виджет управления переключением без сохранения состояния
переключатель без состояния
Виджет панели температуры в закрытом состоянии.
Температурная панель (закрыта)
Рисунок 2. Набор виджетов, созданных по шаблону.

При касании и удержании виджета вы переходите в приложение для более детального управления. Вы можете настроить значок и цвет каждого виджета, но для наилучшего пользовательского опыта используйте значок и цвет по умолчанию, если набор по умолчанию соответствует устройству.

Виджет панели температуры в открытом состоянии
Рисунок 3. Виджет панели «Открыть температуру».

Создайте сервис

В этом разделе показано, как создать ControlsProviderService . Этот сервис сообщает системному интерфейсу Android, что ваше приложение содержит элементы управления устройства, которые должны отображаться в области « Элементы управления устройства» пользовательского интерфейса Android.

API ControlsProviderService предполагает знакомство с реактивными потоками, как они определены в проекте Reactive Streams на GitHub и реализованы в интерфейсах Flow Java 9. API основан на следующих концепциях:

  • Издатель: ваше приложение является издателем.
  • Подписчик: пользовательский интерфейс системы является подписчиком и может запрашивать у издателя ряд элементов управления.
  • Подписка: период времени, в течение которого издатель может отправлять обновления в пользовательский интерфейс системы. Закрыть это окно может как издатель, так и подписчик.

Заявить об услуге

В вашем приложении необходимо объявить службу, например, MyCustomControlService , в манифесте приложения.

Для ControlsProviderService служба должна включать фильтр намерений. Этот фильтр позволяет приложениям добавлять элементы управления в пользовательский интерфейс системы.

Вам также потребуется label , которая будет отображаться в элементах управления в пользовательском интерфейсе системы.

В следующем примере показано, как объявить сервис:

<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>

Далее создайте новый файл Kotlin с именем MyCustomControlService.kt и настройте его так, чтобы он наследовал ControlsProviderService :

class MyCustomControlService : ControlsProviderService() {
    // ...
}

Выберите правильный тип элемента управления.

API предоставляет методы-конструкторы для создания элементов управления. Чтобы заполнить конструктор, определите устройство, которым вы хотите управлять, и способ взаимодействия пользователя с ним. Выполните следующие шаги:

  1. Выберите тип устройства, который представляет элемент управления. Класс DeviceTypes представляет собой перечисление всех поддерживаемых устройств. Этот тип используется для определения значков и цветов устройства в пользовательском интерфейсе.
  2. Определите отображаемое пользователю имя, местоположение устройства — например, кухня — и другие текстовые элементы пользовательского интерфейса, связанные с элементом управления.
  3. Выберите наиболее подходящий шаблон для поддержки взаимодействия с пользователем. Элементам управления назначается шаблон ControlTemplate из приложения. Этот шаблон напрямую отображает пользователю состояние элемента управления, а также доступные методы ввода — то есть, ControlAction . В следующей таблице приведены некоторые из доступных шаблонов и поддерживаемые ими действия:
Шаблон Действие Описание
ControlTemplate.getNoTemplateObject() None Приложение может использовать это для передачи информации об элементе управления, но пользователь не может с ним взаимодействовать.
ToggleTemplate BooleanAction Представляет собой элемент управления, который можно переключать между включенным и выключенным состояниями. Объект BooleanAction содержит поле, которое изменяется, отображая запрошенное новое состояние при касании пользователем элемента управления.
RangeTemplate FloatAction Представляет собой виджет ползунка с заданными значениями min, max и step. При взаимодействии пользователя с ползунком, в приложение отправляется новый объект FloatAction с обновленным значением.
ToggleRangeTemplate BooleanAction , FloatAction Этот шаблон представляет собой комбинацию шаблонов ToggleTemplate и RangeTemplate . Он поддерживает сенсорные события, а также ползунок, например, для управления освещением с регулируемой яркостью.
TemperatureControlTemplate ModeAction , BooleanAction , FloatAction Помимо описания вышеописанных действий, этот шаблон позволяет пользователю установить режим, например, обогрев, охлаждение, обогрев/охлаждение, экономичный режим или выключение.
StatelessTemplate CommandAction Используется для обозначения элемента управления, обладающего сенсорными возможностями, но состояние которого невозможно определить, например, ИК-пульта дистанционного управления телевизором. Этот шаблон можно использовать для определения процедуры или макроса, представляющего собой совокупность изменений состояния и управления.

Используя эту информацию, вы можете создать элемент управления:

  • Используйте класс построителя Control.StatelessBuilder , если состояние элемента управления неизвестно.
  • Используйте класс-конструктор Control.StatefulBuilder , когда состояние элемента управления известно.

Например, для управления умной лампочкой и термостатом добавьте следующие константы в ваш MyCustomControlService :

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() {
    // ...
}

Создайте издателей для элементов управления.

После создания элемента управления ему необходим издатель. Издатель сообщает пользовательскому интерфейсу системы о существовании элемента управления. Класс ControlsProviderService имеет два метода издателя, которые необходимо переопределить в коде вашего приложения:

  • createPublisherForAllAvailable : создает Publisher для всех элементов управления, доступных в вашем приложении. Используйте Control.StatelessBuilder для создания объектов Control для этого объекта Publisher.
  • createPublisherFor : создает Publisher для списка заданных элементов управления, идентифицируемых по их строковым идентификаторам. Используйте Control.StatefulBuilder для создания этих объектов Control , поскольку издатель должен присвоить состояние каждому элементу управления.

Создайте издателя

Когда ваше приложение впервые публикует элементы управления в системном пользовательском интерфейсе, оно не знает состояния каждого элемента управления. Получение состояния может быть трудоемкой операцией, включающей множество переходов в сети поставщика устройства. Используйте метод createPublisherForAllAvailable , чтобы объявить системе о доступных элементах управления. Этот метод использует класс-конструктор Control.StatelessBuilder , поскольку состояние каждого элемента управления неизвестно.

После того как элементы управления появятся в пользовательском интерфейсе Android, пользователи смогут выбрать понравившиеся элементы управления.

Чтобы использовать корутины Kotlin для создания ControlsProviderService , добавьте новую зависимость в ваш build.gradle :

Классный

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4"
}

Котлин

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4")
}

После синхронизации файлов Gradle добавьте следующий фрагмент кода в ваш Service , чтобы реализовать 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()
    }
}

Прокрутите меню системы вниз и найдите кнопку «Управление устройством» , показанную на рисунке 4:

Пользовательский интерфейс системы для управления устройством
Рисунок 4. Элементы управления устройством в системном меню.

Нажатие на « Элементы управления устройством» переводит вас на второй экран, где вы можете выбрать приложение. После выбора приложения вы увидите, как предыдущий фрагмент кода создает пользовательское системное меню, отображающее ваши новые элементы управления, как показано на рисунке 5:

В системном меню отображается управление освещением и термостатом.
Рисунок 5. Дополнительные элементы управления освещением и термостатом.

Теперь реализуйте метод createPublisherFor , добавив в ваш Service следующее:

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()
}

В этом примере метод createPublisherFor содержит фиктивную реализацию того, что должно делать ваше приложение: взаимодействовать с вашим устройством для получения его статуса и передавать этот статус в систему.

Метод createPublisherFor использует сопрограммы и потоки Kotlin для удовлетворения требований API Reactive Streams, выполняя следующие действия:

  1. Создает Flow .
  2. Ждет одну секунду.
  3. Создает и отображает состояние умного светильника.
  4. Ждет еще секунду.
  5. Создает и передает состояние термостата.

Обработка действий

Метод performControlAction сигнализирует о взаимодействии пользователя с опубликованным элементом управления. Тип отправленного ControlAction определяет действие. Выполните соответствующее действие для данного элемента управления, а затем обновите состояние устройства в пользовательском интерфейсе Android.

Для завершения примера добавьте в свой Service следующее:

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)
}

Запустите приложение, откройте меню «Управление устройством» и просмотрите элементы управления освещением и термостатом.

Элементы управления отображают информацию о освещении и термостате.
Рисунок 6. Элементы управления освещением и термостатом.