ควบคุมอุปกรณ์ภายนอก

ใน Android 11 ขึ้นไป ฟีเจอร์ระบบควบคุมอุปกรณ์สำหรับการเข้าถึงด่วน ช่วยให้ผู้ใช้ดูและควบคุมอุปกรณ์ภายนอก เช่น ไฟ ตัวควบคุมอุณหภูมิ และกล้องได้อย่างรวดเร็วจากข้อบ่งชี้การใช้งานของผู้ใช้ภายใน 3 การโต้ตอบจาก Launcher เริ่มต้น OEM ของอุปกรณ์จะเป็นผู้เลือกใช้ Launcher ตัวรวบรวมอุปกรณ์ เช่น Google Home และแอปของผู้ให้บริการบุคคลที่สามสามารถ แสดงอุปกรณ์ในพื้นที่นี้ได้ หน้านี้แสดงวิธีแสดงตัวควบคุมอุปกรณ์ในพื้นที่นี้และลิงก์ตัวควบคุมกับแอปควบคุม

รูปที่ 1 พื้นที่ควบคุมอุปกรณ์ใน UI ของ Android

หากต้องการเพิ่มการรองรับนี้ ให้สร้างและประกาศ ControlsProviderService สร้าง การควบคุมที่แอปของคุณรองรับตามประเภทการควบคุมที่กำหนดไว้ล่วงหน้า จากนั้นสร้าง ผู้เผยแพร่สำหรับการควบคุมเหล่านี้

อินเทอร์เฟซผู้ใช้

อุปกรณ์จะแสดงในส่วนระบบควบคุมอุปกรณ์เป็นวิดเจ็ตที่สร้างจากเทมเพลต วิดเจ็ตควบคุมอุปกรณ์ 5 รายการพร้อมใช้งานดังที่แสดงในรูปต่อไปนี้

สลับวิดเจ็ตสำหรับระบบควบคุมอุปกรณ์
เปิด/ปิด
เปิด/ปิดด้วยวิดเจ็ตแถบเลื่อน
สลับด้วยแถบเลื่อน
วิดเจ็ตแถบเลื่อนช่วงสำหรับการควบคุมอุปกรณ์
ช่วง (เปิดหรือปิดไม่ได้)
วิดเจ็ตควบคุมการเปิด/ปิดแบบไม่เก็บสถานะ
สวิตช์แบบไม่เก็บสถานะ
วิดเจ็ตแผงอุณหภูมิในสถานะปิด
แผงอุณหภูมิ (ปิด)
รูปที่ 2 คอลเล็กชันวิดเจ็ตที่ใช้เทมเพลต

การแตะวิดเจ็ตค้างไว้จะนำคุณไปยังแอปเพื่อให้ควบคุมได้ละเอียดยิ่งขึ้น คุณปรับแต่งไอคอนและสีในวิดเจ็ตแต่ละรายการได้ แต่เพื่อประสบการณ์ของผู้ใช้ที่ดีที่สุด ให้ใช้ไอคอนและสีเริ่มต้นหากชุดเริ่มต้นตรงกับอุปกรณ์

วิดเจ็ตแผงอุณหภูมิในสถานะเปิด
รูปที่ 3 เปิดวิดเจ็ตแผงอุณหภูมิ

สร้างบริการ

ส่วนนี้แสดงวิธีสร้าง ControlsProviderService บริการนี้จะแจ้งให้ UI ของระบบ Android ทราบว่าแอปของคุณมีตัวควบคุมอุปกรณ์ที่ต้องแสดงในส่วนตัวควบคุมอุปกรณ์ของ UI ของ Android

ControlsProviderService API ถือว่าผู้ใช้คุ้นเคยกับสตรีมแบบรีแอกทีฟตามที่กำหนดไว้ในโปรเจ็กต์ Reactive Streams GitHub และนำไปใช้ในอินเทอร์เฟซ Java 9 Flow API สร้างขึ้นจากแนวคิดต่อไปนี้

  • ผู้เผยแพร่: แอปพลิเคชันของคุณคือผู้เผยแพร่
  • ผู้ติดตาม: UI ของระบบคือผู้ติดตามและสามารถขอตัวควบคุมจำนวนหนึ่งจากผู้เผยแพร่โฆษณาได้
  • การสมัครใช้บริการ: กรอบเวลาที่ผู้เผยแพร่เนื้อหาสามารถส่งการอัปเดตไปยัง UI ของระบบได้ ทั้งผู้เผยแพร่โฆษณาและผู้ติดตามสามารถปิดหน้าต่างนี้ได้

