في نظام التشغيل Android 11 والإصدارات الأحدث، تم توفير ميزة "عناصر التحكّم في الجهاز" ضمن "الوصول السريع" تتيح للمستخدم عرض الأجهزة الخارجية والتحكم فيها بسرعة مثل الأضواء وأجهزة الترموستات والكاميرات من ميزات المستخدم خلال ثلاثة تفاعلات من مشغّل التطبيقات التلقائي. يختار المصنّع الأصلي للجهاز مشغّل التطبيقات الذي يستخدمه. الجهاز تطبيقات التجميع، مثل Google Home وتطبيقات المورّدين التابعة لجهات خارجية توفير أجهزة للعرض في هذه المساحة. توضّح لك هذه الصفحة كيفية عرض عناصر التحكم بالجهاز في هذه المساحة وربطها بتطبيق التحكّم.
لإضافة هذا الدعم، عليك إنشاء ControlsProviderService
وتعريفها. إنشاء
يتحكّم بها تطبيقك استنادًا إلى أنواع عناصر تحكُّم محدّدة مسبقًا، ثمّ
للناشرين لعناصر التحكم هذه.
واجهة المستخدم
يتم عرض الأجهزة ضمن عناصر التحكم في الأجهزة كتطبيقات مصغّرة وفقًا لنموذج. خمسة تتوفر أدوات التحكم في الجهاز، كما هو موضح في الشكل التالي:
|
|
|
|
|
اللمس & ويؤدي الضغط على تطبيق مصغّر إلى نقلك إلى التطبيق لمزيد من التحكم. يمكنك تخصيص الرمز واللون في كل تطبيق مصغّر، ولكن لتقديم أفضل تجربة للمستخدم، يمكنك استخدام الرمز واللون الافتراضيين إذا كانت المجموعة التلقائية مطابقة للجهاز.
إنشاء الخدمة
يوضح هذا القسم كيفية إنشاء
ControlsProviderService
تُعلِم هذه الخدمة واجهة مستخدم نظام Android بأنّ تطبيقك يحتوي على عناصر تحكُّم في الجهاز.
التي يجب أن تظهر في منطقة عناصر التحكّم في الجهاز ضمن واجهة مستخدم Android.
تفترض واجهة برمجة التطبيقات ControlsProviderService
أن هناك إلمامًا بأحداث البث التفاعلية، على سبيل المثال
محدد في Reactive Streams GitHub
المشروع
وتنفيذه في تدفق Java 9
.
تم إنشاء واجهة برمجة التطبيقات استنادًا إلى المفاهيم التالية:
- الناشر: تطبيقك هو الناشر.
- المشترِك: واجهة مستخدم النظام هي المشترك ويمكنها طلب رقم. لعناصر التحكم من الناشر.
- الاشتراك: الإطار الزمني الذي يمكن للناشر إرسال تحديثات خلاله إلى واجهة مستخدم النظام. يمكن للناشر أو المشترك إغلاق هذا القسم نافذة.
تقديم بيان عن الخدمة
يجب أن يفصح تطبيقك عن خدمة معيّنة، مثل 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()
:
Kotlin
class MyCustomControlService : ControlsProviderService() { ... }
Java
public class MyCustomJavaControlService extends 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
:
Kotlin
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() { ... }
Java
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
، حيث تكون حالة كل عنصر تحكم
غير معروف.
بمجرد ظهور عناصر التحكم في واجهة مستخدم Android، يمكن للمستخدم تحديد المفضلة والتحكم المستمر.
لاستخدام الكوروتينات في لغة Kotlin لإنشاء ControlsProviderService
، يجب إضافة سمة
التبعية لـ build.gradle
:
Groovy
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()
:
Kotlin
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() } }
Java
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> 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); } }
مرِّر سريعًا للأسفل في قائمة النظام وحدِّد مكان زر عناصر التحكّم في الجهاز الذي يظهر في الشكل 4:
من خلال النقر على عناصر التحكم في الجهاز، سيتم نقلك إلى شاشة ثانية يمكنك من خلالها اختيار تطبيقك. بعد تحديد التطبيق، سترى كيف ينشئ المقتطف السابق قائمة نظام مخصصة تعرض عناصر التحكم الجديدة، كما هو موضح في الشكل 5:
والآن، نفِّذ الطريقة createPublisherFor()
، مع إضافة ما يلي إلى
Service
:
Kotlin
private val job = SupervisorJob() private val scope = CoroutineScope(Dispatchers.IO + job) private val controlFlows = mutableMapOf>() 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() }
Java
@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 والتدفقات لتلبية احتياجات المستخدمين.
واجهة برمجة التطبيقات Reactive Streams API المطلوبة لتنفيذ ما يلي:
- ينشئ
Flow
. - انتظر لمدة ثانية واحدة.
- تعمل هذه السياسة على إنشاء حالة الضوء الذكي وإطلاقها.
- يُرجى الانتظار لمدة ثانية أخرى.
- تعمل هذه السياسة على إنشاء حالة الترموستات وإصدارها.
التعامل مع الإجراءات
تُرسِل الطريقة performControlAction()
إشارات عندما يتفاعل المستخدم مع
التحكم المنشور. يحدّد نوع إرسال ControlAction
الإجراء الإجراء.
تنفيذ الإجراء المناسب لعنصر التحكم المحدّد ثم تحديث الحالة
للجهاز في واجهة مستخدم Android.
لإكمال المثال، أضِف ما يلي إلى Service
:
Kotlin
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) }
Java
@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()); } }
شغِّل التطبيق وادخل إلى قائمة عناصر التحكم في الجهاز وشاهِد المصباح. عناصر التحكم بالترموستات.