В Android 11 и более поздних версиях функция быстрого доступа к элементам управления устройствами позволяет пользователю быстро просматривать и управлять внешними устройствами, такими как освещение, термостаты и камеры, с помощью пользовательского интерфейса, всего за три взаимодействия с помощью стандартного лаунчера. Производитель устройства выбирает используемый лаунчер. Агрегаторы устройств, например, Google Home, и сторонние приложения могут предоставлять устройства для отображения в этом пространстве. На этой странице показано, как отображать элементы управления устройствами в этом пространстве и связывать их с вашим приложением управления.
Чтобы добавить эту поддержку, создайте и объявите ControlsProviderService
. Создайте элементы управления, поддерживаемые вашим приложением, на основе предопределённых типов элементов управления, а затем создайте издателей для этих элементов управления.
Пользовательский интерфейс
Устройства отображаются в разделе «Управление устройствами» в виде шаблонных виджетов. Доступно пять виджетов управления устройствами, как показано на следующем рисунке:
![]() | ![]() | ![]() |
![]() | ![]() |
Прикосновение и удерживание виджета открывает приложение для более глубоких настроек. Вы можете настроить значок и цвет каждого виджета, но для лучшего пользовательского опыта используйте значок и цвет по умолчанию, если они соответствуют устройству.

Создать услугу
В этом разделе показано, как создать 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() { ... }
Ява
public class MyCustomJavaControlService extends 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 | Используется для обозначения элемента управления, поддерживающего сенсорное управление, состояние которого невозможно определить, например, ИК-пульта дистанционного управления телевизором. Этот шаблон можно использовать для определения процедуры или макроса, представляющего собой совокупность изменений элементов управления и их состояний. |
Используя эту информацию, вы можете создать элемент управления:
- Используйте класс-конструктор
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() { ... }
Ява
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; ... }
Создайте издателей для элементов управления
После создания элемента управления ему требуется издатель. Издатель сообщает системному пользовательскому интерфейсу о существовании элемента управления. Класс ControlsProviderService
имеет два метода издателя, которые необходимо переопределить в коде приложения:
-
createPublisherForAllAvailable()
: создаёт объектPublisher
для всех элементов управления, доступных в вашем приложении. ИспользуйтеControl.StatelessBuilder()
для создания объектовControl
для этого издателя. -
createPublisherFor()
: создаётPublisher
для списка заданных элементов управления, определяемых их строковыми идентификаторами. Для создания этих объектовControl
используйтеControl.StatefulBuilder
, поскольку издатель должен назначить состояние каждому элементу управления.
Создать издателя
Когда ваше приложение впервые публикует элементы управления в системном пользовательском интерфейсе, оно не знает состояние каждого элемента управления. Получение состояния может быть трудоёмкой операцией, требующей множества переходов в сети поставщика устройства. Используйте метод 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= 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() } }
Ява
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); } }
Проведите пальцем вниз по системному меню и найдите кнопку «Управление устройством» , показанную на рисунке 4:

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

Теперь реализуйте метод createPublisherFor()
, добавив в свою Service
следующее:
Котлин
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() }
Ява
@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(); }
В этом примере метод createPublisherFor()
содержит фиктивную реализацию того, что должно делать ваше приложение: взаимодействовать с вашим устройством для получения его статуса и передавать этот статус системе.
Метод createPublisherFor()
использует сопрограммы и потоки Kotlin для удовлетворения требуемого API Reactive Streams, выполняя следующие действия:
- Создает
Flow
. - Ждет одну секунду.
- Создает и излучает состояние интеллектуального света.
- Ждет еще секунду.
- Создает и выдает состояние термостата.
Действия по обработке
Метод performControlAction()
подаёт сигнал, когда пользователь взаимодействует с опубликованным элементом управления. Тип отправленного ControlAction
определяет действие. Выполняет соответствующее действие для данного элемента управления, а затем обновляет состояние устройства в пользовательском интерфейсе Android.
Чтобы завершить пример, добавьте в свою Service
следующее:
Котлин
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) }
Ява
@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()); } }
Запустите приложение, откройте меню «Управление устройством» и просмотрите элементы управления освещением и термостатом.
