תשתית לאפליקציות

אינטראקציות חלקות בין מכשירים

רמקולים

מידע כללי

מידע על ההרצאה

המשתמשים עוברים בין טלפונים, גאדג'טים לבישים וציוד היקפי לאודיו, ולכן חשוב לספק חוויות שימוש חלקות בכמה מכשירים כדי לעודד מעורבות. עם זאת, הסתמכות על גישות סטנדרטיות של רשתות מובילה לעיתים קרובות להתרוקנות הסוללה, לעליות חדות בזמן האחזור ולסשנים מקוטעים של משתמשים.

במדריך המעמיק הזה נציג ממשקי Android API ייעודיים שנועדו לתקשורת בין מכשירים שונים, ומציעים דפוסים יעילים מבחינת זיכרון וצריכת חשמל כדי שהמעברים בין המכשירים יהיו מיידיים, אמינים ואינטואיטיביים.

סיכום

  • התכונה "שיוך של מכשירים לאפליקציות נלוות" מחליפה את השירותים שפועלים בחזית עבור ציוד היקפי של Bluetooth. היא מפעילה אפליקציות על סמך הנוכחות שלהן ומנהלת את החיבורים בלי לרוקן את הסוללה. ב-Android 17 נוספו פרופילים חדשים במיוחד למכשירים רפואיים ולצמידי כושר.
  • ‫Jetpack Telecom פועלת כמתאמת שיחות מערכת לאפליקציות VoIP במכשירים מחוברים כמו שעונים ומכוניות, החל מ-Android 8. היא גם מסנכרנת פעולות של השתקה וניתוק באמצעות CallControlScope.
  • התכונות הקרובות של Jetpack Telecom 1.1.0 כוללות היסטוריית שיחות מאוחדת בחייגן המערכת (SDK 36.1 ואילך) ופריסת רשת 5G אוטומטית ברשתות נתמכות של ספקי סלולר.
  • בשנה הבאה נשיק את ערכת הפיתוח של "שיתוף מהיר", כדי שמפתחים יוכלו להטמיע שיתוף קבצים מקומי ומוצפן מקצה לקצה ישירות באפליקציות ל-Android ול-iOS. התכונה "שיתוף בהצמדה" (שהושקה ב-Pixel 11) יכולה אפילו להוציא ממצב שינה באופן אוטומטי אפליקציה סגורה שמקבלת את הקובץ.

משאבים

Gemini Notebook

נושאים

  • מקושר למכשיר אחר
  • companion-device-manager
  • jetpack-telecom
  • שיתוף מהיר
  • Bluetooth
  • וויי-פיי
  • מקצה לקצה (P2P)

תמליל

הורדת התמליל

