عرض المصدر على أداة "بحث الرموز البرمجية لنظام التشغيل Android"
إذا لم تكن من مورّدي شرائح Thread أو أجهزة Android، يمكنك التوقف عن القراءة الآن.
يرشدك هذا المستند خلال خطوات إنشاء جهاز Thread Border Router جديد يستند إلى Android باستخدام أحدث رمز مصدر AOSP. من خلال اتّباع هذا المستند، ستتعرّف على ما يلي:
- البنية العامة وحالة إتاحة Thread في Android
- كيفية إنشاء خدمة Thread HAL
- كيفية جعل جهازك متوافقًا مع Google Home
- كيفية اختبار جهاز توجيه الحدود Thread
إذا كنت بحاجة إلى دعم، يُرجى الإبلاغ عن مشكلة في GitHub أو فتح مناقشة إذا كان لديك أي أسئلة.
نظرة عامة
تستند حِزمة Android Thread إلى OpenThread وot-br-posix
، وهي
برامج مفتوحة المصدر تقدّمها Google على GitHub. بالطريقة نفسها التي يتم بها تطوير OpenThread في
مستودع GitHub مفتوح، يتم تطوير حِزمة Android Thread في
قاعدة بيانات AOSP المفتوحة. يتم إرسال جميع الميزات وإصلاحات الأخطاء أولاً في AOSP. يتيح ذلك للمورّدين بدء استخدام أحدث إصدارات
Thread بدون انتظار إصدارات Android العادية.
الهندسة المعمارية
تتألف حزمة Android Thread بالكامل من مكوّنَين رئيسيَّين: حزمة Thread الأساسية في قسم نظام عام وخدمة Thread HAL في قسم مخصّص للمورّد. لا يحتاج مورّدو الأجهزة عادةً إلا إلى إنشاء خدمة HAL والاعتناء بها.
في ما يلي ملخّص موجز لطريقة عمل حِزمة Android Thread:
- هناك خدمة نظام Java Thread في خادم النظام تدير
الحِزمة بأكملها، وهي توفّر واجهة برمجة التطبيقات لنظام Thread، وتُنشئ thread-wpan
واجهة النفق، وتُسجِّل شبكة Thread في
خدمة الاتصال
وتنفِّذ وظيفتَي "توجيه الحدود" و"الخادم الوكيل للإعلانات".
- يتم استضافة حِزمة Thread / OpenThread الأساسية في عملية مستقلة
أصلية غير مفوَّضة باسم ot-daemon
. تتم إدارة ot-daemon
مباشرةً
من خلال خدمة نظام Java عبر واجهات برمجة تطبيقات AIDL الخاصة، كما يمكنها الوصول إلى بث الوسائط المرئي والصوتي
من خلال واجهة برمجة التطبيقات Thread HAL API.
- يجب أن توفّر خدمة Thread HAL التي يوفّرها المورّد واجهة برمجة التطبيقات Thread HAL API. ويعمل
عادةً كمنصة
RCP
وينفِّذ بروتوكول
spinel.
أين الرمز؟
- إطار عمل / واجهة برمجة تطبيقات وخدمة Android Thread: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Connectivity/thread/
- واجهة برمجة التطبيقات Thread HAL وتنفيذ الخدمة التلقائية: https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/threadnetwork/
- مستودع OpenThread المستورَد: https://cs.android.com/android/platform/superproject/main/+/main:external/openthread/
- مستودع ot-br-posix المستورَد: https://cs.android.com/android/platform/superproject/main/+/main:external/ot-br-posix/
إعداد بيئة التطوير
يمكن لمورّدي أجهزة Android الذين سبق لهم إنشاء بيئة تطوير لنظام Android على الجهاز تخطّي هذا القسم.
إذا كنت مبتدئًا في منظومة Android المتكاملة أو كنت مورّدًا لشرائح السيليكون وتريد جعل شريحة Thread متوافقة مع Android وتقديم الدعم لمورّدي الأجهزة، يُرجى مواصلة القراءة.
اتّباع الدرس التطبيقي حول الترميز المخصّص لمطوّري تطبيقات Android
لإعداد بيئة تطوير Android للمرة الأولى، استخدِم الدرس التطبيقي التالي: https://source.android.com/docs/setup/start. في نهاية هذا الدليل التعليمي حول الرموز البرمجية، ستتمكّن من إنشاء جهاز Cuttlefish محاكي وتشغيله من الرمز المصدر.
إنشاء خدمة Thread HAL
تجربة ميزة "سلسلة محادثات" في Cuttlefish
Cuttlefish هو جهاز Android افتراضي. قبل بدء إنشاء خدمة HAL الخاصة بك، من الأفضل تجربة Thread في Cuttlefish لفهم آلية عمل HAL.
تتوفر خدمة Thread HAL تلقائيًا في Cuttlefish ويتم تنفيذها باستخدام بروتوكول RCP المحاكي الذي ينقل ويستقبل الحِزم عبر مقبس UDP من وإلى برمجية محاكاة Thread (802.15.4) اللاسلكية.
في مثيل Cuttlefish، يكون تطبيق ThreadNetworkDemoApp مثبّتًا مسبقًا. افتح هذا التطبيق للانضمام بجهاز Cuttlefish إلى شبكة Thread التلقائية.
تتوفّر أيضًا أداتا سطر الأوامر ot-ctl
وot-cli-ftd
لضبط شبكة Thread بغرض اختبارها. تتوافق هذه الأدوات مع جميع
أوامر OpenThread CLI التي قد تكون على دراية بها.
يمكنك استخدام grep للبحث عن سجلات خدمة Cuttlefish Thread HAL من خلال:
adb logcat | egrep -i threadnetwork-service
07-21 10:43:05.048 0 0 I init : Parsing file /apex/com.android.hardware.threadnetwork/etc/threadnetwork-service.rc...
07-21 10:59:27.233 580 580 W android.hardware.threadnetwork-service: ThreadChip binder is unlinked
07-21 10:59:27.233 580 580 I android.hardware.threadnetwork-service: Close IThreadChip successfully
07-21 10:59:27.385 580 580 I android.hardware.threadnetwork-service: Open IThreadChip successfully
أو يمكنك استخدام grep للبحث عن سجلّات ot-daemon من خلال:
adb logcat | egrep -i ot-daemon
07-21 10:43:48.741 0 0 I init : starting service 'ot-daemon'...
07-21 10:43:48.742 0 0 I init : Created socket '/dev/socket/ot-daemon/thread-wpan.sock', mode 660, user 1084, group 1084
07-21 10:43:48.762 0 0 I init : ... started service 'ot-daemon' has pid 2473
07-21 10:46:26.320 2473 2473 I ot-daemon: [I] P-Daemon------: Session socket is ready
07-21 10:46:30.290 2473 2473 W ot-daemon: [W] P-Daemon------: Daemon read: Connection reset by peer
07-21 10:48:07.264 2473 2473 I ot-daemon: [INFO]-BINDER--: Start joining...
07-21 10:48:07.267 2473 2473 I ot-daemon: [I] Settings------: Saved ActiveDataset
07-21 10:48:07.267 2473 2473 I ot-daemon: [I] DatasetManager: Active dataset set
07-21 10:48:07.273 2473 2473 I ot-daemon: [I] DnssdServer---: Started
07-21 10:48:07.273 2473 2473 I ot-daemon: [N] Mle-----------: Role disabled -> detached
07-21 10:48:07.273 2473 2473 I ot-daemon: [I] Mle-----------: AttachState Idle -> Start
07-21 10:48:07.273 2473 2473 I ot-daemon: [I] Notifier------: StateChanged (0x111fd11d) [Ip6+ Role LLAddr MLAddr KeySeqCntr Ip6Mult+ Channel PanId NetName ExtPanId ...
07-21 10:48:07.273 2473 2473 I ot-daemon: [I] Notifier------: StateChanged (0x111fd11d) ... NetworkKey PSKc SecPolicy NetifState ActDset]
تستخدم خدمة Cuttlefish Thread HAL خدمة Thread HAL التلقائية بالإضافة إلى ملف RCP الثنائي المحاكي لـ OpenThread. اطّلِع على القسم التالي لمعرفة كيفية عملها.
خدمة HAL التلقائية
يتم تضمين خدمة HAL التلقائية مع Thread HAL API. تتوافق خدمة HAL التلقائية مع كل من أجهزة RCP المحاكية والحقيقية. يتلقّى هذا الإجراء عنوان URL اختياريًا لجهاز RCP، وإذا لم يتم تقديم عنوان URL، يتم ضبطه تلقائيًا على جهاز RCP المحاكي.
في الملف hardware/interfaces/threadnetwork/aidl/default/threadnetwork-service.rc
:
service vendor.threadnetwork_hal /apex/com.android.hardware.threadnetwork/bin/hw/android.hardware.threadnetwork-service class hal user thread_network
ويعادل ذلك ما يلي:
service vendor.threadnetwork_hal /apex/com.android.hardware.threadnetwork/bin/hw/android.hardware.threadnetwork-service spinel+hdlc+forkpty:///apex/com.android.hardware.threadnetwork/bin/ot-rcp?forkpty-arg=1 class hal user thread_network
بالنسبة إلى أجهزة RCP الحقيقية، تتيح واجهات SPI وUART، ويمكنك
تحديد الجهاز باستخدام المخطّط spinel+spi://
وspinel+hdlc+uart://
و
spinel+socket://
على التوالي.
فهم APEX الخاص بالمورّد
على غرار حِزمة Thread في وحدة Tethering الرئيسية، يتم أيضًا تجميع خدمة Thread
HAL التلقائية في Cuttlefish في وحدة APEX. ولكن هذه ملفًا لوحدة APEX الخاصة بالمورّد وسيتم تثبيتها على /vendor/apex/
(سيتم فك ضغط العناصر في
الوحدة إلى /apex/com.android.hardware.threadnetwork/
).
apex { name: "com.android.hardware.threadnetwork", manifest: "manifest.json", file_contexts: "file_contexts", key: "com.android.hardware.key", certificate: ":com.android.hardware.certificate", updatable: false, vendor: true, binaries: [ "android.hardware.threadnetwork-service", "ot-rcp", ], prebuilts: [ "threadnetwork-default.xml", // vintf_fragment "threadnetwork-service.rc", // init_rc "android.hardware.thread_network.prebuilt.xml", // permission ], }
هناك بعض الإعدادات المهمة التي يجب الانتباه إليها أو إجراء تغييرات عليها عند إنشاء وحدة HAL APEX الخاصة بك:
file_contexts
: يصف هذا الحقل ملفات البيانات أو الملفات الثنائية التي يتم تسليمها في ملف وحدة APEX هذا أو الملفات التي تحتاج خدمة HAL إلى الوصول إليها (مثل جهاز RCP ). يتيح لك ذلك تحديد قواعد سياسة أمان معيّنة لخدمة HAL للوصول إلى جهاز RCP للأجهزة.binaries
: الملف الثنائي الذي تم إرساله في وحدة APEX هذهthreadnetwork-service.rc
: كيفية بدء خدمة HAL عليك تحديد مسار جهاز RCP هنا.
android.hardware.thread_network.prebuilt.xml
: لتحديد ميزة الجهازandroid.hardware.thread_network
هذه الخطوة مطلوبة لكي يعرف نظام Android أنّ جهازك متوافق مع الأجهزة التي تستخدم بروتوكول Thread. بخلاف ذلك، لن يتم تفعيل حزمة Android Thread.
إنشاء خدمة HAL
سواءً كنت مطوّر أجهزة Android أو مورّد سيليكون، يجب أن تكون على دراية بإنشاء البرامج الثابتة لبروتوكول RCP في شبكة الطاقة (OT RCP) لشريحة Thread. تفترض التعليمات التالية أنّ شريحة الجهاز متصلة بشكلٍ صحيح وقد تم التحقّق منها.
إنّ أبسط طريقة لإنشاء HAL APEX هي إنشاء APEX جديد باستخدام
الثنائيات والإصدارات المُسبقة من HAL APEX التلقائي. على سبيل المثال، إذا كانت شركتك هي
Banana وكان جهاز RCP على جهازك هو /dev/ttyACM0
، سيظهر Thread HAL
APEX على النحو التالي:
Android.bp
:
prebuilt_etc { name: "banana-threadnetwork-service.rc", src: "banana-threadnetwork-service.rc", installable: false, } apex { name: "com.banana.android.hardware.threadnetwork", manifest: "manifest.json", file_contexts: "file_contexts", key: "com.android.hardware.key", certificate: ":com.android.hardware.certificate", updatable: false, vendor: true, binaries: [ "android.hardware.threadnetwork-service", ], prebuilts: [ "banana-threadnetwork-service.rc", "threadnetwork-default.xml", "android.hardware.thread_network.prebuilt.xml", ], }
file_contexts
:
(/.*)? u:object_r:vendor_file:s0 /etc(/.*)? u:object_r:vendor_configs_file:s0 /bin/hw/android\.hardware\.threadnetwork-service u:object_r:hal_threadnetwork_default_exec:s0 /dev/ttyACM0 u:object_r:threadnetwork_rcp_device:s0
ترتبط مسارات الملفات في العمود الأول بـ /apex/com.android.hardware.threadnetwork/
.
threadnetwork-service.rc
:
service vendor.threadnetwork_hal /apex/com.android.hardware.threadnetwork/bin/hw/android.hardware.threadnetwork-service spinel+hdlc+uart:///dev/ttyACM0?uart-baudrate=115200 class hal user root
manifest.json
:
{ "name": "com.android.hardware.threadnetwork", "version": 1 }
لنفترض أنّك تنشئ جهازًا جديدًا باسم Orange، سيكون دليل الإعدادات الخاص بالجهاز على النحو التالي:
device/banana/orange/threadnetwork/ sepolicy/ Android.bp file_contexts manifest.json threadnetwork-default.xml threadnetwork-service.rc
اطّلِع على القسم التالي لمعرفة قواعد sepolicy التي يجب إضافتها في الدليل الفرعي sepolicy/
.
قواعد سياسة الأمان لجهاز RCP
لا تملك خدمة Thread HAL إذن الوصول إلى جهاز RCP تلقائيًا (مثل
/dev/ttyACM0
)، ويجب إضافة قواعد سياسة الأمان المخصّصة إلى دليل
sepolicy/
.
أنشئ ملف sepolicy/threadnetwork_hal.te
جديدًا يتضمّن المحتوى التالي:
type threadnetwork_rcp_device, dev_type; # Allows the Thread HAL service to read / write the Thread RCP device allow hal_threadnetwork_default threadnetwork_rcp_device:chr_file rw_file_perms;
تجميع
لقد أكملت الآن جميع متطلبات الرمز البرمجي تقريبًا لإضافة Thread، والخطوة الأخيرة هي إضافة Thread HAL APEX وقواعد sepolicy إلى صورة جهازك.
يمكنك إجراء ذلك عن طريق إضافة الرمز أدناه إلى Makefile
على جهازك (على سبيل المثال،
device.mk
):
PRODUCT_PACKAGES += com.banana.hardware.threadnetwork BOARD_SEPOLICY_DIRS += device/banana/orange/threadnetwork/sepolicy
إذا كان كل شيء يعمل على ما يرام، ستتمكّن الآن من الاطّلاع على سجلّ خدمة Thread HAL على النحو التالي:
adb logcat | egrep -i threadnetwork-service
08-13 13:26:41.751 477 477 I android.hardware.threadnetwork-service: ServiceName: android.hardware.threadnetwork.IThreadChip/chip0, Url: spinel+spi
08-13 13:26:41.751 477 477 I android.hardware.threadnetwork-service: Thread Network HAL is running
08-13 13:26:55.165 477 477 I android.hardware.threadnetwork-service: Open IThreadChip successfully
سيكون سجلّ ot-daemon
على النحو التالي:
adb logcat -s ot-daemon
08-13 13:26:55.157 1019 1019 I ot-daemon: [NOTE]-AGENT---: Running OTBR_AGENT/Unknown
08-13 13:26:55.157 1019 1019 I ot-daemon: [NOTE]-AGENT---: Thread version: 1.3.0
08-13 13:26:55.157 1019 1019 I ot-daemon: [NOTE]-AGENT---: Thread interface: thread-wpan
08-13 13:26:55.157 1019 1019 I ot-daemon: [NOTE]-AGENT---: Backbone interface is not specified
08-13 13:26:55.157 1019 1019 I ot-daemon: [NOTE]-AGENT---: Radio URL: threadnetwork_hal://binder?none
08-13 13:26:55.157 1019 1019 I ot-daemon: [NOTE]-ILS-----: Infra link selected:
08-13 13:26:55.160 1019 1019 I ot-daemon: [I] Platform------: [HAL] Wait for getting the service android.hardware.threadnetwork.IThreadChip/chip0 ...
08-13 13:26:55.165 1019 1019 I ot-daemon: [I] Platform------: [HAL] Successfully got the service android.hardware.threadnetwork.IThreadChip/chip0
08-13 13:26:55.275 1019 1019 I ot-daemon: [I] P-RadioSpinel-: RCP reset: RESET_UNKNOWN
08-13 13:26:55.276 1019 1019 I ot-daemon: [I] P-RadioSpinel-: Software reset RCP successfully
08-13 13:26:55.277 1019 1019 I ot-daemon: [I] P-RadioSpinel-: RCP reset: RESET_POWER_ON
08-13 13:26:55.322 1019 1019 I ot-daemon: [I] ChildSupervsn-: Timeout: 0 -> 190
08-13 13:26:55.324 1019 1019 I ot-daemon: [I] RoutingManager: Initializing - InfraIfIndex:0
08-13 13:26:55.324 1019 1019 I ot-daemon: [I] InfraIf-------: Init infra netif 0
08-13 13:26:55.324 1019 1019 I ot-daemon: [I] Settings------: Read BrUlaPrefix fd7b:cc45:ff06::/48
08-13 13:26:55.324 1019 1019 I ot-daemon: [N] RoutingManager: BR ULA prefix: fd7b:cc45:ff06::/48 (loaded)
08-13 13:26:55.324 1019 1019 I ot-daemon: [I] RoutingManager: Generated local OMR prefix: fd7b:cc45:ff06:1::/64
08-13 13:26:55.324 1019 1019 I ot-daemon: [N] RoutingManager: Local on-link prefix: fdde:ad00:beef:cafe::/64
08-13 13:26:55.324 1019 1019 I ot-daemon: [I] RoutingManager: Enabling
التخصيص
توفّر وحدة Thread الرئيسية (التي تشكّل في الواقع جزءًا من وحدة "ربط الأجهزة") بضع إعدادات قابلة للتراكب يمكن للمورّدين تحديدها لتخصيص سلوك الحزمة. اطّلِع علىملف config_thread.xml للحصول على القائمة الكاملة.
عادةً ما يجب ضبط config_thread_border_router_default_enabled
على true
لتفعيل جهازك كجهاز توجيه حدودي في Thread، وتغيير
config_thread_vendor_name
وconfig_thread_vendor_oui
و
config_thread_model_name
إلى قيم المورّد أو المنتج. سيتم تضمين هذه القيم
في خدمة _meshcop._udp
mDNS التي يعلن عنها دائمًا
جهاز توجيه حدود Thread.
لإضافة التراكب، عليك إنشاء هدف ConnectivityOverlayOrange
runtime_resource_overlay جديد لجهاز Orange. أنشئ دليلاً جديدًا باسم
ConnectivityOverlay/
ضمن device/banana/orange/rro_overlays
وأنشئ المحتوى التالي فيه:
device/banana/orange/rro_overlays/ConnectivityOverlay/ res values config_thread.xml Android.bp AndroidManifest.xml
Android.bp
:
package { default_applicable_licenses: ["Android-Apache-2.0"], } runtime_resource_overlay { name: "ConnectivityOverlayOrange", manifest: "AndroidManifest.xml", resource_dirs: ["res"], certificate: "platform", product_specific: true, sdk_version: "current", }
AndroidManifest.xml
:
<!-- Orange overlays for the Connectivity module --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.banana.android.connectivity.resources.orange" android:versionCode="1" android:versionName="1.0"> <application android:hasCode="false" /> <!-- If your device uses google-signed mainline modules, the targetPackage needs to be "com.google.android.connectivity.resources", otherise, it should be "com.android.connectivity.resources" --> <overlay android:targetPackage="com.google.android.connectivity.resources" android:targetName="ServiceConnectivityResourcesConfig" android:isStatic="true" android:priority="1"/> </manifest>
config_thread.xml
:
<bool name="config_thread_border_router_default_enabled">true</bool> <string translatable="false" name="config_thread_vendor_name">Banana Inc.</string> <string translatable="false" name="config_thread_vendor_oui">AC:DE:48</string> <string translatable="false" name="config_thread_model_name">Orange</string>
كما هو الحال مع HAL APEX، عليك إضافة التطبيق المتراكب إلى ملف device.mk
:
PRODUCT_PACKAGES += \ ConnectivityOverlayOrange</code>
إذا كان كل شيء يعمل على ما يرام، ستلاحظ أنّ ot-daemon
يسجّل اسم المورّد والطراز
في بداية السجلّ:
adb logcat -s ot-daemon
07-22 15:31:37.693 1472 1472 I ot-daemon: [I] P-Daemon------: Session socket is ready
07-22 15:31:37.693 1472 1472 I ot-daemon: [I] Cli-----------: Input: state
07-22 15:31:37.693 1472 1472 I ot-daemon: [I] Cli-----------: Output: disabled
07-22 15:31:37.693 1472 1472 I ot-daemon: [I] Cli-----------: Output: Done
07-22 15:31:37.693 1472 1472 W ot-daemon: [W] P-Daemon------: Daemon read: Connection reset by peer
07-22 15:31:50.091 1472 1472 I ot-daemon: [I] P-Daemon------: Session socket is ready
07-22 15:31:50.091 1472 1472 I ot-daemon: [I] Cli-----------: Input: factoryreset
07-22 15:31:50.092 1472 1472 I ot-daemon: [I] Settings------: Wiped all info
07-22 15:31:50.092 1472 1472 I ot-daemon: [INFO]-ADPROXY-: Stopped
07-22 15:31:50.092 1472 1472 I ot-daemon: [INFO]-DPROXY--: Stopped
07-22 15:31:50.092 1472 1472 I ot-daemon: [INFO]-BA------: Stop Thread Border Agent
07-22 15:31:50.092 1472 1472 I ot-daemon: [INFO]-BA------: Unpublish meshcop service Banana Inc. Orange #4833._meshcop._udp.local
07-22 15:31:50.092 1472 1472 I ot-daemon: [INFO]-MDNS----: Removing service Banana Inc. Orange #4833._meshcop._udp
07-22 15:31:50.092 1472 1472 I ot-daemon: [INFO]-MDNS----: Unpublishing service Banana Inc. Orange #4833._meshcop._udp listener ID = 0
أن تكون متوافقة مع Google Home
بالإضافة إلى ذلك، إذا كنت تريد أن يستخدم نظام Google Home
المتكامل جهاز التوجيه الحدودي، يمكنك تحديد هذا الإعداد في config_thread.xml
:
<string-array name="config_thread_mdns_vendor_specific_txts"> <item>vgh=1</item> </string-array>
الاختبار
من المفترض أن يكون جهازك متوافقًا مع مواصفات جهاز توجيه الحدود Thread 1.3 والإصدارات الأحدث الآن. قبل إرسال التطبيق إلى برنامج اعتماد Thread، يجب إجراء بعض اختبارات xTS لنظام التشغيل Android لضمان التوافق.
يضمن اختبار VTS عمل خدمة Thread HAL على النحو المتوقّع على جهازك. يمكنك إجراء الاختبارات باستخدام الأمر
atest VtsHalThreadNetworkTargetTest
يضمن اختبار CTS عمل واجهات برمجة التطبيقات Thread على النحو المتوقّع على جهازك. يمكنك إجراء الاختبارات باستخدام الأمر
atest CtsThreadNetworkTestCases
يقدّم اختبار الدمج ضمانًا إضافيًا للجودة بشأن طريقة عمل رمز Thread mainline على جهازك. يمكنك إجراء الاختبارات باستخدام الأمر
atest ThreadNetworkIntegrationTests
يمكنك أيضًا العثور على مزيد من التعليمات حول كيفية إجراء اختبارات VTS/CTS/MTS باستخدام مجموعات الاختبار التي تم إصدارها:
- https://source.android.com/docs/core/tests/vts
- https://source.android.com/docs/compatibility/cts/run
- https://docs.partner.android.com/mainline/test/mts (يجب أن تكون شريكًا للوصول إلى هذا الرابط)
الاختبار باستخدام تطبيق Thread التجريبي
على غرار جهاز Cuttlefish، يمكنك إضافة تطبيق Thread التجريبي إلى صورة النظام:
# ThreadNetworkDemoApp for testing PRODUCT_PACKAGES_DEBUG += ThreadNetworkDemoApp
يُرجى العلم أنّه يجب إضافته إلى الصيغة المخصّصة لتصحيح الأخطاء أو الإصدار المخصّص للمطوّرين فقط (على سبيل المثال،
PRODUCT_PACKAGES_DEBUG
) لأنّه من المفترض ألا يتم تضمينه في الإصدار المخصّص للمستخدمين النهائيين.