В Android 11 и более поздних версиях функция быстрого доступа к управлению устройствами позволяет пользователям быстро просматривать внешние устройства, такие как светильники, термостаты и камеры, и управлять ими с помощью пользовательского интерфейса в пределах трех взаимодействий из средства запуска по умолчанию. OEM-производитель устройства выбирает, какой лаунчер использовать. Агрегаторы устройств, например Google Home, и приложения сторонних поставщиков могут предоставлять устройства для показа в этом разделе. На этой странице рассказывается, как показывать элементы управления устройствами в этом разделе и связывать их с приложением для управления.
Чтобы добавить такую поддержку, создайте и объявите ControlsProviderService. Создайте элементы управления, которые поддерживает ваше приложение, на основе предопределенных типов элементов управления, а затем создайте издателей для этих элементов управления.
Пользовательский интерфейс
Устройства отображаются в разделе Управление устройствами в виде виджетов. Доступно пять виджетов управления устройствами, как показано на рисунке ниже.
|
|
|
|
|
Если нажать на виджет и удерживать, откроется приложение, в котором можно выполнить более точную настройку. Вы можете настроить значок и цвет для каждого виджета, но для удобства пользователей лучше использовать значок и цвет по умолчанию, если они соответствуют устройству.
Создайте сервис
В этом разделе рассказывается, как создать ControlsProviderService. Этот сервис сообщает системному интерфейсу Android, что ваше приложение содержит элементы управления устройствами, которые должны отображаться в разделе Управление устройствами.
Для работы с API ControlsProviderService необходимо иметь представление о реактивных потоках, описанных в проекте Reactive Streams на GitHub и реализованных в интерфейсах Java 9 Flow.
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 есть методы для создания элементов управления. Чтобы заполнить конструктор, определите, каким устройством вы хотите управлять и как пользователь взаимодействует с ним. Выполните следующие действия:
- Выберите тип устройства, которым управляет элемент. Класс
DeviceTypesпредставляет собой перечисление всех поддерживаемых устройств. Тип используется для определения значков и цветов устройства в интерфейсе. - Определите пользовательское название, местоположение устройства (например, кухня) и другие текстовые элементы интерфейса, связанные с элементом управления.
- Выберите шаблон, который лучше всего подходит для взаимодействия с пользователями. Элементам управления назначается
ControlTemplateиз приложения. Этот шаблон напрямую показывает пользователю состояние элемента управления, а также доступные методы ввода, то естьControlAction. В таблице ниже перечислены некоторые доступные шаблоны и действия, которые они поддерживают.
| Шаблон | Действие | Описание |
ControlTemplate.getNoTemplateObject()
|
None
|
Приложение может использовать его для передачи информации об элементе управления, но пользователь не может взаимодействовать с ним. |
ToggleTemplate
|
BooleanAction
|
Представляет элемент управления, который можно включать и отключать. Объект BooleanAction содержит поле, которое меняется, чтобы отразить новое состояние, когда пользователь нажимает на элемент управления.
|
RangeTemplate
|
FloatAction
|
Представляет виджет ползунка с указанными минимальным, максимальным и пошаговым значениями. Когда пользователь взаимодействует с ползунком, отправьте в приложение новый объект FloatAction с обновленным значением.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Этот шаблон представляет собой комбинацию ToggleTemplate и RangeTemplate. Поддерживает события касания и ползунок, например для управления яркостью света.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Помимо перечисленных выше действий, этот шаблон позволяет пользователю задать режим, например обогрев, охлаждение, обогрев/охлаждение, эко или выкл. |
StatelessTemplate
|
CommandAction
|
Используется для обозначения элемента управления, который поддерживает сенсорный ввод, но состояние которого определить невозможно, например инфракрасного пульта ДУ для телевизора. Этот шаблон можно использовать для определения программы или макроса, которые представляют собой совокупность изменений состояния и управления. |
Теперь вы можете создать элемент управления:
- Если состояние элемента управления неизвестно, используйте класс builder
Control.StatelessBuilder. - Если состояние элемента управления известно, используйте класс builder
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для этого издателя.createPublisherFor: создаетPublisherдля списка элементов управления, идентифицируемых по их строковым идентификаторам. Для создания объектовControlиспользуйтеControl.StatefulBuilder, поскольку издатель должен назначить состояние каждому элементу управления.
Создайте издателя
Когда приложение впервые публикует элементы управления в системном интерфейсе, оно не знает состояние каждого элемента управления. Получение состояния может занять много времени и потребовать нескольких переходов в сети поставщика устройства. Используйте метод createPublisherForAllAvailable, чтобы сообщить системе о доступных элементах управления. В этом методе используется класс builder Control.StatelessBuilder, поскольку состояние каждого элемента управления неизвестно.
Когда элементы управления появятся в интерфейсе Android, пользователи смогут выбрать избранные.
Чтобы использовать сопрограммы Kotlin для создания ControlsProviderService, добавьте новую зависимость в файл build.gradle:
Яркий
dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4" }
Kotlin
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.
Нажав Управление устройствами, вы перейдете на второй экран, где можно выбрать приложение. После этого вы увидите, как предыдущий фрагмент создает настраиваемое системное меню с новыми элементами управления, как показано на рисунке 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, чтобы обеспечить соответствие требованиям Reactive Streams API. Для этого он выполняет следующие действия:
- Создает
Flow. - Ожидание в течение одной секунды.
- Создает и передает состояние умного светильника.
- Подождите ещё секунду.
- Создает и передает состояние термостата.
Как обрабатывать действия
Метод 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) }
Запустите приложение, откройте меню Управление устройствами и посмотрите, как управлять светом и термостатом.