מבוא לאינטראקציות חוצות-מכשירים

  • שלום לכולם,

  • ברוכים הבאים לקורס "איך לאפשר אינטראקציות חלקות בין מכשירים".

  • אני הנרוי, והיום הצטרפו אליי הקולגות שלי, יאסין ונטראג'.

  • כשאנחנו מדברים על קישוריות, אנחנו בדרך כלל חושבים על קישור בסיסי לרשת או על אוזניות Bluetooth.

  • ב-Android, הקישוריות רחבה הרבה יותר.

  • המטרה היא לגרום למכשירים לעבוד טוב יותר ביחד.

  • היום נסקור תרחישים נפוצים של המרות חוצות-מכשירים ונראה איזה API מתאים לכל אחד מהם.

  • במשך הרבה זמן, פיתחנו אפליקציות למכשיר אחד בלבד, לטלפון.

  • ואז הגיעו הגאדג'טים הלבישים.

  • תוך זמן קצר, שעונים חכמים שמחוברים לטלפון, מסנכרנים נתונים ברקע או מקבלים שיחות ישירות הפכו לנפוצים מאוד.

  • הוספת אוזניות כפתור אלחוטיות.

  • עכשיו המשתמשים יכולים להחליף את מכשיר האודיו באמצע השיחה, מהשעון לטלפון, לאוזניות או לרכב.

  • לאחרונה, משתמשים רבים נושאים איתם גם מכשיר שני עם מסך גדול, כמו מכשיר מתקפל או טאבלט.

  • הם מצפים שהאפליקציה תפעל בצורה חלקה בכל המכשירים האלה.

  • זה כולל גם מחשבים ניידים ומכשירי Chromebook, שבהם המשתמשים יכולים להפעיל אפליקציות ל-Android ולהישאר מחוברים ישירות מהשולחן שלהם.

  • כבר בשנת 2023, למשתמש ממוצע היו בין שלושה לארבעה מכשירים.

  • המשתמשים מצפים ליותר מסתם פונקציונליות עצמאית במכשירים האלה.

  • הם מחפשים חוויה יוצאת דופן של עבודה משותפת.

  • החוויה הזו של "טובים השניים" ברורה במיוחד כשאנשים נמצאים באותו חדר, כמונו היום, ורוצים דרך קלה לשתף תמונות, קישורים או אפליקציות עם האנשים שלידם.

  • לפי הנתונים, הסביבה העסקית הזו עצומה וגדלה במהירות.

  • השנה יש כ-22 מיליארד מכשירי IoT ברחבי העולם, והמספר הזה גדל ב-14% בהשוואה לשנה הקודמת.

  • מבין כל נקודות הקצה האלה, Wi-Fi מהווה שליש ו-Bluetooth מהווה רבע.

  • קישוריות בין מכשירים היא כבר לא מקרה קצה.

  • היא הפכה לחלק בלתי נפרד מחיי היום-יום.

  • עם זאת, רוב האפליקציות עדיין מפתחות את התכונות שלהן לפעולה בכמה מכשירים על בסיס ממשקי API של רשת ו-Bluetooth ברמה נמוכה, מה שיוצר שלושה אתגרים.

  • קודם כל, הסוללה והזיכרון.

  • סריקות ושירותים שפועלים ברקע מרוקנים את הסוללה והזיכרון.

  • שנית, זמן האחזור.

  • עיכובים בלתי צפויים משבשים שיחות וסנכרון נתונים בזמן אמת.

  • שלישית, קשיים בחיבור.

  • ניהול הגילוי, ההתאמה והחיבור מחדש בכל המכשירים הוא תהליך מורכב שעלול לפגוע בחוויית המשתמש.

  • כדי לפתור את הבעיות האלה, יצרנו ממשקי API ייעודיים בשלושה תחומים עיקריים, שמאפשרים להשתמש בתכונות שפועלות בכל המכשירים בצורה פשוטה מאוד.

  • אני אתחיל עם אפליקציות נלוות, ואז נטראג' ידבר על אפליקציות VoIP, ואחר כך יאסין ידבר על שיתוף P2P.

