في الإصدار Android 11 والإصدارات الأحدث، تتيح ميزة "عناصر التحكّم في الأجهزة من خلال الوصول السريع" للمستخدمين عرض الأجهزة الخارجية والتحكّم فيها بسرعة، مثل وحدات الإضاءة وأجهزة الترموستات والكاميرات، من خلال ميزة مخصّصة للمستخدمين ضمن ثلاث تفاعلات من مشغّل تطبيقات تلقائي. يختار المصنّع الأصلي للجهاز مشغّل التطبيقات الذي سيتم استخدامه. يمكن لمجمّعي الأجهزة، مثل Google Home، وتطبيقات مزوّدي الخدمات الخارجيين توفير أجهزة للعرض في هذه المساحة. توضّح لك هذه الصفحة كيفية عرض عناصر التحكّم في الأجهزة في هذه المساحة وربطها بتطبيق التحكّم.
لإضافة هذا الدعم، أنشِئ ControlsProviderService وأعلِن عنه. أنشئ عناصر التحكّم التي يتيحها تطبيقك استنادًا إلى أنواع عناصر التحكّم المحدّدة مسبقًا، ثم أنشئ ناشرين لعناصر التحكّم هذه.
واجهة المستخدم
تظهر الأجهزة ضمن عناصر التحكّم في الجهاز كتطبيقات مصغّرة مستندة إلى نماذج. تتوفّر خمسة تطبيقات مصغّرة للتحكّم في الأجهزة، كما هو موضّح في الشكل التالي:
|
|
|
|
|
ويؤدي النقر مع الاستمرار على تطبيق مصغّر إلى الانتقال إلى التطبيق الأساسي لمزيد من التحكّم. يمكنك تخصيص الرمز واللون في كل تطبيق مصغّر، ولكن للحصول على أفضل تجربة للمستخدم، استخدِم الرمز واللون التلقائيين إذا كانت المجموعة التلقائية تتطابق مع الجهاز.
إنشاء الخدمة
يوضّح هذا القسم كيفية إنشاء
ControlsProviderService. تُعلم هذه الخدمة واجهة مستخدم نظام Android بأنّ تطبيقك يحتوي على عناصر تحكّم في الجهاز يجب عرضها في قسم عناصر التحكّم في الجهاز ضمن واجهة مستخدم Android.
تفترض واجهة برمجة التطبيقات ControlsProviderService API أنّك على دراية بمفهوم التدفقات التفاعلية، كما هو موضّح في مشروع Reactive Streams على GitHub ومطبَّق في واجهات Java 9 Flow.
تستند واجهة برمجة التطبيقات إلى المفاهيم التالية:
- الناشر: تطبيقك هو الناشر.
- المشترك: واجهة مستخدم النظام هي المشترك ويمكنها طلب عدد من عناصر التحكّم من الناشر.
- الاشتراك: هو الإطار الزمني الذي يمكن للناشر خلاله إرسال التعديلات إلى واجهة مستخدم النظام. يمكن للناشر أو المشترك إغلاق هذه النافذة.
الإفصاح عن الخدمة
يجب أن يعرّف تطبيقك خدمة، مثل MyCustomControlService، في بيان التطبيق.
يجب أن تتضمّن الخدمة فلتر intent لـ 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" }
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:
يؤدي النقر على عناصر التحكّم في الجهاز إلى الانتقال إلى شاشة ثانية حيث يمكنك اختيار تطبيقك. بعد اختيار تطبيقك، يمكنك الاطّلاع على كيفية إنشاء المقتطف السابق لقائمة نظام مخصّصة تعرض عناصر التحكّم الجديدة، كما هو موضّح في الشكل 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 coroutines وKotlin flows لتلبية متطلبات واجهة برمجة التطبيقات Reactive Streams API المطلوبة من خلال تنفيذ ما يلي:
- تنشئ
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) }
شغِّل التطبيق، وانتقِل إلى قائمة عناصر التحكّم في الجهاز، واطّلِع على عناصر التحكّم في الإضاءة والترموستات.