در اندروید 11 و جدیدتر، ویژگی «کنترلهای دستگاه دسترسی سریع» به کاربر این امکان را میدهد که به سرعت دستگاههای خارجی مانند چراغها، ترموستاتها و دوربینها را در سه تعامل از یک راهانداز پیشفرض مشاهده و کنترل کند. دستگاه OEM انتخاب می کند که از چه لانچری استفاده کند. تجمیعکنندههای دستگاه - برای مثال Google Home - و برنامههای فروشنده شخص ثالث میتوانند دستگاههایی را برای نمایش در این فضا ارائه دهند. این صفحه به شما نشان می دهد که چگونه کنترل های دستگاه را در این فضا قرار دهید و آنها را به برنامه کنترل خود پیوند دهید.
برای افزودن این پشتیبانی، یک ControlsProviderService
ایجاد و اعلام کنید. کنترلهایی را که برنامهتان پشتیبانی میکند بر اساس انواع کنترلهای از پیش تعریفشده ایجاد کنید، و سپس ناشرهایی برای این کنترلها ایجاد کنید.
رابط کاربری
دستگاه ها تحت کنترل های دستگاه به عنوان ویجت های الگو نمایش داده می شوند. پنج ویجت کنترل دستگاه موجود است، همانطور که در شکل زیر نشان داده شده است:
برای کنترل عمیقتر، با لمس و نگهداشتن ویجت به برنامه میروید. میتوانید نماد و رنگ هر ویجت را سفارشی کنید، اما برای بهترین تجربه کاربری، اگر مجموعه پیشفرض با دستگاه مطابقت داشت، از نماد و رنگ پیشفرض استفاده کنید.
سرویس را ایجاد کنید
این بخش نحوه ایجاد ControlsProviderService
را نشان می دهد. این سرویس به رابط کاربری سیستم اندروید میگوید که برنامه شما حاوی کنترلهای دستگاه است که باید در قسمت کنترلهای دستگاه رابط کاربری Android ظاهر شوند.
ControlsProviderService
API آشنایی با جریان های واکنشی را فرض می کند، همانطور که در پروژه Reactive Streams GitHub تعریف شده و در رابط های Java 9 Flow پیاده سازی شده است. 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() { ... }
جاوا
public class MyCustomJavaControlService extends ControlsProviderService { ... }
نوع کنترل صحیح را انتخاب کنید
API متدهای سازنده را برای ایجاد کنترل ها ارائه می دهد. برای پر کردن سازنده، دستگاهی را که میخواهید کنترل کنید و نحوه تعامل کاربر با آن را تعیین کنید. مراحل زیر را انجام دهید:
- نوع دستگاهی را که کنترل نشان می دهد انتخاب کنید. کلاس
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() { ... }
جاوا
public class MyCustomJavaControlService extends ControlsProviderService { private final int LIGHT_ID = 1337; private final String LIGHT_TITLE = "My fancy light"; private final int LIGHT_TYPE = DeviceTypes.TYPE_LIGHT; private final int THERMOSTAT_ID = 1338; private final String THERMOSTAT_TITLE = "My fancy thermostat"; private final int THERMOSTAT_TYPE = DeviceTypes.TYPE_THERMOSTAT; ... }
برای کنترل ها ناشر ایجاد کنید
پس از ایجاد کنترل، به یک ناشر نیاز دارد. ناشر رابط کاربری سیستم را از وجود کنترل مطلع می کند. کلاس ControlsProviderService
دارای دو روش ناشر است که باید آنها را در کد برنامه خود لغو کنید:
-
createPublisherForAllAvailable()
: یکPublisher
برای تمام کنترل های موجود در برنامه شما ایجاد می کند. ازControl.StatelessBuilder()
برای ساخت اشیاءControl
برای این ناشر استفاده کنید. -
createPublisherFor()
: یکPublisher
برای لیستی از کنترل های داده شده، همانطور که توسط شناسه های رشته آنها مشخص می شود، ایجاد می کند. ازControl.StatefulBuilder
برای ساخت این اشیاءControl
استفاده کنید، زیرا ناشر باید به هر کنترل یک حالت اختصاص دهد.
ناشر را ایجاد کنید
وقتی برنامه شما برای اولین بار کنترلها را در رابط کاربری سیستم منتشر میکند، برنامه از وضعیت هر یک از کنترلها اطلاعی ندارد. گرفتن حالت می تواند یک عملیات وقت گیر باشد که شامل پرش های زیادی در شبکه ارائه دهنده دستگاه می شود. از متد createPublisherForAllAvailable()
برای تبلیغ کنترل های موجود در سیستم استفاده کنید. این روش از کلاس سازنده Control.StatelessBuilder
استفاده می کند، زیرا وضعیت هر کنترل ناشناخته است.
هنگامی که کنترلها در رابط کاربری اندروید ظاهر شدند، کاربر میتواند کنترلهای دلخواه را انتخاب کند.
برای استفاده از کوروتین های 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= 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 ): Flow.Publisher { TODO() } override fun performControlAction( controlId: String, action: ControlAction, consumer: Consumer ) { TODO() } }
جاوا
public class MyCustomJavaControlService extends ControlsProviderService { private final int LIGHT_ID = 1337; private final String LIGHT_TITLE = "My fancy light"; private final int LIGHT_TYPE = DeviceTypes.TYPE_LIGHT; private final int THERMOSTAT_ID = 1338; private final String THERMOSTAT_TITLE = "My fancy thermostat"; private final int THERMOSTAT_TYPE = DeviceTypes.TYPE_THERMOSTAT; private boolean toggleState = false; private float rangeState = 18f; private final Map<String, ReplayProcessor> controlFlows = new HashMap<>(); @NonNull @Override public Flow.Publisher createPublisherForAllAvailable() { List controls = new ArrayList<>(); controls.add(createStatelessControl(LIGHT_ID, LIGHT_TITLE, LIGHT_TYPE)); controls.add(createStatelessControl(THERMOSTAT_ID, THERMOSTAT_TITLE, THERMOSTAT_TYPE)); return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls)); } @NonNull @Override public Flow.Publisher createPublisherFor(@NonNull List controlIds) { ReplayProcessor updatePublisher = ReplayProcessor.create(); controlIds.forEach(control -> { controlFlows.put(control, updatePublisher); updatePublisher.onNext(createLight()); updatePublisher.onNext(createThermostat()); }); return FlowAdapters.toFlowPublisher(updatePublisher); } }
منوی سیستم را به سمت پایین بکشید و دکمه Device controls را که در شکل 4 نشان داده شده است پیدا کنید:
با ضربه زدن روی کنترلهای دستگاه به صفحه دومی هدایت میشود که در آن میتوانید برنامه خود را انتخاب کنید. هنگامی که برنامه خود را انتخاب می کنید، می بینید که چگونه قطعه قبلی یک منوی سیستم سفارشی ایجاد می کند که کنترل های جدید شما را نشان می دهد، همانطور که در شکل 5 نشان داده شده است:
اکنون متد createPublisherFor()
را پیاده سازی کنید و موارد زیر را به Service
خود اضافه کنید:
کاتلین
private val job = SupervisorJob() private val scope = CoroutineScope(Dispatchers.IO + job) private val controlFlows = mutableMapOf<String, MutableSharedFlow>() private var toggleState = false private var rangeState = 18f override fun createPublisherFor(controlIds: List ): Flow.Publisher { val flow = MutableSharedFlow (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 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() }
جاوا
@NonNull @Override public Flow.PublishercreatePublisherFor(@NonNull List controlIds) { ReplayProcessor updatePublisher = ReplayProcessor.create(); controlIds.forEach(control -> { controlFlows.put(control, updatePublisher); updatePublisher.onNext(createLight()); updatePublisher.onNext(createThermostat()); }); return FlowAdapters.toFlowPublisher(updatePublisher); } private Control createStatelessControl(int id, String title, int type) { Intent intent = new Intent(this, MainActivity.class) .putExtra(EXTRA_MESSAGE, title) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent action = PendingIntent.getActivity( this, id, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); return new Control.StatelessBuilder(id + "", action) .setTitle(title) .setDeviceType(type) .build(); } private Control createLight() { return createStatefulControl( LIGHT_ID, LIGHT_TITLE, LIGHT_TYPE, toggleState, new ToggleTemplate( LIGHT_ID + "", new ControlButton( toggleState, String.valueOf(toggleState).toUpperCase(Locale.getDefault()) ) ) ); } private Control createThermostat() { return createStatefulControl( THERMOSTAT_ID, THERMOSTAT_TITLE, THERMOSTAT_TYPE, rangeState, new RangeTemplate( THERMOSTAT_ID + "", 15f, 25f, rangeState, 0.1f, "%1.1f" ) ); } private Control createStatefulControl(int id, String title, int type, T state, ControlTemplate template) { Intent intent = new Intent(this, MainActivity.class) .putExtra(EXTRA_MESSAGE, "$title $state") .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent action = PendingIntent.getActivity( this, id, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); return new Control.StatefulBuilder(id + "", action) .setTitle(title) .setDeviceType(type) .setStatus(Control.STATUS_OK) .setControlTemplate(template) .build(); }
در این مثال، متد createPublisherFor()
شامل اجرای جعلی کاری است که برنامه شما باید انجام دهد: با دستگاه خود ارتباط برقرار کنید تا وضعیت آن را بازیابی کنید و آن وضعیت را به سیستم ارسال کنید.
متد createPublisherFor()
از کوروتینها و جریانهای Kotlin برای برآوردن API جریانهای واکنشی مورد نیاز با انجام کارهای زیر استفاده میکند:
- یک
Flow
ایجاد می کند. - یک ثانیه صبر می کند.
- حالت نور هوشمند را ایجاد و ساطع می کند.
- یک ثانیه دیگر صبر می کند.
- حالت ترموستات را ایجاد و منتشر می کند.
اقدامات را انجام دهید
متد performControlAction()
هنگامی که کاربر با یک کنترل منتشر شده تعامل برقرار می کند سیگنال می دهد. نوع ControlAction
ارسال شده، عمل را تعیین می کند. عمل مناسب را برای کنترل داده شده انجام دهید و سپس وضعیت دستگاه را در رابط کاربری اندروید به روز کنید.
برای تکمیل مثال، موارد زیر را به Service
خود اضافه کنید:
کاتلین
override fun performControlAction( controlId: String, action: ControlAction, consumer: Consumer) { 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) }
جاوا
@Override public void performControlAction(@NonNull String controlId, @NonNull ControlAction action, @NonNull Consumerconsumer) { ReplayProcessor processor = controlFlows.get(controlId); if (processor == null) return; if (controlId.equals(LIGHT_ID + "")) { consumer.accept(ControlAction.RESPONSE_OK); if (action instanceof BooleanAction) toggleState = ((BooleanAction) action).getNewState(); processor.onNext(createLight()); } if (controlId.equals(THERMOSTAT_ID + "")) { consumer.accept(ControlAction.RESPONSE_OK); if (action instanceof FloatAction) rangeState = ((FloatAction) action).getNewValue() processor.onNext(createThermostat()); } }
برنامه را اجرا کنید، به منوی Device Controls دسترسی پیدا کنید و کنترلهای نور و ترموستات خود را ببینید.