סקירה כללית של אפליקציות נלוות

  • קודם נתחיל עם אפליקציות נלוות.

  • אפליקציה נלווית היא אפליקציה שמתקינים בטלפון כדי שמכשיר נלווה, כמו שעון חכם או חיישן גלוקוז, יוכל לפעול איתו בצורה תקינה.

  • בקטע הזה נתמקד במכשירים שמחוברים ב-Bluetooth.

  • בדרך כלל יש ארבע קטגוריות של מכשירים נלווים.

  • הראשונה היא מכשירים רפואיים, כמו חיישני גלוקוז ומכשירי ניטור של סימנים חיוניים.

  • וגם מכשירים לבישים, כמו שעונים חכמים וצמידי כושר.

  • אוזניות עם מיקרופון, עם התאמה מהירה ואמצעי בקרה חכמים.

  • וערכות לרכב, לדברים כמו מפתחות דיגיטליים לרכב ושיחות ללא מגע יד.

  • לכל קטגוריה יש תהליכים משלה שהמשתמשים עוברים, אבל כולם תלויים בחיבור אמין בין המכשיר לטלפון.

  • במכשירים רפואיים, החיבור הזה יכול להיות קריטי להצלת חיים.

  • בואו נסקור ארבעה צרכים נפוצים שאנחנו רואים בקטגוריות האלה.

  • קודם כל, ניהול מכשירים.

  • האפליקציה צריכה למצוא את המכשיר הנכון מבין כל המכשירים שבסביבה, כמו השעון שכאן, ולא את האוזנייה או הרמקול, ואז להתחיל את תהליך ההתאמה כדי להתחבר.

  • שנית, בדיקת נוכחות.

  • האפליקציה צריכה לדעת מתי המכשיר נמצא בקרבת מקום ומתי הוא לא.

  • כשהמכשיר מופיע, האפליקציה צריכה דרך להתעורר כדי שתוכל לבצע את הפעולה שלה גם אם המשתמש לא פתח אותה.

  • שלישית, תחזוקת החיבור.

  • אחרי שהאפליקציות מתחברות, הן צריכות שהחיבור יהיה אמין ורציף.

  • אם החיבור מתנתק, למשל אם המשתמש יוצא מהטווח וחוזר, האפליקציה צריכה להתחבר מחדש באופן אוטומטי, גם אם היא לא פועלת באותו זמן.

  • רביעית, סנכרון והעברת נתונים.

  • זה הערך האמיתי.

  • סנכרון קריאות של חיישנים, שליחת עדכוני קושחה או שליחת הגדרות למכשיר.

  • במכשירים רפואיים, זה יכול להיות קריטי.

  • אם המכשיר לא יקרא את הנתונים, יכול להיות שלא תתקבל התראה.

  • כדי שכל זה יקרה, תהליך האפליקציה צריך להיות פעיל ומחובר בזמן הנכון.

  • אז איך האפליקציות עונות על כל הצרכים האלה?

  • בעבר, אפליקציות הסתמכו במידה רבה על שירותים שפועלים בחזית.

  • הנה דוגמה להטמעה אופיינית.

  • אם כבר יצרתם אפליקציה נלווית בעבר, יכול להיות שהתהליך הזה מוכר לכם.

  • אתם מצהירים על הרשאות לסריקת Bluetooth ולהתחברות, על הרשאות לשירות שפועל בחזית ועל סוג השירות שפועל בחזית של המכשיר המחובר. לאחר מכן אתם מצהירים על הרשאה לשליחת התראות, כי שירות שפועל בחזית צריך להציג התראה קבועה.

    <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 תומך בפרופילים של מכשירים.

  • באמצעות פרופיל מכשיר, המשתמש מעניק את כל ההרשאות שהאפליקציה צריכה בלחיצה אחת, במקום שרשרת של תיבות דו-שיח של הרשאות.

  • כך אפשר לשפר משמעותית את חוויית המשתמש ואת שיעור ההמרה.

  • הפרופיל לשעון זמין החל מגרסה 12 של Android, והפרופיל למשקפיים זמין החל מגרסה 14 של Android.

  • ב-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 ל-true כי אנחנו רוצים רק התאמה אחת, ולהגדיר את הפרופיל כרפואי.

  • ואז מתקשרים לנציג.

  • ה-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 יש כמה מגבלות שמפתחים צריכים לזכור.

  • קודם כל, גרסאות של מערכת ההפעלה.

  • רוב התכונות של CDM הגיעו ב-Android 12 ו-13.

  • אבל כמה שיפורים מגניבים הגיעו ב-Android 16 והפרופילים החדשים ב-Android 17.

  • לכן, צריך לבדוק את גרסת מערכת ההפעלה בזמן הריצה ולהגדיר חלופה לגרסאות ישנות יותר.

  • עם זאת, גרסת ה-SDK המינימלית עולה מדי שנה, וגרסת ה-SDK המינימלית נוטה לעלות בהתאם.

  • כדי להתמודד עם הלחץ על הזיכרון שהאפליקציות מתמודדות איתו עכשיו ויתמודדו איתו בעתיד, אנחנו ממליצים מאוד להעביר את האפליקציות משירות שפועל בחזית ל-CDM בהקדם האפשרי.

  • אם יש לכם תרחיש לדוגמה שאי אפשר לתמוך בו כרגע באמצעות CDM, נשמח לדעת.

  • שנית, נכון להיום, CDM תומך בסריקה ברקע, אבל לא בפרסום ברקע.

  • אנחנו כמעט לא מקבלים בקשות כאלה.

  • אבל אם האפליקציה שלך צריכה שהטלפון יפעל כציוד היקפי ויפרסם באופן רציף ברקע, נשמח לקבל מידע נוסף על כך.

  • שלישית, CDM משתמש במצב סריקה בצריכת חשמל נמוכה.

  • הוא מקריב קצת מהתגובה בזמן אמת לטובת הסוללה והזיכרון.

  • לכן, אל תצפו להתעורר מיד.

  • ולסיום, אין הצצה למטען הייעודי (payload).

  • אי אפשר להירשם למאפיין ספציפי או לנתוני פרסום.

  • אבל כשהמכשיר נמצא בטווח, האפליקציה מתעוררת.

  • כך הוא יכול להתחבר ולבדוק את המטען הייעודי (payload) לפי הצורך.

  • זה כל מה שצריך לדעת על אפליקציות נלוות ו-CDM.

  • אני מעביר את השרביט לנטראג' שידבר על אפליקציות VoIP.

