Trong phiên bản Android 11 trở lên, tính năng Điều khiển thiết bị truy cập nhanh cho phép người dùng xem và điều khiển nhanh các thiết bị bên ngoài như đèn, bộ điều nhiệt và máy ảnh tùy khả năng chi trả của người dùng trong 3 lượt tương tác từ trình khởi chạy mặc định. (Nhà sản xuất thiết bị gốc sẽ chọn trình chạy nào được sử dụng.) Trang web tổng hợp thiết bị (chẳng hạn như Google Home) và ứng dụng của nhà cung cấp bên thứ ba có thể cung cấp thiết bị hiển thị trong không gian này. Tài liệu này sẽ hướng dẫn bạn cách hiển thị các nút điều khiển thiết bị trong không gian này và liên kết các nút điều khiển đó với ứng dụng điều khiển.
Để thêm tính năng hỗ trợ này, hãy tạo và khai báo ControlsProviderService
, tạo các chế độ điều khiển mà ứng dụng hỗ trợ dựa trên chế độ điều khiển định sẵn, sau đó tạo kênh xuất bản cho các chế độ điều khiển này.
Giao diện người dùng
Các thiết bị hiển thị trong phần điều khiển thiết bị dưới dạng tiện ích mẫu. Năm tiện ích điều khiển thiết bị khác nhau hiện có:
![]() |
![]() |
![]() |
![]() |
![]() |

