Trê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 nhanh chóng xem và điều khiển các thiết bị bên ngoài như đèn, máy điều nhiệt và camera từ một thuộc tính tương tác của người dùng trong vòng ba lượt tương tác từ một trình chạy mặc định. OEM của thiết bị sẽ chọn trình chạy mà họ sử dụng. Thiết bị đơn vị tập hợp (ví dụ: Google Home) và các ứ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. Trang này hướng dẫn bạn cách hiển thị quảng cáo các chế độ điều khiển thiết bị trong không gian này rồi liên kết chúng 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ế độ kiểm soát mà ứng dụng của bạn hỗ trợ dựa trên các loại chế độ điều khiển định sẵn, sau đó tạo
nhà xuất bản để áp dụng các chế độ kiểm soát 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 đã có các tiện ích điều khiển thiết bị, như minh hoạ trong hình sau:
|
|
|
|
|
Cảm xúc và khi giữ tiện ích, bạn sẽ được đưa đến ứng dụng để kiểm soát sâu hơn. Bạn có thể tuỳ 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, sử dụng biểu tượng và màu mặc định nếu bộ mặc định khớp với thiết bị.
Tạo dịch vụ
Phần này trình bày cách tạo
ControlsProviderService
.
Dịch vụ này cho giao diện người dùng hệ thống Android biết rằng ứng dụng của bạn chứa các chế độ điều khiển thiết bị
phải xuất hiện trong khu vực Điều khiển thiết bị trên giao diện người dùng Android.
API ControlsProviderService
giả định rằng bạn đã quen với luồng phản ứng, vì
được xác định trong Luồng phản ứng GitHub
dự án
và được triển khai trong Luồng Java 9
.
API này được xây dựng dựa trên các khái niệm sau:
- 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à có thể yêu cầu một số nhà xuất bản.
- Gói thuê bao: khung thời gian mà nhà xuất bản có thể gửi thông tin cập nhật vào Giao diện người dùng hệ thống. Nhà xuất bản hoặc người đăng ký có thể đóng thông báo này cửa sổ.
Khai báo dịch vụ
Ứng dụng của bạn phải khai báo một dịch vụ (chẳng hạn như MyCustomControlService
) trong
tệp kê khai ứng dụng.
Dịch vụ phải có một bộ lọc ý định cho ControlsProviderService
. Chiến dịch này
bộ lọc cho phép các ứng dụng đóng góp quyền kiểm soát cho giao diện người dùng hệ thống.
Bạn cũng cần có một label
hiển thị trong các thành phần điều khiển trên giao diện người dùng hệ thống.
Ví dụ sau đây trình bày cách khai báo một dịch vụ:
<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>
Tiếp theo, hãy tạo một tệp Kotlin mới có tên MyCustomControlService.kt
rồi đặt tệp này
mở rộng ControlsProviderService()
:
Kotlin
class MyCustomControlService : ControlsProviderService() { ... }
Java
public class MyCustomJavaControlService extends ControlsProviderService { ... }
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 sẵn tạo, xác định thiết bị bạn muốn điều khiển và cách người dùng tương tác với nó. Thực hiện các bước sau đây:
- Chọn loại thiết bị mà tùy chọn điều khiển đại diện. Chiến lược phát hành đĩa đơn
Lớp
DeviceTypes
là một liệt kê tất cả các thiết bị được hỗ trợ. Loại này được dùng để xác định 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í của 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 chế độ đ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ư phương thức nhập có sẵn – tức là phương thức nhậpControlAction
. Bảng sau đây trình bày một số mẫu có sẵn và các thao tác mà chúng 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 thông tin về chế độ kiểm soát, nhưng người dùng không thể tương tác với ứng dụng. |
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 một trường thay đổi
để thể hiện trạng thái mới được yêu cầu khi người dùng nhấn vào chế độ điều khiển.
|
RangeTemplate
|
FloatAction
|
Biểu thị một tiện ích thanh trượt với các giá trị tối thiểu, tối đa và bước được chỉ định. Thời gian
người dùng tương tác với thanh trượt, hãy gửi một FloatAction mới
quay lại ứng dụng với 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 sự kiện chạm cũng như thanh trượt.
chẳng hạn như điều khiển đèn có thể giảm độ sáng.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Ngoài việc đóng gói các hành động trước đó, mẫu này còn cho phép người dùng đặt chế độ (chẳng hạn như sưởi ấm, làm mát, sưởi ấm/làm mát, tiết kiệm năng lượng hoặc tắt). |
StatelessTemplate
|
CommandAction
|
Dùng để chỉ ra một chế độ điều khiển cung cấp chức năng cảm ứng nhưng có trạng thái không thể xác định được, chẳng hạn như điều khiển từ xa cho TV hồng ngoại. 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, bạn có thể tạo chế độ kiểm soát:
- 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.
Ví dụ: để điều khiển bóng đèn thông minh và máy điều nhiệt, hãy thêm thông tin sau
hằng số vào MyCustomControlService
của bạn:
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; ... }
Tạo trình xuất bản cho các chế độ kiểm soát
Sau khi bạn tạo chế độ kiểm soát, cần có 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ạo mộtPublisher
cho mọi chế độ điều khiển có trong ứng dụng của bạn. Sử dụngControl.StatelessBuilder()
để tạo đối tượngControl
cho nhà xuất bản này.createPublisherFor()
: tạo mộtPublisher
cho danh sách các chế độ điều khiển cụ thể, như được xác định bằng giá trị nhận dạng chuỗi của chúng. Sử dụngControl.StatefulBuilder
để tạo các đối tượngControl
này, vì nhà xuất bản phải chỉ định trạng thái cho từng chế độ điều khiển.
Tạo trình xuất bản
Khi ứng dụng của bạn xuất bản các chế độ điều khiển lên giao diện người dùng hệ thống lần đầu tiên, ứ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 trạng thái có thể là một thao tác 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. Phương thức này sử dụng
Lớp trình tạo Control.StatelessBuilder
, vì trạng thái của mỗi chế độ điều khiển là
không xác định.
Sau khi các nút đ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 mục yêu thích .
Để sử dụng coroutine của Kotlin cho việc tạo ControlsProviderService
, hãy thêm một thuộc tính mới
phần phụ thuộc vào build.gradle
của bạn:
Groovy
dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4" }
Kotlin
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4") }
Sau khi bạn đồng bộ hoá các tệp Gradle, hãy thêm đoạn mã sau vào Service
để
triển khai 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); } }
Vuốt trình đơn hệ thống xuống rồi tìm nút Device controls (Điều khiển thiết bị), xuất hiện trong hình 4:
Khi nhấn vào Device controls (Điều khiển thiết bị), bạn sẽ được chuyển đến màn hình thứ hai để chọn ứng dụng của bạn. Sau khi chọn ứng dụng, bạn sẽ thấy cách đoạn mã trước đó tạo trình đơn hệ thống tuỳ chỉnh hiển thị các chế độ điều khiển mới, như trong hình 5:
Bây giờ, hãy triển khai phương thức createPublisherFor()
, thêm đoạn mã sau vào
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(); }
Trong ví dụ này, phương thức createPublisherFor()
chứa giá trị giả mạo
và triển khai những việc ứng dụng của bạn phải làm: giao tiếp với thiết bị để
truy xuất trạng thái của ứng dụng và phát trạng thái đó đến hệ thống.
Phương thức createPublisherFor()
sử dụng coroutine và luồng Kotlin để đáp ứng
API Luồng phản ứng bắt buộc bằng cách làm như sau:
- Tạo
Flow
. - Đợi một giây.
- Tạo và phát ra trạng thái của đèn thông minh.
- Đợi một giây nữa.
- Tạo và phát ra trạng thái của máy điều nhiệt.
Xử lý thao tác
Phương thức performControlAction()
báo hiệu khi người dùng tương tác với một
bản kiểm soát được xuất bản. Loại ControlAction
được gửi sẽ xác định hành động.
Thực hiện hành động thích hợp cho chế độ điều khiển đã cho, sau đó cập nhật trạng thái
của thiết bị trong giao diện người dùng Android.
Để hoàn tất ví dụ này, hãy thêm đoạn mã sau vào 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()); } }
Chạy ứng dụng, truy cập trình đơn Device controls (Điều khiển thiết bị) để xem đèn và điều khiển máy điều nhiệt.