בעיות בשיחות VoIP בין מכשירים שונים

  • ‫CompanionDeviceManager הוא הכלי המתאים אם לאפליקציה יש ציוד היקפי ייעודי והיא מתקשרת איתו באופן רציף.

  • שלום לכולם,

  • אני נטראג' קיי אר.

  • עכשיו אדבר על התרחיש הבא של שימוש בכמה מכשירים, שהוא שיחות בזמן אמת.

  • היום, הדרך שבה אנשים מטפלים בשיחות משתנה.

  • יותר מ-40% מהשיחות מתבצעות ברשתות VoIP.

  • ול-Android יש תפקיד מרכזי בזה.

  • למעשה, 71% מהשיחות האלה מתבצעות במכשירי Android, כלומר כל שיחה שנייה.

  • לא מדובר רק בשיחות.

  • גם סביבת המכשירים שאנשים מחזיקים בבעלותם גדלה באופן משמעותי.

  • יש יותר מ-250 מיליון מכוניות עם Android Auto שנוסעות בכבישים, ואנחנו גם שולחים יותר ממיליארד מכשירי Bluetooth למשתמשים.

  • אוזניות, שעוני Wear OS, מכשירי Android, מכוניות עם Android Auto, רמקולי Bluetooth – כל אלה מכשירים שהמשתמשים מחזיקים בנוסף לטלפון, והם רוצים שהשיחה תעבור בין כל המכשירים האלה בצורה חלקה.

  • לדוגמה, המשתמש מקבל שיחה, הוא מסתכל על השעון כדי לראות את פרטי המתקשר.

  • הם יענו לשיחה בשעון.

  • הם היו משתיקים או מבטלים את ההשתקה לפי הצורך, מכניסים את האוזניות וממשיכים בשיחה.

  • אם הם רוצים לצאת, הם נכנסים לשיחה, נכנסים לרכב ומצפים שהשיחה תעבור בצורה חלקה לרכב.

  • כך, הפעולה הזו הפכה לשגרה רגילה עבור המשתמש, וזו לא חוויית פרימיום.

  • מבחינת המשתמשים, זו דרישה בסיסית, ואנחנו כמפתחי Android צריכים לספק את חוויית השימוש החלקה הזו באפליקציות ה-VoIP שלנו.

  • אבל אם תנסו לבנות את כל זה בעצמכם מאפס, תיתקלו בשלוש בעיות עיקריות.

  • הראשונה, פרגמנטציה עם שקעי Bluetooth.

  • המערכת לא יודעת שהאפליקציה שלכם מטפלת בשיחה, ולכן תצטרכו ליצור שקעי Bluetooth וסשנים של מדיה בהתאמה אישית כדי שהשיחה תוצג בשעון או ב-Android Auto.

  • הבעיה השנייה היא מיקוד האודיו.

  • בגלל שצריך להתמודד עם ממשקי API של AudioManager ברמה נמוכה וממשקי API של Bluetooth, יש זליגות אודיו, וזה לא בעדיפות גבוהה במערכת, נכון?

  • באפליקציה שלכם מתבצעת שיחת VoIP, וכשנכנסת שיחה דרך ספק הסלולר, היא קוטעת את השיחה הקיימת והמודם משתלט על האודיו, כך שהאפליקציה שלכם נשארת בלי אודיו.

  • גרוע מכך, בכל פעם שהשיחה הסלולרית מסתיימת, המיקוד של האודיו לא חוזר לאפליקציה.

  • הבעיה השלישית היא סנכרון.

  • המשתמשים יכולים ללחוץ על השתקה בשעון, אבל בממשק המשתמש של הטלפון עדיין יוצג שהשיחה לא מושתקת.

  • זה יוצר בלבול גדול.

  • המשתמש מנתק את השיחה מההגה של הרכב, אבל בטלפון עדיין מופיעה התראה על שיחה פעילה.

  • זה מאוד מתסכל את המשתמש כשזה קורה.

