در Android نسخه ۱۱ و بالاتر، ویژگی «کنترلهای دستگاه با دسترسی سریع» به کاربران امکان میدهد دستگاههای خارجی مانند چراغها، دماپاها، و دوربینها را بهسرعت ازطریق یک امکان کاربر در سه تعامل از یک راهانداز پیشفرض مشاهده و کنترل کنند. سازنده اصلی دستگاه انتخاب میکند که از کدام راهانداز استفاده شود. تجمیعکنندههای دستگاه (برای مثال Google Home) و برنامههای فروشنده طرف سوم میتوانند دستگاههایی را برای نمایش در این فضا ارائه دهند. این صفحه به شما نشان میدهد چگونه کنترلهای دستگاه را در این فضا نمایش دهید و آنها را به برنامه کنترل خود پیوند دهید.
برای افزودن این پشتیبانی، ControlsProviderService ایجاد و اعلام کنید. کنترلهایی را که برنامهتان پشتیبانی میکند براساس انواع کنترل ازپیش تعریفشده ایجاد کنید، سپس برای این کنترلها ناشر ایجاد کنید.
رابط کاربری
دستگاهها در بخش کنترلهای دستگاه بهصورت ابزارههای الگو نمایش داده میشوند. پنج ابزاره کنترل دستگاه دردسترس است، همانطور که در شکل زیر نشان داده شده است:
|
|
|
|
|
با لمس کردن و نگه داشتن ابزاره، برای کنترل بیشتر به برنامه میروید. میتوانید نماد و رنگ هر ابزاره را سفارشیسازی کنید، اما برای بهترین تجربه کاربری، اگر مجموعه پیشفرض با دستگاه مطابقت دارد، از نماد و رنگ پیشفرض استفاده کنید.
ایجاد سرویس
این بخش نحوه ایجاد
ControlsProviderService را نشان میدهد. این سرویس به
واسط کاربر سیستم Android میگوید که برنامه شما حاوی کنترلهای دستگاهی است که باید
در بخش کنترلهای دستگاه واسط کاربر Android نمایش داده شود.
ControlsProviderService API assumes familiarity with reactive streams, as
defined in the Reactive Streams GitHub project
and implemented in the Java 9 Flow interfaces.
این API براساس مفاهیم زیر ساخته شده است:
- ناشر: برنامه شما ناشر است.
- مشترک: واسط کاربر سیستم مشترک است و میتواند تعدادی کنترل از ناشر درخواست کند.
- اشتراک: چارچوب زمانیای که طی آن ناشر میتواند بهروزرسانیها را به «میانای کاربر سیستم» ارسال کند. ناشر یا مشترک میتواند این پنجره را ببندد.
اعلام سرویس
برنامه شما باید سرویسی (مانند MyCustomControlService) را در مانیفست برنامهاش اعلام کند.
سرویس باید شامل فیلتر قصد برای ControlsProviderService باشد. این
فیلتر به برنامهها اجازه میدهد کنترلهایی را به میانای کاربر سیستم اضافه کنند.
همچنین به label نیاز دارید که در کنترلهای واسط کاربر سیستم نمایش داده میشود.
مثال زیر نحوه اعلام سرویس را نشان میدهد:
<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() { // ... }
نوع کنترل صحیح را انتخاب کنید
این میانای برنامهسازی کاربردی روشهای سازندهای برای ایجاد کنترلها ارائه میدهد. برای پر کردن سازنده، دستگاهی را که میخواهید کنترل کنید و نحوه تعامل کاربر با آن را تعیین کنید. مراحل زیر را انجام دهید:
- نوع دستگاهی را که کنترل نشان میدهد انتخاب کنید. کلاس
DeviceTypesشمارشی از همه دستگاههای پشتیبانیشده است. از نوع برای تعیین نمادها و رنگهای دستگاه در میانای کاربر استفاده میشود. - نام سَمت کاربر، مکان دستگاه (مثلاً آشپزخانه)، و دیگر عناصر نوشتاری میانای کاربر مرتبط با کنترل را تعیین کنید.
- بهترین الگو را برای پشتیبانی از تعامل کاربر انتخاب کنید. کنترلها به
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() { // ... }
ایجاد ناشران برای کنترلها
پساز ایجاد کنترل، به ناشر نیاز دارد. ناشر وجود کنترل را به واسط کاربر سیستم اطلاع میدهد. کلاس ControlsProviderService
دو روش ناشر دارد که باید در کد برنامهتان لغو کنید:
createPublisherForAllAvailable:Publisherبرای همه کنترلهای دردسترس در برنامهتان ایجاد میکند. ازControl.StatelessBuilderبرای ساختن اشیایControlبرای این ناشر استفاده کنید.createPublisherFor:Publisherرا برای فهرستی از کنترلهای ارائهشده ایجاد میکند، که با شناسههای رشتهایشان شناسایی میشوند. ازControl.StatefulBuilderبرای ساختن این اشیایControlاستفاده کنید، زیرا ناشر باید برای هر کنترل یک وضعیت اختصاص دهد.
ایجاد ناشر
وقتی برنامهتان برای اولینبار کنترلها را در میانای کاربری سیستم منتشر میکند، برنامه از
وضعیت هر کنترل مطلع نیست. دریافت وضعیت میتواند عملیاتی زمانبر باشد
که شامل پرشهای زیادی در شبکه ارائهدهنده دستگاه است. از روش
createPublisherForAllAvailable برای
تبلیغ کنترلهای دردسترس برای سیستم استفاده کنید. این روش از کلاس سازنده
Control.StatelessBuilder استفاده میکند، زیرا وضعیت هر کنترل
نامشخص است.
وقتی کنترلها در میانای کاربری Android ظاهر شد، کاربران میتوانند کنترلهای دلخواه را انتخاب کنند.
برای استفاده از روالهای مشترک Kotlin جهت ایجاد ControlsProviderService، وابستگی جدیدی به build.gradle اضافه کنید:
شیک
dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4" }
کاتلین
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() } }
منو سیستم را تند بهپایین بکشید و دکمه کنترلهای دستگاه را که در شکل ۴ نشان داده شده است پیدا کنید:
با تکضرب زدن روی کنترلهای دستگاه، به صفحه دوم پیمایش میکنید که در آنجا میتوانید برنامهتان را انتخاب کنید. پساز انتخاب برنامهتان، میبینید که قطعه کد قبلی چگونه منو سیستم سفارشیای ایجاد میکند که کنترلهای جدیدتان را نشان میدهد، همانطور که در شکل ۵ نشان داده شده است:
اکنون، روش 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 برای برآورده کردن
«میانای برنامهسازی کاربردی جریانهای واکنشی» موردنیاز با انجام کارهای زیر استفاده میکند:
Flowایجاد میکند.- یک ثانیه صبر میکند.
- وضعیت چراغ هوشمند را ایجاد و منتشر میکند.
- یک ثانیه دیگر صبر میکند.
- وضعیت دماپا را ایجاد و منتشر میکند.
رسیدگی به کنشها
روش performControlAction زمانی را که کاربر با کنترل منتشرشده تعامل میکند نشان میدهد. نوع ControlAction ارسالشده کنش را تعیین میکند.
کنش مناسب را برای کنترل ارائهشده انجام دهید و سپس وضعیت دستگاه را در میانای کاربری 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) }
برنامه را اجرا کنید، به منو کنترلهای دستگاه دسترسی پیدا کنید، و کنترلهای چراغ و دماپا را ببینید.