در Android 11 و نسخههای بالاتر، ویژگی «کنترلهای دستگاه دسترسی سریع» به کاربران امکان میدهد دستگاههای خارجی مثل چراغ، دماپا، و دوربین را بهسرعت ازطریق یک امکان کاربر در سه تعامل از راهانداز پیشفرض مشاهده و کنترل کنند. سازنده تجهیزات اصلی دستگاه انتخاب میکند که از کدام راهانداز استفاده شود. تجمیعکنندههای دستگاه—برای مثال، Google Home—و برنامههای فروشنده طرف سوم میتوانند دستگاههایی برای نمایش در این فضا ارائه دهند. این صفحه به شما نشان میدهد چگونه کنترلهای دستگاه را در این فضا نمایش دهید و آنها را به برنامه کنترل خود پیوند دهید.
برای افزودن این پشتیبانی، ControlsProviderService را ایجاد و اعلام کنید. براساس انواع کنترل ازپیشتعریفشده، کنترلهایی را که برنامهتان پشتیبانی میکند ایجاد کنید، و سپس ناشرانی برای این کنترلها ایجاد کنید.
رابط کاربری
دستگاهها در بخش کنترلهای دستگاه بهعنوان ابزارههای الگو نمایش داده میشوند. پنج ابزارک کنترل دستگاه دردسترس است، همانگونه که در شکل زیر نشان داده شده است:
|
|
|
|
|
لمس کردن و نگه داشتن ابزاره شما را به برنامه میبرد تا کنترل بیشتری داشته باشید. میتوانید نماد و رنگ هر ابزاره را سفارشیسازی کنید، اما برای داشتن بهترین تجربه کاربری، اگر مجموعه پیشفرض با دستگاه مطابقت دارد، از نماد و رنگ پیشفرض استفاده کنید.
ایجاد سرویس
این بخش نحوه ایجاد
ControlsProviderService را نشان میدهد. این سرویس به واسط کاربر سیستم Android میگوید که برنامه شما حاوی کنترلهای دستگاه است که باید در ناحیه کنترلهای دستگاه واسط کاربر Android نمایش داده شود.
ControlsProviderService API آشنایی با جاریسازیهای واکنشگرا را، همانگونه که در پروژه GitHub «جاریسازیهای واکنشگرا» تعریف شده است و در واسطهای «جاریسازی جاوا ۹» پیادهسازی شده است، پیشفرض میگیرد.
این «میانای برنامهسازی کاربردی» براساس مفاهیم زیر ساخته شده است:
- ناشر: برنامه شما ناشر است.
- مشترک: رابط کاربری سیستم مشترک است و میتواند تعدادی کنترل از ناشر درخواست کند.
- اشتراک: بازه زمانی که ناشر میتواند بهروزرسانیها را به «میانای کاربر سیستم» ارسال کند. هم ناشر و هم مشترک میتوانند این پنجره را ببندند.
اعلام سرویس
برنامه شما باید سرویسی مثل 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
|
برای نشان دادن کنترلی که قابلیت لمسی دارد اما وضعیت آن قابل تعیین نیست، مثل کنترل از دور تلویزیون فروسرخ. میتوانید از این الگو برای تعریف روال یا کلان استفاده کنید، که مجموعهای از کنترل و تغییرات وضعیت است. |
با این اطلاعات، میتوانید کنترل را ایجاد کنید:
- وقتی وضعیت کنترل نامشخص است، از کلاس سازنده
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) }
برنامه را اجرا کنید، به منو کنترلهای دستگاه دسترسی پیدا کنید، و کنترلهای چراغ و ترموستات را ببینید.