חדש: Jetpack Telecom

  • כדי לפתור את כל הבעיות האלה, פיתחנו את Jetpack Telecom.

  • לכן, אתם רושמים את השיחה פעם אחת ב-Jetpack Telecom באמצעות CallsManager, ו-Jetpack Telecom פועל כמתאם מרכזי של קריאות למערכת.

  • הוא אחראי להעביר את השיחה לשעון, ל-Android Auto ולמכשירים אחרים של המשתמש.

  • הוא גם מטפל במיקוד האודיו.

  • והכי חשוב, כל זה פועל בצורה חלקה, אפילו במכשירים עם Android 8.

  • הצגת השיחה בשעון או במסך הרכב היא רק חצי מהסיפור, נכון? כי המשתמשים כמובן רוצים לשלוט במצב השיחה מהמכשירים האלה.

  • הם רוצים להשתיק את השיחה מהשעון או לבטל את ההשתקה לפי הצורך.

  • הם רוצים לנתק את השיחה מההגה של הרכב או בלחיצה כפולה על האוזניות.

  • לכן, Jetpack Telecom מאזין לאירועי החומרה האלה ומציג אותם כקריאות חוזרות (callbacks) במקום אחד, שהוא CallControlScope.

  • כל מה שצריך לעשות הוא להאזין לקריאות החוזרות האלה, לטפל במנוע השיחות ולעדכן את מצב השיחה בהתאם.

  • ‫Jetpack Telecom אחראי לסנכרן את מצב השיחה בין המכשירים.

  • אז מאחורי הקלעים, הספרייה מספקת יתרונות בשני תחומים.

  • קודם כל, ברור שזה פותר את כל הבעיות שדיברנו עליהן.

  • אז במקום שתצטרכו להתעסק עם ממשקי API של AudioManager ברמה נמוכה, הספרייה מספקת נקודות קצה של שיחות, מנהלת את הרשאת האודיו ומסנכרנת בין המכשירים.

  • הוא אחראי על העברת השיחה בין המכשירים.

  • הוא גם מטפל בשיחות סלולריות נכנסות.

  • אבל יש עוד יתרון לספרייה הזו: היא מבוססת על Kotlin, אז בכל פעם שמוסיפים שיחה עם Jetpack Telecom, נפתח היקף של שגרת המשך (coroutine) ב-Kotlin.

  • כך, הוא מנצל את היתרונות של שגרות ההמשך של Kotlin, ושגרת ההמשך של Kotlin נשארת פעילה בדיוק עד לסיום השיחה.

  • לכן, כל משימה שהפעלתם שקשורה לשיחה נמחקת אוטומטית ברגע שהשיחה מתנתקת.

  • כך לא יישארו משימות מיותרות, והספרייה תטפל גם במחזור החיים של הקריאה.

  • אז עכשיו אתם בטח חושבים שהספרייה המדהימה הזו נהדרת ואתם רוצים להטמיע אותה, נכון?

  • זה פשוט כמו 1, 2, 3.

  • בשלב הראשון, צריך להצהיר על התלות.

  • אחרי זה, מצהירים על ההרשאה הנדרשת, שהיא 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.

  • בשלב 3, המערכת מאזינה לפעולות שהמשתמש עומד לבצע מהפלטפורמות המרוחקות האלה.

  • לכן, בגלל שיש לך את CallControlScope, כל מה שצריך לעשות הוא לאסוף את ה-Kotlin flows.

    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 כ-true בתוך האובייקט CallAttributesCompat.

  • אובייקט המטא-נתונים שדיברתי עליו קודם, פשוט מגדירים אותו כ-true והשיחה לא תתועד במערכת ולא תופיע בחייגן של המערכת.

  • התכונה המדהימה השנייה שתגיע עם גרסה 1.1.0 היא פריסת רשת 5G.

  • לכן, ברשתות של ספקים נתמכים, הרשתות מספקות לכם פרוסות רשת פרימיום. כלומר, במקרה שימוש מסוים, הספק ייתן לכם פרוסת רשת עם זמן אחזור קצר וללא הפרעות אחרות, כדי שתיהנו מאיכות טובה יותר ומחוויית שימוש טובה יותר.

  • לכן, ברשתות של ספקים נתמכים, התכונה הזו זמינה באופן מיידי, כך שלא נדרש מאמץ נוסף מצידכם, אפילו לא שורת קוד אחת.

  • אחרי שתשלבו את הספרייה, השיחות שלכם ינצלו את פרוסות הרשת האלה ותיהנו מזמן אחזור קצר יותר ומביצועים טובים יותר.

  • אבל אם מסיבה כלשהי אתם לא רוצים להשתמש בתכונה הזו, אתם יכולים לבטל את ההסכמה להשתמש בה גם באמצעות הדגל CAPABILITY_OPT_OUT_OF_PREMIUM_NETWORK.

  • אפליקציות התקשורת המובילות בעולם, כמו Google Meet, ‏WhatsApp, ‏KakaoTalk ו-Zoho Arattai, כבר הטמיעו את Jetpack Telecom.

  • אז עכשיו זה תלוי בכם, עכשיו אתם צריכים לשלב את הספרייה הזו באפליקציה שלכם ולספק למשתמשים חוויה חלקה וטובה יותר.

  • עכשיו, אחרי שדיברנו על שיחות בזמן אמת, אני רוצה להעביר את המיקרופון לקולגה המדהים שלי, יאסין, שידבר על "שיתוף בהצמדה".

  • תודה.