ประกาศบริการ

แอปของคุณต้องประกาศบริการ เช่น MyCustomControlService ใน ไฟล์ Manifest ของแอป

บริการต้องมีตัวกรอง Intent สำหรับ ControlsProviderService ฟิลเตอร์นี้ช่วยให้แอปพลิเคชันมีส่วนร่วมในการควบคุม UI ของระบบ

นอกจากนี้ คุณยังต้องมี label ที่แสดงในการควบคุมใน UI ของระบบด้วย

ตัวอย่างต่อไปนี้แสดงวิธีกำหนดบริการ

<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 มีเมธอด Builder สำหรับสร้างตัวควบคุม หากต้องการป้อนข้อมูลใน เครื่องมือสร้าง ให้ระบุอุปกรณ์ที่ต้องการควบคุมและวิธีที่ผู้ใช้โต้ตอบ กับอุปกรณ์ ทำตามขั้นตอนต่อไปนี้

  1. เลือกประเภทอุปกรณ์ที่ตัวควบคุมแสดง คลาส DeviceTypes คือการแจงนับอุปกรณ์ทั้งหมดที่รองรับ ระบบจะใช้ประเภทนี้เพื่อกำหนดไอคอนและสีสำหรับอุปกรณ์ ใน UI
  2. กำหนดชื่อที่แสดงต่อผู้ใช้ ตำแหน่งอุปกรณ์ เช่น ห้องครัว และองค์ประกอบข้อความอื่นๆ ของ UI ที่เชื่อมโยงกับการควบคุม
  3. เลือกเทมเพลตที่ดีที่สุดเพื่อรองรับการโต้ตอบของผู้ใช้ ระบบจะกำหนดControlTemplateให้กับตัวควบคุมจากแอปพลิเคชัน เทมเพลตนี้ จะแสดงสถานะการควบคุมต่อผู้ใช้โดยตรง รวมถึงวิธีการป้อนข้อมูลที่ใช้ได้ ซึ่งก็คือ ControlAction ตารางต่อไปนี้แสดงเทมเพลตบางส่วนที่พร้อมใช้งานและการดำเนินการที่เทมเพลตรองรับ
เทมเพลต การดำเนินการ คำอธิบาย
ControlTemplate.getNoTemplateObject() None แอปพลิเคชันอาจใช้ข้อความนี้เพื่อสื่อสารข้อมูลเกี่ยวกับการควบคุม แต่ผู้ใช้จะโต้ตอบกับข้อความนี้ไม่ได้
ToggleTemplate BooleanAction แสดงถึงตัวควบคุมที่สามารถสลับระหว่างสถานะเปิดใช้และปิดใช้ได้ ออบเจ็กต์ BooleanAction มีฟิลด์ที่เปลี่ยนแปลง เพื่อแสดงสถานะใหม่ที่ขอเมื่อผู้ใช้แตะตัวควบคุม
RangeTemplate FloatAction แสดงวิดเจ็ตแถบเลื่อนที่มีค่าต่ำสุด สูงสุด และค่าขั้นที่ระบุ เมื่อ ผู้ใช้โต้ตอบกับแถบเลื่อน ให้ส่งออบเจ็กต์ FloatAction ใหม่กลับไปยังแอปพลิเคชันพร้อมค่าที่อัปเดต
ToggleRangeTemplate BooleanAction, FloatAction เทมเพลตนี้เป็นการผสมผสานระหว่าง ToggleTemplate และ RangeTemplate โดยรองรับทั้งเหตุการณ์การแตะและแถบเลื่อน เช่น เพื่อควบคุมหลอดไฟที่หรี่แสงได้
TemperatureControlTemplate ModeAction, BooleanAction, FloatAction นอกจากจะห่อหุ้มการดำเนินการก่อนหน้าแล้ว เทมเพลตนี้ยังช่วยให้ ผู้ใช้ตั้งค่าโหมดต่างๆ ได้ เช่น ทำความร้อน ทำความเย็น ทำความร้อน/ทำความเย็น อีโค หรือปิด
StatelessTemplate CommandAction ใช้เพื่อระบุตัวควบคุมที่ให้ความสามารถในการสัมผัส แต่ไม่สามารถระบุสถานะได้ เช่น รีโมตทีวี IR คุณสามารถใช้ เทมเพลตนี้เพื่อกำหนดกิจวัตรหรือมาโคร ซึ่งเป็นการรวบรวมการควบคุม และการเปลี่ยนแปลงสถานะ

