ใน Android 11 ขึ้นไป ฟีเจอร์ระบบควบคุมอุปกรณ์ที่เข้าถึงด่วนช่วยให้ผู้ใช้ดูและควบคุมอุปกรณ์ภายนอก เช่น หลอดไฟ ตัวควบคุมอุณหภูมิ และกล้องได้อย่างรวดเร็วจากความสามารถของผู้ใช้ภายใน 3 การโต้ตอบจาก Launcher เริ่มต้น OEM ของอุปกรณ์จะเลือก Launcher ที่จะใช้ ผู้รวบรวมอุปกรณ์ เช่น Google Home และแอปของผู้ให้บริการบุคคลที่สามสามารถจัดหาอุปกรณ์เพื่อแสดงในพื้นที่นี้ได้ หน้านี้จะแสดงวิธีแสดงระบบควบคุมอุปกรณ์ในพื้นที่นี้และลิงก์กับแอปควบคุมของคุณ
หากต้องการเพิ่มการรองรับนี้ ให้สร้างและประกาศ ControlsProviderService สร้างตัวควบคุมที่แอปของคุณรองรับตามประเภทตัวควบคุมที่กำหนดไว้ล่วงหน้า แล้วสร้างผู้เผยแพร่สำหรับตัวควบคุมเหล่านี้
อินเทอร์เฟซผู้ใช้
อุปกรณ์จะแสดงในส่วนระบบควบคุมอุปกรณ์เป็นวิดเจ็ตที่มีเทมเพลต วิดเจ็ตระบบควบคุมอุปกรณ์มีให้เลือก 5 รายการ ดังที่แสดงในรูปภาพต่อไปนี้
|
|
|
|
|
การแตะวิดเจ็ตค้างไว้จะนำคุณไปยังแอปเพื่อการควบคุมที่ละเอียดยิ่งขึ้น คุณสามารถปรับแต่งไอคอนและสีในวิดเจ็ตแต่ละรายการได้ แต่เพื่อประสบการณ์ของผู้ใช้ที่ดีที่สุด ให้ใช้ไอคอนและสีเริ่มต้นหากชุดเริ่มต้นตรงกับอุปกรณ์
สร้างบริการ
ส่วนนี้จะแสดงวิธีสร้าง ControlsProviderService บริการนี้จะบอก UI ของระบบ Android ว่าแอปของคุณมีระบบควบคุมอุปกรณ์ที่ต้องแสดงในส่วนระบบควบคุมอุปกรณ์ของ UI ของ Android
ControlsProviderService API ถือว่าคุณคุ้นเคยกับสตรีมแบบรีแอ็กทีฟตามที่ระบุไว้ในโปรเจ็กต์ 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 เพื่อสร้างตัวควบคุม หากต้องการป้อนข้อมูลในเครื่องมือสร้าง ให้กำหนดอุปกรณ์ที่ต้องการควบคุมและวิธีที่ผู้ใช้โต้ตอบกับอุปกรณ์นั้น ทำตามขั้นตอนต่อไปนี้
- เลือกประเภทอุปกรณ์ที่ตัวควบคุมแสดง คลาส
DeviceTypesคือการแจกแจงอุปกรณ์ที่รองรับทั้งหมด ระบบจะใช้ประเภทเพื่อกำหนดไอคอนและสีสำหรับอุปกรณ์ใน UI - กำหนดชื่อที่แสดงต่อผู้ใช้ ตำแหน่งของอุปกรณ์ เช่น ห้องครัว และองค์ประกอบข้อความอื่นๆ ของ UI ที่เชื่อมโยงกับการควบคุม
- เลือกเทมเพลตที่ดีที่สุดเพื่อรองรับการโต้ตอบของผู้ใช้ ระบบจะกำหนด
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 เนื่องจากไม่ทราบสถานะของตัวควบคุมแต่ละรายการ
เมื่อตัวควบคุมปรากฏใน Android UI ผู้ใช้จะเลือกตัวควบคุมที่ชื่นชอบได้
หากต้องการใช้โครูทีน 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
การแตะการควบคุมอุปกรณ์จะนำทางไปยังหน้าจอที่ 2 ซึ่งคุณสามารถเลือกแอปได้ เมื่อเลือกแอปแล้ว คุณจะเห็นว่าข้อมูลโค้ดก่อนหน้านี้สร้างเมนูระบบที่กำหนดเองซึ่งแสดงการควบคุมใหม่ได้อย่างไร ดังที่แสดงในรูปที่ 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 เพื่อให้เป็นไปตาม Reactive Streams API ที่จำเป็นโดยทำดังนี้
- สร้าง
Flow - รอ 1 วินาที
- สร้างและแสดงสถานะของหลอดไฟอัจฉริยะ
- รออีก 1 วินาที
- สร้างและส่งสถานะของตัวควบคุมอุณหภูมิ
จัดการการดำเนินการ
เมธอด 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) }
เปิดแอป เข้าถึงเมนูระบบควบคุมอุปกรณ์ แล้วดูระบบควบคุมหลอดไฟและตัวควบคุมอุณหภูมิ