אתגרים בשיתוף בין משתמשים

  • אוקיי חברים, אני יודע שאתם קצת עייפים, אז אל דאגה, זה החלק האחרון.

  • אז אני אדבר על שיתוף קבצים בין עמיתים, אבל יש עוד תרחישי שימוש שאפשר לעשות גם עם שיתוף בין עמיתים.

  • בכל יום משתפים יותר מ-14 מיליארד תמונות וסרטונים באפליקציות של רשתות חברתיות ואפליקציות הודעות, וזה מספר עצום.

  • ובדרך כלל כל הקבצים האלה משותפים דרך הענן, אז אתם מצלמים את התמונה, מעלים אותה דרך השירות המועדף עליכם ואז האדם השני צריך להוריד אותה באותו אופן בצד השני.

  • אותו דבר קורה כשמנסים להזמין חבר לרשת חברתית, או להזמין מישהו לערוך ביחד פלייליסט או מסמך. בדרך כלל צריך להקליד את שם המשתמש או את כתובת האימייל שלו, וזה לוקח קצת זמן במקלדת.

  • אתם עלולים לעשות טעות, וזו לא חוויה נעימה, במיוחד כששניכם יושבים ממש אחד ליד השני, ואז אתם אומרים לעצמכם, "אוי, אני צריך לשלוח דרך הענן, אני צריך לחכות".

  • זה פשוט לא טוב.

  • היום לכולנו יש טלפונים, מכשירים, טאבלטים ומכשירים מתקפלים די משוכללים, ואנחנו מצלמים בהם תמונות מדהימות.

  • אבל כשמשתפים דרך חלק מהרשתות החברתיות או חלק מאפליקציות ההודעות האלה, ברור שבגלל בעיות בביצועים ובאחסון, הן נדחסות, ובסופו של דבר, במסך המדהים שלכם, הן נראות די מפוקסלות או פשוט לא באיכות הכי טובה.

  • זו לא החוויה הכי טובה שאתם רוצים לקבל במכשיר פרימיום.

  • דבר שני, הקבצים עכשיו די גדולים.

  • תמונה אחת יכולה בקלות להיות בגודל 10MB, וסרטון של 5 דקות יכול להיות בגודל 500MB, אז לוקח הרבה זמן להעלות ולהוריד את כל זה.

  • ולבסוף, בצד השני, צריך להמתין לזמן האחזור הזה.

  • לפעמים אתם עולים על אוטובוס צפוף, ובזמן שהאוטובוס נוסע, זמן האחזור לא הכי טוב, הכיסוי של הרשת לא הכי טוב, ולפעמים העברת הקובץ נכשלת. לפעמים בגלל זמן האחזור הארוך הזה, יכול להיות שתהיה חריגה מהזמן המוקצב והעברת הקובץ תבוטל, וזה ממש מתסכל את המשתמש.

  • אז במקום להסתמך על הענן כדי לשתף את כל הקבצים האלה ולחכות שכל זה יקרה, למה לא להשתמש ברשת מקומית, כמו Bluetooth או Wi-Fi?

  • אבל אם אתם מנסים לעשות את זה בעצמכם, ברור שאתם צריכים להתמודד עם מנהל ה-Bluetooth, מנהל ה-Wi-Fi, לכתוב פרוטוקול משלכם, כלומר, לא כולם רוצים להשקיע זמן בזה.

  • אבל למעשה, יש כבר משהו כזה ב-Android.

  • היא כבר זמינה, ואולי שמעתם עליה – "שיתוף מהיר".