Nhấn và giữ một tiện ích đưa bạn đến ứng dụng để kiểm soát sâu hơn. Có thể tùy chỉnh biểu tượng và màu sắc trên từng tiện ích, nhưng để có trải nghiệm người dùng tốt nhất, bạn nên sử dụng màu và biểu tượng mặc định trừ khi bộ mặc định không khớp với thiết bị.
Tạo dịch vụ
Phần này cho biết cách tạo ControlsProviderService
.
Dịch vụ này báo cho giao diện người dùng hệ thống Android biết ứng dụng của bạn chứa các tùy chọn điều khiển thiết bị sẽ hiển thị trong khu vực Điều khiển thiết bị của giao diện người dùng Android.
TrangControlsProviderService
API giả định là đã làm quen với luồng phản hồi, như được xác định ở Dự án GitHub của Reactive Stream và triển khai trong Giao diện Luồng của Java 9 ,
API được xây dựng dựa trên các khái niệm này:
- Nhà xuất bản: Ứng dụng của bạn là nhà xuất bản
- Người đăng ký: Giao diện người dùng hệ thống là người đăng ký và họ có thể yêu cầu nhà xuất bản cung cấp một mã điều khiển
- Gói đăng ký: Khoảng thời gian mà nhà xuất bản gửi các bản cập nhật cho giao diện người dùng của Hệ thống; nhà xuất bản hoặc người đăng ký có thể đóng cửa sổ này
Khai báo dịch vụ
Ứng dụng phải khai báo một dịch vụ trong tệp kê khai ứng dụng. Nhớ cung cấp quyền BIND_CONTROLS
.
Dịch vụ phải có một bộ lọc ý định cho ControlsProviderService
. Bộ lọc này cho phép ứng dụng đóng góp quyền điều khiển cho giao diện người dùng hệ thống.
<!-- New signature permission to ensure only systemui can bind to these services -->
<service android:name="YOUR-SERVICE-NAME" android:label="YOUR-SERVICE-LABEL"
android:permission="android.permission.BIND_CONTROLS">
<intent-filter>
<action android:name="android.service.controls.ControlsProviderService" />
</intent-filter>
</service>
Chọn loại điều khiển chính xác
API này cung cấp phương thức trình tạo để tạo các tùy chọn điều khiển. Để điền trình tạo, bạn cần xác định thiết bị mình muốn điều khiển và cách người dùng tương tác với thiết bị đó. Cụ thể, bạn cần làm như sau:
- Chọn loại thiết bị mà tùy chọn điều khiển đại diện. Lớp
DeviceTypes
là bảng liệt kê tất cả các thiết bị hiện được hỗ trợ. Loại này dùng để xác định biểu tượng và màu sắc cho thiết bị trong giao diện người dùng. - Xác định tên mà người dùng nhìn thấy, vị trí thiết bị (ví dụ: nhà bếp) và các thành phần văn bản khác trên giao diện người dùng liên quan đến tùy chọn điều khiển này.
- Chọn mẫu tốt nhất để hỗ trợ tương tác cho người dùng. Các tùy chọn điều khiển đã gán
ControlTemplate
từ ứng dụng. Mẫu này trực tiếp hiển thị trạng thái điều khiển cho người dùng cũng như các phương thức nhập có sẵn (nghĩa làControlAction
). Bảng sau trình bày một số mẫu có sẵn và thao tác các mẫu đó hỗ trợ:
Mẫu | Hành động | Mô tả |
ControlTemplate.getNoTemplateObject()
|
None
|
Ứng dụng có thể sử dụng thông tin này để truyền đạt biện pháp điều khiển nhưng người dùng không thể tương tác với nó. |
ToggleTemplate
|
BooleanAction
|
Biểu thị một nút điều khiển có thể chuyển đổi giữa trạng thái đang bật và đã tắt. Đối tượng BooleanAction chứa trường thay đổi để biểu thị trạng thái mới được yêu cầu khi người dùng chạm vào tùy chọn điều khiển.
|
RangeTemplate
|
FloatAction
|
Biểu thị tiện ích thanh trượt có các giá trị tối thiểu, tối đa và bước được chỉ định. Khi người dùng tương tác với thanh trượt, đối tượng FloatAction mới sẽ được gửi lại ứng dụng này kèm theo giá trị cập nhật.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Mẫu này là kết hợp giữa ToggleTemplate và RangeTemplate . Tính năng này hỗ trợ các thao tác chạm cũng như thanh trượt, ví dụ như thanh trượt có thể điều chỉnh độ sáng của đèn.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Ngoài việc tổng hợp một trong các thao tác trên, mẫu này còn cho phép người dùng đặt chế độ như sưởi ấm, làm mát, sưởi ấm/làm mát, thân thiện với môi trường hoặc tắt. |
StatelessTemplate
|
CommandAction
|
Dùng để chỉ ra một chức năng điều khiển có cảm ứng nhưng không thể xác định trạng thái của chức năng đó, chẳng hạn như TV hồng ngoại điều khiển từ xa. Bạn có thể sử dụng mẫu này để xác định một quy trình hoặc macro, tổng hợp các tùy chọn điều khiển và thay đổi trạng thái. |
Với thông tin này, giờ bạn đã có thể tạo tùy chọn điều khiển:
- Sử dụng lớp trình tạo
Control.StatelessBuilder
khi không xác định được trạng thái của tùy chọn điều khiển này. - Sử dụng lớp trình tạo
Control.StatefulBuilder
khi xác định được trạng thái của tùy chọn điều khiển này.
Tạo nhà xuất bản cho các chế độ điều khiển
Sau khi được tạo, tùy chọn điều khiển này cần một nhà xuất bản. Nhà xuất bản sẽ thông báo cho giao diện người dùng hệ thống về tính khả dụng của tùy chọn điều khiển. Lớp ControlsProviderService
c ó hai phương thức mà nhà xuất bản phải viết hàm đè trong mã ứng dụng:
createPublisherForAllAvailable()
: TạoPublisher
cho tất cả các tùy chọn điều khiển có trong ứng dụng. Sử dụngControl.StatelessBuilder()
để tạoControls
cho nhà xuất bản này.createPublisherFor()
: TạoPublisher
cho danh sách các tùy chọn điều khiển nhất định, như được xác định theo giá trị nhận dạng chuỗi của các tùy chọn đó. Sử dụngControl.StatefulBuilder
để tạoControls
vì nhà xuất bản cần chỉ định một trạng thái cho mỗi tùy chọn điều khiển.
Tạo nhà xuất bản
Khi ứng dụng phát hành chế độ điều khiển trên giao diện người dùng của hệ thống lần đầu, ứng dụng sẽ không biết trạng thái của từng chế độ điều khiển. Việc nhận biết trạng thái có thể là một hoạt động tốn thời gian liên quan đến nhiều bước trong mạng của nhà cung cấp thiết bị. Sử dụng phương thức createPublisherForAllAvailable()
để quảng cáo các tùy chọn điều khiển có sẵn cho hệ thống. Lưu ý phương thức này sử dụng lớp trình tạo Control.StatelessBuilder
vì không xác định được trạng thái của mỗi tùy chọn điều khiển.
Sau khi các tùy chọn điều khiển xuất hiện trong giao diện người dùng Android , người dùng có thể chọn (tức là, chọn mục yêu thích) mà họ quan tâm.
Kotlin
/* If you choose to use Reactive Streams API, you will need to put the following * into your module's build.gradle file: * implementation 'org.reactivestreams:reactive-streams:1.0.3' * implementation 'io.reactivex.rxjava2:rxjava:2.2.0' */ class MyCustomControlService : ControlsProviderService() { override fun createPublisherForAllAvailable(): Flow.Publisher{ val context: Context = baseContext val i = Intent() val pi = PendingIntent.getActivity( context, CONTROL_REQUEST_CODE, i, PendingIntent.FLAG_UPDATE_CURRENT ) val controls = mutableListOf () val control = Control.StatelessBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT .build() controls.add(control) // Create more controls here if needed and add it to the ArrayList // Uses the RxJava 2 library return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls)) } }
Java
/* If you choose to use Reactive Streams API, you will need to put the following * into your module's build.gradle file: * implementation 'org.reactivestreams:reactive-streams:1.0.3' * implementation 'io.reactivex.rxjava2:rxjava:2.2.0' */ public class MyCustomControlService extends ControlsProviderService { @Override public PublishercreatePublisherForAllAvailable() { Context context = getBaseContext(); Intent i = new Intent(); PendingIntent pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); List controls = new ArrayList<>(); Control control = new Control.StatelessBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT .build(); controls.add(control); // Create more controls here if needed and add it to the ArrayList // Uses the RxJava 2 library return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls)); } }
Khi người dùng đã chọn một tập hợp các tùy chọn điều khiển, hãy tạo nhà xuất bản cho các tùy chọn điều khiển đó. Sử dụng phương thức createPublisherFor()
vì phương thức này sử dụng lớp trình tạo Control.StatefulBuilder
cung cấp trạng thái hiện tại của từng tùy chọn điều khiển (ví dụ: bật hoặc tắt).
Kotlin
class MyCustomControlService : ControlsProviderService() { private lateinit var updatePublisher: ReplayProcessoroverride fun createPublisherFor(controlIds: MutableList ): Flow.Publisher { val context: Context = baseContext /* Fill in details for the activity related to this device. On long press, * this Intent will be launched in a bottomsheet. Please design the activity * accordingly to fit a more limited space (about 2/3 screen height). */ val i = Intent(this, CustomSettingsActivity::class.java) val pi = PendingIntent.getActivity(context, CONTROL_REQUEST_CODE, i, PendingIntent.FLAG_UPDATE_CURRENT) updatePublisher = ReplayProcessor.create() if (controlIds.contains(MY-UNIQUE-DEVICE-ID)) { val control = Control.StatefulBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY -CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT // Required: Current status of the device .setStatus(Control.CURRENT-STATUS) // For example, Control.STATUS_OK .build() updatePublisher.onNext(control) } // If you have other controls, check that they have been selected here // Uses the Reactive Streams API updatePublisher.onNext(control) } }
Java
private ReplayProcessorupdatePublisher; @Override public Publisher createPublisherFor(List controlIds) { Context context = getBaseContext(); /* Fill in details for the activity related to this device. On long press, * this Intent will be launched in a bottomsheet. Please design the activity * accordingly to fit a more limited space (about 2/3 screen height). */ Intent i = new Intent(); PendingIntent pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); updatePublisher = ReplayProcessor.create(); // For each controlId in controlIds if (controlIds.contains(MY-UNIQUE-DEVICE-ID)) { Control control = new Control.StatefulBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT // Required: Current status of the device .setStatus(Control.CURRENT-STATUS) // For example, Control.STATUS_OK .build(); updatePublisher.onNext(control); } // Uses the Reactive Streams API return FlowAdapters.toFlowPublisher(updatePublisher); }
Xử lý thao tác
Phương thức performControlAction()
cho biết người dùng đã tương tác với một tùy chọn điều khiển đã xuất bản. Thao tác được xác định bằng loại ControlAction
đã được gửi. Thực hiện thao tác thích hợp cho chức năng điều khiển đã định, sau đó cập nhật trạng thái của thiết bị trong giao diện người dùng Android.
Kotlin
class MyCustomControlService : ControlsProviderService() { override fun performControlAction( controlId: String, action: ControlAction, consumer: Consumer) { /* First, locate the control identified by the controlId. Once it is located, you can * interpret the action appropriately for that specific device. For instance, the following * assumes that the controlId is associated with a light, and the light can be turned on * or off. */ if (action is BooleanAction) { // Inform SystemUI that the action has been received and is being processed consumer.accept(ControlAction.RESPONSE_OK) // In this example, action.getNewState() will have the requested action: true for “On”, // false for “Off”. /* This is where application logic/network requests would be invoked to update the state of * the device. * After updating, the application should use the publisher to update SystemUI with the new * state. */ Control control = Control.StatefulBuilder (MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT // Required: Current status of the device .setStatus(Control.CURRENT-STATUS) // For example, Control.STATUS_OK .build() // This is the publisher the application created during the call to createPublisherFor() updatePublisher.onNext(control) } } }
Java
@Override public void performControlAction(String controlId, ControlAction action, Consumerconsumer) { /* First, locate the control identified by the controlId. Once it is located, you can * interpret the action appropriately for that specific device. For instance, the following * assumes that the controlId is associated with a light, and the light can be turned on * or off. */ if (action instanceof BooleanAction) { // Inform SystemUI that the action has been received and is being processed consumer.accept(ControlAction.RESPONSE_OK); BooleanAction action = (BooleanAction) action; // In this example, action.getNewState() will have the requested action: true for “On”, // false for “Off”. /* This is where application logic/network requests would be invoked to update the state of * the device. * After updating, the application should use the publisher to update SystemUI with the new * state. */ Control control = new Control.StatefulBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT // Required: Current status of the device .setStatus(Control.CURRENT-STATUS) // For example, Control.STATUS_OK .build(); // This is the publisher the application created during the call to createPublisherFor() updatePublisher.onNext(control); } }