Điều khiển các thiết bị bên ngoài

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.

Không gian điều khiển thiết bị trong giao diện người dùng Android

Để 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ó:

Bật/tắt tiện ích
Bật/tắt
Bật/tắt bằng tiện ích thanh trượt
Bật/tắt bằng thanh trượt
Tiện ích phạm vi
Phạm vi (không thể bật hoặc tắt)
Bật/tắt tiện ích không trạng thái
Bật/tắt trạng thái
Tiện ích bảng nhiệt độ (đã đóng)
Bảng nhiệt độ (đã đóng)
Tiện ích bảng nhiệt độ (mở)
Bảng nhiệt độ (mở)

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:

  1. 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.
  2. 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.
  3. 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 ToggleTemplateRangeTemplate. 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 ControlsProviderServicec ó 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 Publisher cho tất cả các tùy chọn điều khiển có trong ứng dụng. Sử dụng Control.StatelessBuilder() để tạo Controls cho nhà xuất bản này.
  • createPublisherFor(): Tạo Publisher 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ụng Control.StatefulBuilder để tạo Controls 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 Publisher createPublisherForAllAvailable() {
        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: ReplayProcessor

    override 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 ReplayProcessor updatePublisher;

@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,
    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 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);
  }
}