في الإصدار 11 من نظام Android والإصدارات الأحدث، تتيح ميزة "الوصول السريع إلى الأجهزة" للمستخدم عرض الأجهزة الخارجية، مثل المصابيح وأجهزة الترموستات والكاميرات بشكل سريع، والتحكّم فيها من خلال اهتمامات المستخدمين، وذلك خلال ثلاثة تفاعلات من مشغِّل التطبيقات التلقائي. يختار المصنّع الأصلي للجهاز مشغّل التطبيقات الذي يستخدمه. يمكن لخدمات تجميع الأجهزة، مثل Google Home، وتطبيقات المورّدين التابعة لجهات خارجية، توفير أجهزة لعرضها في هذه المساحة. توضّح لك هذه الصفحة كيفية عرض عناصر التحكم في الجهاز في هذه المساحة وربطها بتطبيق التحكّم.
لإضافة هذا الدعم، يُرجى إنشاء ControlsProviderService
والإعلان عنه. يمكنك إنشاء عناصر التحكّم التي يتيحها تطبيقك استنادًا إلى أنواع عناصر التحكّم المحدّدة مسبقًا، ثم إنشاء ناشري عناصر التحكّم هذه.
واجهة المستخدم
يتم عرض الأجهزة ضمن عناصر التحكم في الجهاز كأدوات نموذجية. تتوفر خمس أدوات للتحكم في الأجهزة، كما هو موضح في الشكل التالي:
![]() |
![]() |
![]() |
![]() |
![]() |
ويؤدي لمس إحدى الأدوات مع الاستمرار إلى نقلك إلى التطبيق للتحكُّم بشكل أكبر. يمكنك تخصيص الرمز واللون على كل أداة، ولكن للحصول على أفضل تجربة للمستخدم، استخدِم الرمز واللون التلقائيَين إذا كانت المجموعة التلقائية مطابقة للجهاز.

إنشاء الخدمة
يوضّح هذا القسم كيفية إنشاء
ControlsProviderService
.
تعمل هذه الخدمة على إعلام واجهة مستخدم نظام Android بأنّ تطبيقك يحتوي على عناصر تحكّم بالجهاز يجب ظهورها في منطقة عناصر التحكّم في الجهاز من واجهة مستخدم Android.
تفترض واجهة برمجة التطبيقات ControlsProviderService
الاعتياد على عمليات البث التفاعلية، على النحو
المحدّد في مشروع GitHub
والمستخدَمة في واجهات
Java 9 Flow.
تم تصميم واجهة برمجة التطبيقات استنادًا إلى المفاهيم التالية:
- الناشر: تطبيقك هو الناشر.
- المشترك: واجهة مستخدم النظام هي المشترك ويمكنها طلب عدد من عناصر التحكم من الناشر.
- الاشتراك: الإطار الزمني الذي يمكن للناشر خلاله إرسال التحديثات إلى واجهة مستخدم النظام. ويمكن للناشر أو المشترك إغلاق هذه النافذة.
الإعلان عن الخدمة
يجب أن يذكر تطبيقك خدمة، مثل 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() { ... }
جافا
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() { ... }
جافا
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
:
رائع
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() } }
جافا
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() }
جافا
@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) }
جافا
@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()); } }
شغِّل التطبيق ثم ادخل إلى قائمة عناصر التحكم في الجهاز واطّلع على عناصر التحكم في الإضاءة والترموستات.