סקירה כללית על "שיתוף מהיר"

  • התכונה "שיתוף מהיר" ב-Android מאפשרת לכם להשתמש בהפשטה הזו שכבר קיימת במערכת, ואתם יכולים פשוט לשתף קבצים עם מכשיר אחר בלי לנהל את כל זה.

  • היא כבר זמינה בגיליון השיתוף שלכם, והיא עוברת בין הפרוטוקולים השונים כדי למצוא את הפרוטוקול הכי טוב למכשיר שלכם בין שני המכשירים. היא זמינה ב-Marshmallow ומעלה, שזה קהל די גדול בימינו, נכון?

  • ולבסוף, ממשק המשתמש וחוויית המשתמש המוכרים שאתם רואים בצד, ממש עוזרים למשתמשים שלכם.

  • הם מכירים את ממשק המשתמש, הם לא צריכים ללמוד איך להשתמש בו, הם כבר ראו אותו בעבר.

  • אבל אנחנו יודעים שהמפתחים רוצים גם לשלוט בחוויה.

  • אתם מכירים את האפליקציה שלכם, ואתם כנראה מכירים את המשתמשים שלכם יותר טוב מאיתנו.

  • אז למה לא להוסיף את "שיתוף מהיר" ישירות לאפליקציה?

  • אז התחלנו לדבר עם מפתחים וראינו שהתכונה "שיתוף מהיר" כבר די נפוצה.

  • היא זמינה ב-Android, ב-Chromebook, לאחרונה ב-Google Books וגם ב-Windows, כלומר ביותר מ-3 מיליארד מכשירים, זה עצום.

  • ב-Pixel 11 הוספנו גם את האפשרות לשתף בהצמדה.

  • במקום לבחור את המכשיר שאיתו רוצים לשתף, אפשר פשוט ללחוץ על NFC, ואנחנו נבצע את הגילוי ישירות בשבילכם.

  • אז אתם יכולים לבחור תמונה, אבל אתם יכולים גם לבחור קובץ, פשוט לוחצים על שני מכשירים והם משותפים ישירות באמצעות נגיעה.

  • ושוב, אנחנו מכירים מפתחים שרוצים לשלוט בחוויה.

  • אנחנו יודעים שזו הדרך הכי טובה לאפשר למשתמשים להשתמש בתכונה ספציפית, במקרה הזה, שיתוף קבצים.

  • אז למה לא להטמיע את "שיתוף מהיר" באפליקציה?

  • לכן, בשנה הבאה נשיק את ערכת ה-SDK של "שיתוף מהיר" ישירות בשבילכם.

  • תוכלו להשתמש בה ישירות באפליקציה, והיא כוללת אבטחה מובנית, כמו הצפנה מקצה לקצה בין שני המכשירים.

  • ההעברות מתבצעות בין הרשתות המקומיות האלה, אז אתם נהנים מהמהירות המלאה, כמובן ב-Wi-Fi הביתי, אבל גם בין שניכם, אפשר פשוט ליצור נקודה לשיתוף אינטרנט באופן אוטומטי, אתם לא צריכים להתעסק עם זה.

  • במכשירים שתומכים ב-Wi-Fi ישיר, התכונה כבר כלולה.

  • והקבצים שצילמתם במצלמה המדהימה שלכם, הם ישמרו על הרזולוציה והפרטים הטובים ביותר שלהם, כך שתוכלו לראות אותם יפה במסך הגדול שלכם.

  • ומכיוון שהיא מסתמכת על רשתות מקומיות, אפשר להשתמש בה ברכבת התחתית, אפילו בחלק הכי עמוק שלה, עדיין תהיה רשת כדי לשתף עם החבר.

  • גם במטוס, בגובה של לא יודע כמה אלפי רגל, בשמיים, עדיין אפשר לשתף את הקבצים האלה.

  • ולבסוף, למפתחים, זה חיסכון עצום בעלויות מבחינת נתונים, כי קודם כל, המשתמשים לא צריכים להשתמש בחבילת הגלישה שלהם, וזה עניין גדול כשהם מטיילים כתיירים.

  • בנוסף, אתם לא צריכים לתחזק את זה בשרתים שלכם.

  • בשנה שעברה הודענו למפתחים ולמשתמשים שבאמצעות "שיתוף מהיר" הם יוכלו לשתף קבצים עם מכשירי אייפון דרך AirDrop.

  • עם "שיתוף מהיר", אנחנו מביאים את אותו הדבר למפתחים.

  • אנחנו מתכננים להשיק ערכת SDK של "שיתוף מהיר" ל-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 שורות קוד בשני הצדדים.

  • אנחנו יודעים שעם "שיתוף מהיר" ו"שיתוף בהצמדה", היכולת לשתף קבצים פשוט על ידי הצמדת מכשירים מאפשרת גם תרחיש שימוש חדש.

  • יכול להיות שהאפליקציה שלכם תיסגר במכשיר השני, ואחרי שתלחצו, ברגע שהקובץ יועבר, האפליקציה תיפתח באופן אוטומטי ותקבלו את ה-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 הוא ה-API הכי טוב לטיפול בכל אינטראקציה עם Bluetooth, כולל אינטראקציות רציפות.

  • אם באפליקציה יש שיחות VoIP, זה לא חייב להיות התרחיש העיקרי לשימוש בה. לדוגמה, יכול להיות שבאפליקציה יש לקוחות תמיכה והמשתמש יכול להתקשר לתמיכה.

  • גם במקרה הזה, הדרך הכי טובה לטפל בשיחות VoIP ב-Android היא Jetpack Telecom.

  • ולסיום, כמו שציינתי, עבור כל שיתוף קבצים מקצה לקצה או פתיחת אפליקציה, כפי שציינתי, עם שיתוף בהצמדה, שיתוף מהיר הוא הפתרון.

  • אז תודה לכולם, ולפני שאתם הולכים, רק דבר קטן, אנחנו נהיה בחוץ אחר כך כדי לענות על השאלות שלכם.

  • יש לנו שעות קבלה אם יש לכם שאלות, אתם יודעים, בכל תרחישי השימוש שהצגנו.

  • ואל תשכחו לשתף את מה שלמדתם מההרצאה שלכם או ממפגשים אחרים, כמובן, ברשתות החברתיות.

  • יש לנו את #AndroidDevSummit.

  • שוב, חשוב לנו מאוד לדעת שיש מעורבות, שיש אנשים שבאמת נהנים מהאירוע.

  • זה עוזר לנו לדעת שיש לנו קהילה שאנחנו רוצים לראות שוב.

  • בנוסף, אתם יכולים לאסוף בדלפק המידע את הצ'ארם של הרשת החברתית ולהוסיף אותו לאוסף המפתחות של הרובוט של Android.

  • עשיתי את זה נכון?

  • תודה.