คุณสามารถใช้ข้อมูลนี้เพื่อสร้างการควบคุมได้ดังนี้

  • ใช้คลาสบิลเดอร์ 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() {
    // ...
}

สร้างผู้เผยแพร่โฆษณาสำหรับการควบคุม

หลังจากสร้างตัวควบคุมแล้ว คุณต้องกำหนดผู้เผยแพร่ ผู้เผยแพร่โฆษณาจะแจ้งให้ UI ของระบบทราบว่ามีการควบคุม คลาส ControlsProviderService มีเมธอดของผู้เผยแพร่โฆษณา 2 รายการที่คุณต้องลบล้างในโค้ดแอปพลิเคชัน

  • createPublisherForAllAvailable: สร้าง Publisher สำหรับ การควบคุมทั้งหมดที่มีในแอป ใช้ Control.StatelessBuilder เพื่อสร้างออบเจ็กต์ Control สำหรับผู้เผยแพร่โฆษณารายนี้
  • createPublisherFor: สร้าง Publisher สำหรับรายการการควบคุมที่ระบุ ตามตัวระบุสตริง ใช้ Control.StatefulBuilder เพื่อ สร้างออบเจ็กต์ Control เหล่านี้ เนื่องจากผู้เผยแพร่โฆษณาต้องกำหนดสถานะให้กับ ตัวควบคุมแต่ละรายการ

สร้างผู้เผยแพร่

เมื่อแอปเผยแพร่ตัวควบคุมไปยัง UI ของระบบเป็นครั้งแรก แอปจะไม่ทราบ สถานะของตัวควบคุมแต่ละรายการ การรับสถานะอาจเป็นการดำเนินการที่ใช้เวลานาน ซึ่งเกี่ยวข้องกับการข้ามหลายครั้งในเครือข่ายของผู้ให้บริการอุปกรณ์ ใช้วิธีการ createPublisherForAllAvailable เพื่อ โฆษณาตัวควบคุมที่พร้อมใช้งานในระบบ วิธีนี้ใช้คลาสบิลเดอร์ Control.StatelessBuilder เนื่องจากสถานะของตัวควบคุมแต่ละรายการ ไม่ทราบ

เมื่อการควบคุมปรากฏใน UI ของ Android แล้ว ผู้ใช้จะเลือกการควบคุมที่ชื่นชอบได้

หากต้องการใช้ Kotlin โครูทีน เพื่อสร้าง ControlsProviderService ให้เพิ่มทรัพยากร Dependency ใหม่ ลงใน 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

อินเทอร์เฟซผู้ใช้ของระบบสำหรับการควบคุมอุปกรณ์
รูปที่ 4 ระบบควบคุมอุปกรณ์ในเมนูระบบ

การแตะการควบคุมอุปกรณ์จะนำคุณไปยังหน้าจอที่ 2 ซึ่งคุณสามารถเลือก แอปได้ เมื่อเลือกแอปแล้ว คุณจะเห็นว่าข้อมูลโค้ดก่อนหน้าสร้าง เมนูระบบที่กำหนดเองซึ่งแสดงการควบคุมใหม่ของคุณได้อย่างไร ดังที่แสดงในรูปที่ 5

เมนูระบบแสดงการควบคุมไฟและตัวควบคุมอุณหภูมิ
รูปที่ 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 เพื่อให้เป็นไปตาม API ของ Reactive Streams ที่จำเป็นโดยทำดังนี้

  1. สร้าง Flow
  2. รอ 1 วินาที
  3. สร้างและส่งสถานะของหลอดไฟอัจฉริยะ
  4. รออีก 1 วินาที
  5. สร้างและส่งสถานะของตัวควบคุมอุณหภูมิ

จัดการการดำเนินการ

performControlAction วิธีนี้จะส่งสัญญาณเมื่อผู้ใช้โต้ตอบกับตัวควบคุมที่เผยแพร่ ประเภทของ ControlAction ที่ส่งจะเป็นตัวกำหนดการดำเนินการ ดำเนินการที่เหมาะสมกับตัวควบคุมที่ระบุ จากนั้นอัปเดตสถานะ ของอุปกรณ์ใน UI ของ 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)
}

เรียกใช้แอป เข้าถึงเมนูการควบคุมอุปกรณ์ แล้วดูการควบคุมหลอดไฟและ เทอร์โมสตัท

ควบคุมการแสดงหลอดไฟและตัวควบคุมอุณหภูมิ
รูปที่ 6 การควบคุมไฟและตัวควบคุมอุณหภูมิ