مهندسة علاقات المطوّرين
ومع تنقّل المستخدمين بين الهواتف والأجهزة القابلة للارتداء والأجهزة الصوتية الملحقة، أصبح تقديم تجارب سلسة على جميع الأجهزة أمرًا ضروريًا لتعزيز التفاعل. ومع ذلك، فإنّ الاعتماد على أساليب الاتصال بالشبكة العادية غالبًا ما يؤدي إلى استنزاف البطارية وارتفاع وقت الاستجابة وتجزئة جلسات المستخدمين.
تستعرض هذه النظرة المتعمّقة واجهات برمجة تطبيقات Android المتخصّصة المصمّمة لأجهزة متعدّدة، وتوفّر أنماطًا موفّرة للذاكرة والطاقة للحفاظ على عمليات انتقال فورية وموثوقة وسهلة بين الأجهزة.
النص
مرحبًا،
مرحبًا بك في وحدة "تفعيل التفاعلات السلسة على جميع الأجهزة".
اسمي "هانروي"، وينضم إليّ اليوم زميلاي "ياسين" و"ناتاراج".
عندما نتحدث عن إمكانية الاتصال، غالبًا ما يتبادر إلى الذهن رابط شبكة أساسي أو سماعات رأس بلوتوث.
على Android، إمكانية الاتصال أوسع من ذلك بكثير.
والهدف من ذلك هو تحسين أداء الأجهزة عند ربطها معًا.
اليوم، سنستعرض السيناريوهات الشائعة لاستخدام التطبيقات على أجهزة متعددة وسنوضّح واجهة برمجة التطبيقات المناسبة لكل سيناريو.
لفترة طويلة، كنّا ننشئ تطبيقات لجهاز واحد، وهو الهاتف فقط.
بعد ذلك، انتشرت الأجهزة القابلة للارتداء.
وسرعان ما أصبح من الشائع جدًا إقران ساعة ذكية بهاتف، ومزامنة البيانات في الخلفية أو تلقّي المكالمات مباشرةً.
أضِف سمّاعات أذن-- أضِف سمّاعات أذن لاسلكية.
يمكن للمستخدمين الآن تبديل الجهاز الصوتي أثناء المكالمة، من الساعة إلى الهاتف إلى سماعات الأذن أو إلى سياراتهم.
ومؤخرًا، أصبح العديد من المستخدمين يحملون أيضًا جهازًا ثانيًا بشاشة كبيرة، مثل جهاز قابل للطي أو جهاز لوحي.
ويتوقّعون أن يعمل التطبيق بسلاسة على هذه الأجهزة.
وينطبق ذلك أيضًا على أجهزة الكمبيوتر المحمول وأجهزة Chromebook، حيث يمكن للمستخدم تشغيل تطبيقات Android والبقاء على اتصال من مكتبه مباشرةً.
في عام 2023، كان المستخدم العادي يمتلك ما بين ثلاثة إلى أربعة أجهزة.
يتوقّع المستخدمون أكثر من مجرد وظائف مستقلة على هذه الأجهزة.
ويبحثون عن تجربة أفضل بشكل استثنائي عند استخدام الأجهزة معًا.
تتضح تجربة "التكامل بين الأجهزة" بشكل خاص عندما يكون الأشخاص في الغرفة نفسها مثلنا اليوم ويريدون طريقة سهلة لمشاركة الصور أو الروابط أو التطبيقات مع الأشخاص المجاورين لهم.
بالنظر إلى الأرقام، نجد أنّ المنظومة المتكاملة ضخمة وتنمو بسرعة.
في هذا العام، هناك حوالي 22 مليار جهاز إنترنت الأشياء في جميع أنحاء العالم، بزيادة قدرها 14٪ على أساس سنوي.
ومن بين جميع نقاط النهاية هذه، تشكّل شبكة Wi-Fi الثلث ويشكّل البلوتوث الربع.
لم يعُد الاتصال بين الأجهزة ميزة هامشية.
إنّها جزء من حياتنا اليومية.
ومع ذلك، لا تزال معظم التطبيقات اليوم تنشئ ميزاتها التي تعمل على أجهزة متعددة على واجهات برمجة تطبيقات الشبكة وBluetooth منخفضة المستوى، ما يؤدي إلى ظهور ثلاثة تحديات.
أولاً، البطارية والذاكرة.
يؤدي استمرار عمليات الفحص والخدمات في الخلفية إلى استنزاف البطارية والذاكرة.
ثانيًا، وقت الاستجابة.
تؤدي حالات التأخير غير المتوقعة إلى تعطيل المكالمات ومزامنة البيانات في الوقت الفعلي.
ثالثًا، صعوبة التواصل
إنّ إدارة عمليات الاكتشاف والإقران وإعادة الاتصال على جميع الأجهزة هي عملية معقّدة وتؤدي بسهولة إلى تجربة مستخدم سيئة.
للتخلّص من هذه المشاكل، أنشأنا واجهات برمجة تطبيقات مخصّصة في ثلاثة مجالات رئيسية لتسهيل استخدام الميزات على أجهزة متعددة.
سأبدأ بالتطبيقات المصاحبة، ثم سيتناول "ناتاراج" تطبيقات VoIP، وبعد ذلك سيتحدث "ياسين" عن مشاركة التطبيقات من نظير إلى نظير.
لنلقِ نظرة أولاً على التطبيقات المصاحبة.
التطبيق المصاحب هو تطبيق تثبّته على هاتفك لكي يعمل جهاز مصاحب، مثل ساعة ذكية أو جهاز استشعار الغلوكوز، بشكل جيد معه.
في هذا القسم، سنركّز على الأجهزة المتّصلة بالبلوتوث.
هناك عادةً أربع فئات من الأجهزة المرافقة.
أولاً، الأجهزة الطبية، مثل أدوات استشعار الغلوكوز وأجهزة مراقبة المؤشرات الحيوية
والأجهزة القابلة للارتداء، مثل الساعات الذكية وأساور اللياقة البدنية.
سمّاعات رأس مزوّدة بميزة "الإقران السريع" وعناصر تحكّم ذكية
ومجموعات السيارة، لتنفيذ إجراءات مثل استخدام مفاتيح السيارة الرقمية وإجراء المكالمات بدون لمس الجهاز
لكل فئة تجارب مستخدم رئيسية خاصة بها، ولكنّها تعتمد جميعها على اتصال موثوق بين الجهاز والهاتف.
بالنسبة إلى الأجهزة الطبية، قد يكون هذا الاتصال بالغ الأهمية للحفاظ على الحياة.
لنتعرّف على أربعة احتياجات شائعة في هذه الفئات.
أولاً، إدارة الأجهزة:
يحتاج التطبيق إلى العثور على الجهاز المناسب من بين جميع الأجهزة القريبة، مثل الساعة هنا، وليس سماعة الأذن أو مكبّر الصوت، ثم بدء عملية الإقران للاتصال.
ثانيًا، التحقّق من الحضور
يحتاج التطبيق إلى معرفة متى يكون الجهاز قريبًا ومتى يبتعد.
وعندما يظهر الجهاز، يحتاج التطبيق إلى طريقة لتنشيطه حتى يتمكّن من أداء مهمته حتى لو لم يفتحه المستخدم.
ثالثًا، صيانة الاتصال.
وبعد الاتصال، تتطلّب التطبيقات أن يكون الاتصال موثوقًا ومستمرًا.
إذا انقطع الاتصال، مثلاً إذا خرج المستخدم من النطاق ثم عاد، يجب أن يعيد التطبيق الاتصال تلقائيًا، حتى إذا لم يكن قيد التشغيل في ذلك الوقت.
رابعًا، مزامنة البيانات ونقلها.
هنا تكمن القيمة الحقيقية.
مزامنة قراءات أدوات الاستشعار أو إرسال تحديثات البرامج الثابتة أو إرسال الإعدادات إلى الجهاز
بالنسبة إلى الأجهزة الطبية، قد يكون هذا الأمر بالغ الأهمية.
وقد يؤدي عدم قراءة مستوى السكر في الدم إلى عدم تلقّي تنبيه.
وتتطلّب جميع هذه الميزات أن تكون عملية التطبيق نشطة ومتصلة في الوقت المناسب.
إذًا، كيف لبّت التطبيقات كل هذه الاحتياجات؟
في السابق، كانت التطبيقات تعتمد بشكل كبير على الخدمات التي تعمل في المقدّمة.
في ما يلي مثال على عملية تنفيذ نموذجية.
إذا سبق لك إنشاء تطبيق مصاحب، قد يبدو هذا مألوفًا.
يجب تقديم بيان عن أذونات البحث عن بلوتوث والاتصال به، وأذونات الخدمة التي تعمل في المقدّمة، ونوع الخدمة التي تعمل في المقدّمة للجهاز المتصل، ثم إذن إرسال الإشعارات لأنّ الخدمة التي تعمل في المقدّمة يجب أن تعرض إشعارًا دائمًا.
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />بعد ذلك، يمكنك تشغيل خدمة ثابتة لإبقاء عملية التطبيق نشطة.
class BleForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
…
return START_STICKY
}
}هذه الطريقة فعّالة، ولكنّها تستهلك البطارية والذاكرة لأنّ الخدمة تعمل طوال الوقت، حتى عندما لا يكون الجهاز قريبًا.
لهذا السبب، أنشأنا "أداة إدارة الأجهزة المصاحبة" (CDM).
أولاً، يمكنه تنشيط تطبيقك استنادًا إلى توفّر الجهاز، وطالما بقي الجهاز في النطاق، سيُبقي النظام عملية تطبيقك نشطة.
لا حاجة إلى خدمة تعمل في المقدّمة.
ثانيًا، تتيح وحدة CDM استخدام الملفات الشخصية للأجهزة.
باستخدام ملف تعريف الجهاز، يمنح المستخدم جميع الأذونات التي يحتاجها تطبيقك بنقرة واحدة بدلاً من سلسلة من مربّعات حوار الأذونات.
يؤدي ذلك إلى تحسين تجربة المستخدم ومعدّل الإحالات الناجحة بشكل كبير.
يتوفّر ملف الساعة الشخصي منذ الإصدار Android 12، وملف النظارات الشخصي منذ الإصدار Android 14.
في Android 17، أضفنا ملفَّين شخصيَّين جديدَين خصيصًا للأجهزة الطبية وأدوات تتبُّع اللياقة البدنية.
هناك أيضًا بعض الملفات الشخصية المميّزة لتطبيقات النظام فقط في الوقت الحالي.
وإذا وجدنا حاجة أوسع نطاقًا من المنظومة المتكاملة، فقد نفكّر في إتاحتها لمزيد من المطوّرين.
استخدام CDM سهل للغاية.
لنأخذ جهازًا طبيًا كمثال.
أولاً، يجب الإعلان عن الأذونات اللازمة: إذن للجهاز الطبي، وإذن للتشغيل في الخلفية، وإذن لمراقبة توفّر الجهاز.
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_MEDICAL" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
<uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />على عكس الأذونات التي رأيناها للتو في تنفيذ الخدمة التي تعمل في المقدّمة، فإنّ جميع الأذونات المدرَجة هنا هي أذونات عادية، ما يعني أنّه سيتم منحها عند التثبيت، بدون ظهور مربّع حوار يطلب ذلك.
الشيء الوحيد الذي يجب على المستخدم الموافقة عليه هو الربط.
لنتعرّف على كيفية إنشاء هذا الربط.
في هذا المثال، ننشئ طلب ربط باستخدام فلتر للأجهزة لكي يظهر الجهاز المناسب فقط.
val paringRequest = AssociationRequest.Builder()
.addDeviceFilter(deviceFilter)
.setSingleDevice(true)
.setDeviceProfile(AssociationRequest.DEVICE_PROFILE_MEDICAL)
.build()
companionDeviceManager.associate(paringRequest, ContextCompat.getMainExecutor(this),
object : CompanionDeviceManager.Callback() {
override fun onAssociationPending(intentSender: IntentSender) {
intentSenderLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
}
override fun onAssociationCreated(associationInfo: AssociationInfo) { … }
override fun onFailure(error: CharSequence?) { … }
}
)setSingleDevice على "صحيح" لأننا نريد مطابقة واحدة فقط، ونضبط الملف الشخصي على "طبي".
ثم اتصل بالموظف.
تتولّى وحدة CDM عملية المسح الضوئي.
في onAssociationPending، سنحصل على IntentSender.
عند تشغيله، يعرض النظام مربّع حوار للربط.
باستخدام ملف طبي، لا تؤدي هذه النقرة هنا إلى الموافقة على الربط نفسه فحسب، بل تمنح أيضًا جميع الأذونات التي يحتاجها الملف الشخصي، حتى تجاوز وضع "عدم الإزعاج"، وهو أمر بالغ الأهمية للتنبيهات المهمة.
بعد أن ينقر المستخدم على "السماح"، سنحصل على onAssociationCreated مع AssociationInfo، والذي يتضمّن رقم تعريف الربط.
بعد ذلك، يمكننا رصد حضور الجهاز من خلال استدعاء startObservingDevicePresence باستخدام رقم تعريف الربط هذا.
وعليك أيضًا تنفيذ خدمة تمدّد CompanionDeviceService.
class BleCompanionDeviceService : CompanionDeviceService() {
override fun onDevicePresenceEvent(event: DevicePresenceEvent) {
when (event.event) {
DevicePresenceEvent.EVENT_BLE_APPEARED -> onDeviceAppeared(event.associationId)
DevicePresenceEvent.EVENT_BLE_DISAPPEARED -> onDeviceDisappeared(event.associationId)
}
}
private fun onDeviceAppeared(associationId: Int) { … }
private fun onDeviceDisappeared(associationId: Int) { … }
}عندما يصبح الجهاز ضمن النطاق، يرتبط النظام بخدمتك حتى إذا لم يكن تطبيقك قيد التشغيل.
وسيتصل النظام بالرقم onDevicePresenceEvent باستخدام EVENT_BLE_APPEARED.
بعد ذلك، يمكنك ربط الجهاز ومزامنته.
عندما يغادر الجهاز، ستحصل على EVENT_BLE_DISAPPEARED.
بعد ذلك، يمكنك إجراء عملية التنظيف.
لا توجد خدمة تعمل دائمًا ولا إشعارات لمجرد البقاء على قيد الحياة.
علينا أن نعترف بأنّ نموذج CDM له بعض القيود التي يجب على المطوّرين وضعها في الاعتبار.
أولاً، إصدارات نظام التشغيل.
تم طرح معظم ميزات "إدارة الأجهزة المرافقة" في Android 12 و13.
ولكن تم إجراء بعض التحسينات الرائعة في Android 16 والملفات الشخصية الجديدة في Android 17.
لذا، تحقَّق من إصدار نظام التشغيل في وقت التشغيل واحتفظ بخيار احتياطي للإصدارات القديمة.
ومع ذلك، يرتفع إصدار حزمة SDK المستهدَفة كل عام، ويميل الحد الأدنى لإصدار حزمة SDK إلى اتباعه.
لمعالجة الضغط على الذاكرة الذي تواجهه التطبيقات الآن وستواجهه في المستقبل، ننصح بشدة بنقل التطبيقات من الخدمة التي تعمل في المقدّمة إلى CDM في أقرب وقت ممكن.
وإذا كانت لديك حالة استخدام لا يمكن أن يتيحها CDM اليوم، يُرجى إعلامنا بذلك.
ثانيًا، تتيح ميزة CDM حاليًا المسح في الخلفية، ولكنّها لا تتيح الإعلان في الخلفية.
نادرًا ما نتلقّى طلبات بشأن هذه الميزة.
ولكن إذا كان تطبيقك يحتاج إلى الهاتف ليعمل كجهاز طرفي ويعلن باستمرار في الخلفية، فيسعدنا معرفة المزيد عنه.
ثالثًا، تستخدم ميزة CDM وضع المسح الضوئي المنخفض الطاقة.
وتتخلّى هذه الموارد عن جزء من الاستجابة في الوقت الفعلي مقابل توفير طاقة البطارية والذاكرة.
لذا، لا تتوقّع أن يستيقظ على الفور.
أخيرًا، لا يمكن الاطّلاع على الحمولة.
لا يمكنك التسجيل للحصول على سمة معيّنة أو بيانات إعلانية.
ولكن عندما يصبح الجهاز في النطاق، يتم تنشيط تطبيقك.
وبالتالي، يمكنه الاتصال والتحقّق من الحمولة حسب الحاجة.
هذا كل ما يتعلّق بالتطبيقات المصاحبة وCDM.
سأترك الكلمة لـ "ناتاراج" ليتحدث عن تطبيقات VoIP.
تُعدّ CompanionDeviceManager الأداة المناسبة إذا كان تطبيقك يمتلك جهازًا ملحقًا مخصّصًا ويتواصل معه باستمرار.
مرحبًا،
اسمي "ناتاراج ك ر".
سأتحدث الآن عن السيناريو التالي الذي يتيح استخدام ميزات على أجهزة متعددة، وهو المكالمات في الوقت الفعلي.
في الوقت الحالي، تتغيّر طريقة تعامل المستخدمين مع المكالمات.
يتم إجراء أكثر من %40 من المكالمات عبر شبكات VoIP.
ويؤدي Android دورًا رئيسيًا في ذلك.
في الواقع، يتم إجراء %71 من هذه المكالمات على أجهزة Android، أي ما يعادل كل مكالمة أخرى.
لا يقتصر الأمر على المكالمات فقط.
وتشهد منظومة الأجهزة التي يمتلكها المستخدمون نموًا هائلاً أيضًا.
هناك أكثر من 250 مليون سيارة تستخدم Android Auto على الطرق، ونحن نشحن أيضًا أكثر من مليار جهاز يتضمّن بلوتوث إلى المستخدمين.
يملك المستخدمون أجهزة متعددة إلى جانب هواتفهم، مثل سمّاعات الأذن وساعات Wear OS وأجهزة Android والسيارات المزوّدة بنظام Android Auto ومكبّرات صوت فيها بلوتوث، ويريدون أن تنتقل مكالماتهم بسلاسة بين كل هذه الأجهزة.
على سبيل المثال، إذا تلقّى المستخدم مكالمة، يمكنه إلقاء نظرة على ساعته لمعرفة معلومات المتصل.
ويمكنهم الردّ على المكالمة من خلال ساعتهم.
ويمكنهم كتم الصوت أو إعادة الصوت حسب الضرورة، ووضع سمّاعات الأذن ومواصلة المكالمة.
إذا أرادوا الخروج، فسيجرون مكالمتهم، وسيركبون سيارتهم، ويتوقعون أن تنتقل مكالمتهم بسلاسة إلى مقصورة السيارة.
وبالتالي، أصبح هذا روتينًا عاديًا للمستخدم، وهذه ليست تجربة مميزة.
بالنسبة إلى المستخدم، هذا مطلب أساسي، ويقع على عاتقنا نحن مطوّري برامج Android توفير هذه التجربة السلسة في تطبيقات VoIP للمستخدمين.
ولكن إذا حاولت إنشاء كل هذا بنفسك من الصفر، فستواجه ثلاث مشاكل رئيسية.
أولاً، التجزئة في مقابس البلوتوث.
بما أنّ النظام لا يعرف أنّ تطبيقك يعالج مكالمة، سينتهي بك الأمر إلى إنشاء مآخذ بلوتوث مخصّصة وجلسات وسائط مخصّصة لكي يتم عرض مكالمتك على الساعة أو Android Auto.
المشكلة الثانية هي التركيز على الصوت.
بسبب التعامل مع واجهات برمجة تطبيقات AudioManager وBluetooth منخفضة المستوى، تحدث تسريبات صوتية، ولا يمثّل ذلك أولوية للنظام، أليس كذلك؟
يتلقّى تطبيقك مكالمة عبر بروتوكول VoIP، لذا عندما ترد مكالمة من مشغّل شبكة الجوّال، تتم مقاطعة المكالمة ويستحوذ المودم على الصوت، ما يؤدي إلى عدم توفّر صوت في تطبيقك.
والأسوأ من ذلك، حتى بعد انتهاء المكالمة الخلوية، لن يعود التركيز الصوتي إلى تطبيقك.
والمشكلة الثالثة هي المزامنة.
إذا نقر المستخدم على "كتم الصوت" على ساعته، ستظل واجهة مستخدم الهاتف تعرض المكالمة على أنّها غير مكتومة الصوت.
وهذا يسبب ارتباكًا كبيرًا.
يقطع المستخدم المكالمة من عمود توجيه السيارة، ولكن يظل الهاتف يعرض إشعارًا بمكالمة جارية.
ويكون ذلك محبطًا جدًا للمستخدم عندما يحدث.
لحلّ كل هذه المشاكل، أنشأنا Jetpack Telecom.
لذلك، يمكنك تسجيل مكالمتك مرة واحدة في Jetpack Telecom باستخدام CallsManager، وتعمل Jetpack Telecom كمنسّق مركزي لمكالمات النظام.
وتتولّى هذه الخدمة مسؤولية عرض مكالمتك على الساعة وAndroid Auto والأسطح البعيدة الأخرى التي يملكها المستخدم.
وتتولّى أيضًا معالجة التركيز على الصوت.
والأهم من ذلك أنّ كل هذه الميزات تعمل بسلاسة على جميع الإصدارات، وصولاً إلى Android 8.
إنّ عرض المكالمة على شاشة الساعة أو السيارة هو نصف القصة فقط، لأنّ المستخدم يريد بالطبع التحكّم في حالة المكالمة من هذه الأجهزة.
وقد يريد كتم صوت المكالمة من ساعته أو إعادة الصوت حسب الحاجة.
ويريد إنهاء المكالمة من عمود توجيه السيارة أو بالنقر مرّتين على سمّاعات الأذن.
لذلك، تستمع Jetpack Telecom إلى أحداث الأجهزة هذه وتقدّمها كعمليات استدعاء في مكان واحد، وهو CallControlScope.
ما عليك سوى الاستماع إلى عمليات الاستدعاء هذه، والتعامل مع محرّك المكالمات وتعديل حالة المكالمة وفقًا لذلك.
وتتولّى Jetpack Telecom مسؤولية مزامنة حالة المكالمة هذه بين الأجهزة.
لذا، في الخلفية، تقدّم المكتبة مزايا في فئتَين.
أولاً، من الواضح أنّها تحلّ جميع المشاكل التي تحدثنا عنها.
لذا، بدلاً من التعامل مع واجهات برمجة تطبيقات AudioManager منخفضة المستوى، توفّر المكتبة نقاط نهاية للمكالمات التعريفية، وتدير أولوية الصوت، وتزامن بين الأجهزة.
ويتولّى مسؤولية نقل المكالمة بين الأجهزة.
ويتم التعامل مع أي مكالمة خلوية أيضًا.
ولكنّ الميزة الأخرى التي توفّرها المكتبة هي أنّها مبنية على Kotlin، لذا عند إضافة مكالمة باستخدام Jetpack Telecom، يتم فتح نطاق دالة Kotlin coroutine.
لذلك، يستفيد من أنماط "كوروتين" في Kotlin، ويظل نمط "كوروتين" في Kotlin نشطًا حتى مدة المكالمة بالضبط.
وبالتالي، يتم تلقائيًا إلغاء أي مهمة تم تشغيلها في ما يتعلق بالمكالمة فور انقطاعها.
وبالتالي، لن تتبقّى أي مهام غير مرغوب فيها، وستتولّى المكتبة أيضًا إدارة مراحل نشاط المكالمات.
قد تتساءلون الآن عن كيفية استخدام هذه المكتبة الرائعة، أليس كذلك؟
الأمر بسيط للغاية.
الخطوة الأولى هي أنّه يجب بالطبع الإعلان عن التبعية.
بعد ذلك، حدِّد الإذن المطلوب، وهو MANAGE_OWN_CALLS، ثم أنشئ مثيلاً لعنصر CallsManager.
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />بعد إنشاء مثيل لعنصر CallsManager، سجِّل تطبيقك بالميزات التي تريد توفيرها من خلال التطبيق، مثل مكالمات الفيديو أو بث المكالمات.
// Initialize CallsManager & register supported capabilities once
val callsManager = CallsManager(context)
callsManager.registerAppWithTelecom(
capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
)بعد ذلك، الخطوة رقم 2.
بعد تسجيل تطبيقك في Jetpack Telecom، حان الوقت للتعامل مع المكالمات الواردة والصادرة.
لذلك، عليك إنشاء عنصر CallAttributesCompat يحتوي على البيانات الوصفية للمكالمة، مثل displayName المتصل أو المستلِم، والعنوان، وهو نقطة نهاية المتصل، والاتجاه، وتحديد ما إذا كانت المكالمة واردة أو صادرة حتى تتمكّن Jetpack Telecom من عرض واجهة المستخدم المعنيّة على هذه الأسطح البعيدة، ثم callType، سواء كانت مكالمة صوتية أو مكالمة فيديو حتى تتمكّن من الحصول على الموارد المطلوبة.
val attributes = CallAttributesCompat(
displayName = "Kukk Nat",
address = Uri.parse("tel:+16505550199"),
direction = CallAttributesCompat.DIRECTION_INCOMING,
callType = CallAttributesCompat.CALL_TYPE_AUDIO_CALL
)
callsManager.addCall(
attributes, onAnswer, onDisconnect, onSetActive, onSetInactive
) {
// Coroutine CallControlScope active for the duration of the call
}بعد تجهيز عنصر البيانات الوصفية، أضِفه باستخدام الطريقة callsManager.addCall.
يؤدي ذلك إلى فتح نطاق دالة coroutine للتحكّم في المكالمات.
بعد الانتهاء من إضافة المكالمة، ننتقل إلى الخطوة 3.
الخطوة الثالثة هي الاستماع إلى الإجراءات التي سينفّذها المستخدم من تلك الأسطح البعيدة.
لذلك، بما أنّ لديك CallControlScope، كل ما عليك فعله هو جمع تدفقات Kotlin.
callsManager.addCall(attributes, onAnswer, onDisconnect, onSetActive, onSetInactive) {
// 1. React to hardware mute toggles from Watch, Car, or Headset
launch {
isMuted.collect { muted -> audioEngine.setMicMuted(muted) }
}
// 2. Observe available endpoints & request declarative switches
launch {
currentCallEndpoint.collect { endpoint -> updateUi(endpoint) }
}
requestEndpointChange(preferredBluetoothEndpoint)
}يمكنك جمع isMuted للتفاعل عندما يكتم المستخدم صوت المكالمة من تلك الأجهزة البعيدة، وبالتالي يمكنك تعديل حالة المكالمة.
اجمع currentCallEndpoint لمعرفة نقطة النهاية التي تحدث فيها المكالمة حاليًا حتى تتمكّن من تعديل واجهة المستخدم وفقًا لذلك.
يمكنك عرض هذا الجهاز المحدّد على واجهة مستخدم هاتفك.
وعندما يريد المستخدم التبديل من جهاز إلى آخر، ما عليك سوى استدعاء طريقة requestEndpointChange مع أي نقطة نهاية مفضّلة يريدها المستخدم.
الأمر بهذه البساطة.
باتّباع هذه الخطوات الثلاث، سيكون تطبيقك جاهزًا للاستخدام بعد دمج Jetpack Telecom.
لكن هذا ليس كل شيء.
مع الإصدار 1.1.0 من Jetpack Telecom، نقدّم ميزات أكثر بكثير، مثل سجلّات المكالمات الموحّدة، ما يعني أنّ جميع مكالمات VoIP ستظهر الآن بشكل تلقائي في برنامج الاتصال على النظام.
وبالتالي، تكمن الميزة في أنّ المستخدمين سيتمكّنون من بدء معاودة الاتصال بنقرة واحدة مباشرةً من برنامج الاتصال في النظام إلى تطبيقك.
وبالتالي، لن تحتاج إلى البحث عن تطبيق معيّن بين جميع التطبيقات.
ويمكنهم ببساطة طلب إعادة الاتصال من برنامج الاتصال التابع للنظام الذي اعتادوا عليه.
ولكن بالنسبة إلى البعض، ولكن إذا كنت، ولكننا نقدّم أيضًا عناصر تحكّم دقيقة.
إذا كنت لا تريد تسجيل مكالمة معيّنة في النظام، ما عليك سوى ضبط علامة isLogExcluded على "صحيح" داخل عنصر CallAttributesCompat.
ما عليك سوى ضبط قيمة عنصر البيانات الوصفية الذي تحدّثت عنه سابقًا على "صحيح"، ولن يتم تسجيل مكالمتك في النظام ولن تظهر في برنامج الاتصال الخاص بالنظام.
الميزة الرائعة الثانية التي ستتوفّر في الإصدار 1.1.0 هي تقسيم شبكة الجيل الخامس.
لذلك، على شبكات مشغّلي شبكات الجوّال المتوافقة، توفّر لك الشبكات شرائح شبكة متميّزة، ما يعني أنّه في حالة استخدام معيّنة، سيمنحك مشغّل شبكة الجوّال شريحة شبكة ذات وقت استجابة منخفض وبدون أي انقطاعات أخرى، ما يتيح لك الحصول على جودة أفضل وتجربة أفضل.
لذلك، ستتوفّر هذه الميزة تلقائيًا على شبكات الجوّال المتوافقة، ولن تحتاج إلى بذل أي جهد إضافي، ولا حتى كتابة سطر واحد من الرموز البرمجية.
بعد دمج المكتبة، ستستفيد مكالماتك من شرائح الشبكة هذه وستحصل على وقت استجابة أفضل وأداء أفضل.
ولكن إذا كنت لا تريد هذه الميزة لسبب ما، يمكنك إيقافها أيضًا باستخدام العلامة CAPABILITY_OPT_OUT_OF_PREMIUM_NETWORK.
تم تنفيذ Jetpack Telecom من قِبل أفضل تطبيقات التواصل العالمية، مثل Google Meet وWhatsApp وKakaoTalk وZoho Arattai.
لذا، الأمر متروك لك الآن لدمج هذه المكتبة في تطبيقك وتوفير تجربة أفضل سلسة للمستخدم.
بعد أن تناولنا ميزة "المكالمات في الوقت الفعلي"، أودّ الآن أن أسلّم الميكروفون لزميلي الرائع ياسين ليتحدّث عن ميزة "النقر للمشاركة".
شكرًا لك.
حسنًا يا أصدقائي، أعلم أنّكم متعبون قليلاً، لذا لا تقلقوا، هذا هو القسم الأخير.
لذا، سأتناول مشاركة الملفات من نظير إلى نظير، ولكن هناك حالات استخدام أخرى يمكننا القيام بها أيضًا من خلال المشاركة من نظير إلى نظير.
لاحظنا في منظومة الأجهزة الجوّالة المتكاملة أنّه تتم مشاركة أكثر من 14 مليار صورة وفيديو على وسائل التواصل الاجتماعي وتطبيقات المراسلة كل يوم، وهذا عدد هائل.
وتتم مشاركة كل هذه الملفات في معظم الأحيان من خلال السحابة الإلكترونية، حيث تلتقط الصورة وتحمّلها من خلال الخدمة المفضّلة لديك، ثم يتعيّن على الشخص الآخر تنزيلها بالطريقة نفسها.
ويحدث الشيء نفسه في الواقع عندما تحاول دعوة شخص ما أو صديق إلى شبكة اجتماعية لدعوة شخص ما للتعاون في قائمة تشغيل أو مستند، عليك عادةً كتابة اسم المستخدم أو بريده الإلكتروني، الأمر الذي يستغرق بعض الوقت على لوحة المفاتيح.
قد ترتكب خطأً، وهذه ليست تجربة رائعة في الواقع، خاصةً عندما يجلس كلاكما بجانب بعضكما البعض، لذا يبدو الأمر كما لو أنني يجب أن أرسل عبر السحابة، يجب أن أنتظر.
إنّها ليست جيدة.
في الوقت الحالي، لدينا جميعًا هواتف وأجهزة وأجهزة لوحية وأجهزة قابلة للطي متطورة، ونلتقط صورًا مذهلة باستخدامها.
ولكن عند مشاركة المحتوى من خلال بعض وسائل التواصل الاجتماعي أو بعض تطبيقات المراسلة، يتم ضغط المحتوى لأسباب تتعلّق بمشكلة في الأداء ومساحة التخزين، ما يؤدي في النهاية إلى ظهور المحتوى على شاشتك الرائعة بجودة منخفضة أو غير مثالية.
ندرك أنّ هذه ليست التجربة الأفضل التي تتوقّعها من جهازك المميّز.
ثانيًا، أصبحت الملفات الآن كبيرة جدًا.
يمكن أن يصل حجم الصورة الواحدة بسهولة إلى 10 ميغابايت، ويبلغ حجم فيديو مدته 5 دقائق حوالي 500 ميغابايت، لذا يستغرق تحميل كل ذلك وتنزيله وقتًا طويلاً.
وأخيرًا، من ناحية أخرى، عليك الانتظار حتى انتهاء وقت الاستجابة.
لذا، قد تكون أحيانًا في حافلة مزدحمة، وأثناء تحرّك الحافلة، لا تحصل على أفضل وقت استجابة أو أفضل تغطية للشبكة، وأحيانًا يتم إلغاء نقل الملفات، وأحيانًا بسبب وقت الاستجابة الطويل جدًا، قد تنتهي المهلة ويتم إلغاء نقل الملفات، وهو أمر محبط حقًا للمستخدم.
لذا، بدلاً من الاعتماد على السحابة الإلكترونية لمشاركة كل هذه الملفات والانتظار حتى يتم ذلك، لماذا لا نستخدم شبكة محلية، مثل البلوتوث أو Wi-Fi؟
ولكن إذا حاولت القيام بذلك بنفسك، فمن الواضح أنه يتعين عليك التعامل مع مدير Bluetooth Bluetooth، ومدير Wi-Fi، واضطرارك إلى كتابة البروتوكول الخاص بك، أعني، ليس كل شخص يريد أن يكون لديه بعض الوقت للقيام بذلك.
ولكن في الواقع، هناك ميزة متوفّرة حاليًا على Android.
لقد تم إطلاقها بالفعل، وربما سمعت عنها، وهي ميزة Quick Share.
تتيح لك ميزة Quick Share على Android استخدام هذا التجريد في النظام، ويمكنك مشاركة الملفات مع جهاز آخر بدون الحاجة إلى إدارة كل ذلك.
تتوفّر هذه الميزة في صفحة المشاركة، وهي تختار البروتوكول الأفضل لجهازك من بين الجهازَين، وتتوفّر على نظام التشغيل Marshmallow والإصدارات الأحدث، وهو ما يمثّل في الوقت الحالي جمهورًا كبيرًا، أليس كذلك؟
أخيرًا، إنّ واجهة المستخدم وتجربة المستخدم المألوفتَين اللتين تظهران على الجانب تساعدان المستخدمين بشكل كبير.
وبالتالي، يعرفون واجهة المستخدم ولا يحتاجون إلى تعلّم كيفية استخدامها لأنّهم سبق أن رأوها في الماضي.
لكننا نعلم أنّ المطوّرين يريدون أيضًا التحكّم في التجربة.
أنت أدرى بتطبيقك وبالمستخدمين أكثر منّا على الأرجح.
إذًا، لمَ لا تدمج ميزة Quick Share مباشرةً في تطبيقك؟
لذا، بدأنا في التحدث مع المطوّرين ورأينا أنّ ميزة Quick Share منتشرة على نطاق واسع.
يتوفّر على أجهزة Android وأجهزة Chromebook ومؤخرًا على "كتب Google" وأيضًا على أجهزة Windows، أي على أكثر من 3 مليار جهاز، وهذا عدد هائل.
وأضفنا أيضًا إلى هاتف Pixel 11 ميزة "النقر للمشاركة".
وبدلاً من اختيار الجهاز الذي تريد مشاركة المحتوى معه، يمكنك النقر على الجهاز الآخر، وستتولى تقنية NFC عملية الاكتشاف مباشرةً.
يمكنك اختيار صورة أو ملف، ثم ملامسة جهازك بالآخر لبدء المشاركة مباشرةً.
ونعلم أنّ المطوّرين يريدون التحكّم في تجربة المستخدم.
ونعلم أنّها أفضل طريقة تتيح للمستخدمين استخدام ميزة معيّنة، وهي في هذه الحالة مشاركة الملفات.
لذا، لمَ لا يتم تضمين ميزة Quick Share في تطبيقك؟
لهذا السبب، سنوفّر لكم في العام المقبل حزمة تطوير البرامج (SDK) لميزة Quick Share مباشرةً.
ستتمكّن من استخدامها مباشرةً في تطبيقك، وهي مزوّدة بميزات أمان مدمجة، مثل التشفير التام بين الأطراف بين كلا الجهازين.
بما أنّ عمليات النقل تتم بين هذه الشبكات المحلية، ستكون السرعة كاملة، ومن الواضح أنّ ذلك يتم في شبكة Wi-Fi المنزلية، ولكن يمكن أيضًا إنشاء نقطة اتصال تلقائيًا بينكما، ولن تضطر إلى التعامل مع ذلك.
وهي مضمّنة في الأجهزة التي تتيح اتصال Wi-Fi المباشر.
وستحتفظ الملفات التي التقطتها بالكاميرا الرائعة بأفضل دقة وتفاصيل، حتى تتمكّن من رؤيتها بشكل جيد على شاشتك الكبيرة.
وبما أنّها تعتمد على الشبكات المحلية، يمكنك استخدامها في مترو الأنفاق، حتى في أعمق جزء منه، وستظل لديك شبكة لتتمكّن من مشاركة الملفات مع صديقك.
وفي الطائرة أيضًا، ستكون على ارتفاع آلاف الأقدام في السماء، وستظل قادرًا على مشاركة هذه الملفات.
وأخيرًا، بالنسبة إليك كمطوّر، يوفّر ذلك الكثير من التكاليف في ما يتعلّق بالبيانات، لأنّ المستخدمين لن يضطروا إلى استخدام خطة البيانات، وهو أمر مهم جدًا عند السفر كسائحين.
ولكن أيضًا، على خوادمك الخاصة، ليس عليك صيانة ذلك.
في العام الماضي، أعلنّا للمطوّرين والمستخدمين أنّ ميزة Quick Share ستتيح لهم مشاركة الملفات مع أجهزة iPhone من خلال AirDrop.
من خلال ميزة Quick Share، نقدّم للمطوّرين المزايا نفسها.
سنوفّر حزمة تطوير برامج (SDK) لميزة Quick Share على أجهزة iOS لمطوّري تطبيقات iOS كي يتمكّنوا من مشاركة الملفات بين كلا النظامَين الأساسيَين، بغض النظر عن الجهاز.
كل ما سبق هو عرض تقديمي رائع، والآن، لنلقِ نظرة سريعة على الرمز البرمجي.
لقد حاولنا جعلها بسيطة قدر الإمكان.
يمكنك بدء QuickShareClient، وإرفاق الملفات التي تريد مشاركتها، وبعد بدء الجلسة، ستظهر لك هذه النافذة المشروطة الجميلة التي تظهر على الجانب، وهي البطاقة السفلية التي سيتمكّن المستخدمون من خلالها من رؤية الملفات التي يتم إرسالها كمعاينات، وإذا كنت تعتبر المحتوى حساسًا، يمكنك أيضًا تفعيل ذلك من خلال الخيار الذي يظهر على الشاشة.
// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val attachment = FileAttachment(
fileName = "report.pdf",
size = 1_048_576L,
uri = fileUri,
mimeType = "application/pdf",
// Sensitive content will be masked in preview
isSensitiveContent = false,
)
val content = ShareableContent.withFiles(listOf(attachment))
val request = SharingRequest.withContent(content)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)عندما تريد تلقّي ملف، مرة أخرى، داخل تطبيقك، نفس الشيء، بالطبع، في هذه الحالة، لا تضيف مرفقًا، وبعد ذلك بمجرد بدء الجلسة، لن تضطر إلى التعامل مع أي من ذلك.
// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val request = SharingRequest.withoutContent()
val session = quickShareClient.startSharingSession(request, lifecycleOwner)
// Open the Quick Share overlay in receive mode
session.launchWindow(activity, contentSelectionIntent = null)حتى الآن، ما رأيتموه هو حرفيًا 20 سطرًا فقط من الرموز البرمجية على كلا الجانبَين.
نعلم أنّ ميزتَي Quick Share و"النقر للمشاركة" تتيحان مشاركة الملفات بمجرد لمس الأجهزة، ما يوفّر حالة استخدام جديدة.
قد يتم إغلاق تطبيقك على الجهاز الآخر، ومن خلال النقر، بمجرد نقل الملف، سيتم فتح تطبيقك تلقائيًا وستتلقى معرّف الموارد المنتظم (URI) لتتمكن من إجراء بعض المعالجة على الجهاز فقط، على سبيل المثال، أو عرض شيء ما للمستخدم مباشرةً داخل تطبيقك.
// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val urlAttachment = TextAttachment(
text = groupAddUrl,
type = TextAttachment.Type.URL,
)
val content = ShareableContent.withText(urlAttachment)
val appLink = AppLink("https://myapp.com/group/invite")
val request = SharingRequest.withContent(content, appLink)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)لذا، مرة أخرى، ليس عليك فتح تطبيقك على الجهاز المستلِم.
وباستخدام ميزة "النقر للمشاركة"، سيتم فتحها تلقائيًا.
وبالطبع، إذا أردت أن تتفاعل واجهة المستخدم مع عملية نقل الملفات، فما عليك سوى استخدام Kotlin Flow، وستحصل على جميع الأحداث لتتمكّن من معالجة ذلك.
// Final API may undergo further updates
scope.launch {
for (event in session.sharingEvents) {
when (event) {
is SharingEvent.SendComplete ->
println("Send finished")
is SharingEvent.ReceiveComplete ->
println("Received, files=${event.content?.files?.size}")
else -> println("other event")
}
}
}لقد رأينا الكثير اليوم حتى الآن، لذا سأختتم حديثي.
ولا داعي للقلق، سيكون لديك بضع دقائق لأخذ استراحة.
تُعدّ واجهة CompanionDeviceManager أفضل واجهة برمجة تطبيقات للتعامل مع أي تفاعلات عبر البلوتوث، وتفاعلات البلوتوث المستمرة، كما أوضح "هانروي" سابقًا.
إذا كان تطبيقك يتضمّن أي مكالمات عبر بروتوكول الإنترنت، ليس من الضروري أن تكون هذه هي حالة الاستخدام الرئيسية، بل يمكن أن تكون، على سبيل المثال، جزءًا من تطبيقك يتيح للمستخدم الاتصال بفريق الدعم.
في هذه الحالة أيضًا، أفضل طريقة للتعامل مع مكالمات الصوت عبر بروتوكول الإنترنت على Android هي استخدام Jetpack Telecom.
أخيرًا، لمشاركة الملفات بين الأجهزة أو فتح التطبيقات، كما ذكرتُ، من خلال النقر للمشاركة، فإنّ Quick Share هو الحلّ.
لذا، شكرًا للجميع، وقبل أن تذهبوا، شيء سريع، سنكون في الخارج بعد ذلك للإجابة على أسئلتكم.
يمكنكم طرح أي أسئلة لديكم ضمن ساعات عملنا، بشأن جميع حالات الاستخدام التي عرضناها.
ولا تنسَ مشاركة أي من معلوماتك من حديثك أو جلسات أخرى، من الواضح، على وسائل التواصل الاجتماعي.
لدينا مؤتمر AndroidDevSummit#.
مرة أخرى، من المهم جدًا بالنسبة إلينا معرفة أنّ لدينا تفاعلاً، وأنّ لدينا أشخاصًا يستمتعون حقًا بالفعالية.
يساعدنا ذلك في معرفة أنّ لدينا منتدى نريد رؤيته مرة أخرى.
ويمكنك الحصول على تعليقة اجتماعية يمكنك إضافتها إلى سلسلة مفاتيح Android bot من مكتب المعلومات.
هل فعلت ذلك بشكل صحيح؟
شكرًا لك.
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />class BleForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
…
return START_STICKY
}
}<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_MEDICAL" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
<uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />val paringRequest = AssociationRequest.Builder()
.addDeviceFilter(deviceFilter)
.setSingleDevice(true)
.setDeviceProfile(AssociationRequest.DEVICE_PROFILE_MEDICAL)
.build()
companionDeviceManager.associate(paringRequest, ContextCompat.getMainExecutor(this),
object : CompanionDeviceManager.Callback() {
override fun onAssociationPending(intentSender: IntentSender) {
intentSenderLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
}
override fun onAssociationCreated(associationInfo: AssociationInfo) { … }
override fun onFailure(error: CharSequence?) { … }
}
)class BleCompanionDeviceService : CompanionDeviceService() {
override fun onDevicePresenceEvent(event: DevicePresenceEvent) {
when (event.event) {
DevicePresenceEvent.EVENT_BLE_APPEARED -> onDeviceAppeared(event.associationId)
DevicePresenceEvent.EVENT_BLE_DISAPPEARED -> onDeviceDisappeared(event.associationId)
}
}
private fun onDeviceAppeared(associationId: Int) { … }
private fun onDeviceDisappeared(associationId: Int) { … }
}<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />// Initialize CallsManager & register supported capabilities once
val callsManager = CallsManager(context)
callsManager.registerAppWithTelecom(
capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
)val attributes = CallAttributesCompat(
displayName = "Kukk Nat",
address = Uri.parse("tel:+16505550199"),
direction = CallAttributesCompat.DIRECTION_INCOMING,
callType = CallAttributesCompat.CALL_TYPE_AUDIO_CALL
)
callsManager.addCall(
attributes, onAnswer, onDisconnect, onSetActive, onSetInactive
) {
// Coroutine CallControlScope active for the duration of the call
}callsManager.addCall(attributes, onAnswer, onDisconnect, onSetActive, onSetInactive) {
// 1. React to hardware mute toggles from Watch, Car, or Headset
launch {
isMuted.collect { muted -> audioEngine.setMicMuted(muted) }
}
// 2. Observe available endpoints & request declarative switches
launch {
currentCallEndpoint.collect { endpoint -> updateUi(endpoint) }
}
requestEndpointChange(preferredBluetoothEndpoint)
}// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val attachment = FileAttachment(
fileName = "report.pdf",
size = 1_048_576L,
uri = fileUri,
mimeType = "application/pdf",
// Sensitive content will be masked in preview
isSensitiveContent = false,
)
val content = ShareableContent.withFiles(listOf(attachment))
val request = SharingRequest.withContent(content)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val request = SharingRequest.withoutContent()
val session = quickShareClient.startSharingSession(request, lifecycleOwner)
// Open the Quick Share overlay in receive mode
session.launchWindow(activity, contentSelectionIntent = null)// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val urlAttachment = TextAttachment(
text = groupAddUrl,
type = TextAttachment.Type.URL,
)
val content = ShareableContent.withText(urlAttachment)
val appLink = AppLink("https://myapp.com/group/invite")
val request = SharingRequest.withContent(content, appLink)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)// Final API may undergo further updates
scope.launch {
for (event in session.sharingEvents) {
when (event) {
is SharingEvent.SendComplete ->
println("Send finished")
is SharingEvent.ReceiveComplete ->
println("Received, files=${event.content?.files?.size}")
else -> println("other event")
}
}
}