קוד

קטעי קוד

קטע קוד ב-XML בדקה 06:17 בסרטון Overview of companion apps
<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" />
קטע קוד ב-Kotlin בדקה 06:34 בסרטון "סקירה כללית של אפליקציות נלוות"
class BleForegroundService : Service() {
   override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
      …
      return START_STICKY
   }
}
קטע קוד ב-XML בדקה 08:03 בסרטון Implementation using Companion Device Manager
<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" />
קטע קוד ב-Kotlin בדקה 08:38 בסרטון Implementation using Companion Device Manager
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?) { … }
   }
)
קטע קוד ב-Kotlin בדקה 09:47 בסרטון Implementation using Companion Device Manager
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) { … }
}
קטע קוד XML בדקה 18:29 בסרטון Introducing Jetpack Telecom
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
קטע קוד ב-Kotlin בדקה 18:36 בסרטון "Introducing Jetpack Telecom"
// Initialize CallsManager & register supported capabilities once
val callsManager = CallsManager(context)

callsManager.registerAppWithTelecom(
    capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
                   CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
)
קטע קוד ב-Kotlin בדקה 18:55 בסרטון "Introducing Jetpack Telecom"
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
}
קטע קוד ב-Kotlin בדקה 19:49 בסרטון "Introducing Jetpack Telecom"
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)
}
קטע קוד ב-Kotlin בדקה 29:31 בסרטון "סקירה כללית של שיתוף מהיר"
// 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)
קטע קוד ב-Kotlin בדקה 29:57 בסרטון "סקירה כללית של שיתוף מהיר"
// 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)
קטע קוד ב-Kotlin בדקה 30:23 בסרטון "סקירה כללית של שיתוף מהיר"
// 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 בדקה 30:49 בסרטון "סקירה כללית של שיתוף מהיר"
// 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")
    }
  }
}