Core-Telecom

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

אם אתם רוצים לבדוק הטמעות מעשיות, תוכלו למצוא אפליקציות לדוגמה ב-GitHub:

הגדרת Core-Telecom

מוסיפים את התלות ב-androidx.core:core-telecom לקובץ build.gradle של האפליקציה:

dependencies {
    implementation ("androidx.core:core-telecom:1.0.0")
}

מגדירים את ההרשאה MANAGE_OWN_CALLS ב-AndroidManifest.xml:

<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />

רישום האפליקציה

כדי להתחיל להוסיף שיחות למערכת, צריך לרשום את אפליקציית השיחות ב-Android באמצעות CallsManager. במהלך הרישום, צריך לציין את היכולות של האפליקציה (לדוגמה, תמיכה באודיו ובווידאו):

val callsManager = CallsManager(context)

val capabilities: @CallsManager.Companion.Capability Int =
    (CallsManager.CAPABILITY_BASELINE or
          CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING)

callsManager.registerAppWithTelecom(capabilities)

ניהול השיחות

שימוש בממשקי Core-Telecom API כדי ליצור ולנהל את מחזור החיים של שיחה.

יצירת שיחה

האובייקט CallAttributesCompat מגדיר את המאפיינים של קריאה ייחודית, שיכולה להיות בעלת המאפיינים הבאים:

  • displayName: שם מבצע הקריאה.
  • address: כתובת השיחה (לדוגמה, מספר טלפון, קישור לפגישה).
  • direction: נכנסות או יוצאות.
  • callType: אודיו או וידאו.
  • callCapabilities: תמיכה בהעברה ובהחזקה.

דוגמה ליצירת שיחה נכנסת:

fun createIncomingCallAttributes(
    callerName: String,
    callerNumber: String,
    isVideoCall: Boolean): CallAttributesCompat {
    val addressUri = Uri.parse("YourAppScheme:$callerNumber")

    // Define capabilities supported by your call.
    val callCapabilities = CallAttributesCompat.CallCapability(
        supportsSetInactive = CallAttributesCompat.SUPPORTS_SET_INACTIVE // Call can be made inactive (implies hold)
    )

    return CallAttributesCompat(
        displayName = callerName,
        address = addressUri,
        direction = CallAttributesCompat.DIRECTION_INCOMING,
        callType = if (isVideoCall) CallAttributesCompat.CALL_TYPE_VIDEO_CALL else CallAttributesCompat.CALL_TYPE_AUDIO_CALL,
        callCapabilitiesCompat = callCapabilities
    )
}

הוספת שיחה

משתמשים ב-callsManager.addCall עם CallAttributesCompat ועם קריאות חזרה (callbacks) כדי להוסיף קריאה חדשה למערכת ולנהל עדכונים מרוחקים של ממשק. הקוד callControlScope בתוך הבלוק addCall מאפשר לאפליקציה לשנות את מצב השיחה ולקבל עדכוני אודיו:

try {
    callsManager.addCall(
        INCOMING_CALL_ATTRIBUTES,
        onAnswerCall, // Watch needs to know if it can answer the call.
        onSetCallDisconnected,
        onSetCallActive,
        onSetCallInactive
    ) {
        // The call was successfully added once this scope runs.
        callControlScope = this
    }
}
catch(addCallException: Exception){
   // Handle the addCall failure.
}

מענה לשיחה

מענה לשיחה נכנסת בתוך CallControlScope:

when (val result = answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
    is CallControlResult.Success -> { /* Call answered */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

דחיית שיחה

דחיית שיחה באמצעות disconnect() עם DisconnectCause.REJECTED בתוך CallControlScope:

disconnect(DisconnectCause(DisconnectCause.REJECTED))

הפעלת שיחה יוצאת

מגדירים שיחה יוצאת כפעילה ברגע שהצד השני עונה:

when (val result = setActive()) {
    is CallControlResult.Success -> { /* Call active */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

העברת שיחה להמתנה

כדי להעביר שיחה למצב המתנה, מקישים על setInactive():

when (val result = setInactive()) {
    is CallControlResult.Success -> { /* Call on hold */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

ניתוק שיחה

כדי לנתק שיחה באמצעות disconnect() עם DisconnectCause:

disconnect(DisconnectCause(DisconnectCause.LOCAL))

ניהול נקודות קצה של אודיו בשיחות

מעקב אחרי נקודות קצה של אודיו וניהול שלהן באמצעות Flow של currentCallEndpoint,‏ availableEndpoints ו-isMuted ב-CallControlScope

fun observeAudioStateChanges(callControlScope: CallControlScope) {
    with(callControlScope) {
        launch { currentCallEndpoint.collect { /* Update UI */ } }
        launch { availableEndpoints.collect { /* Update UI */ } }
        launch { isMuted.collect { /* Handle mute state */ } }
    }
}

משנים את התקן האודיו הפעיל באמצעות requestEndpointChange():

coroutineScope.launch {
     callControlScope.requestEndpointChange(callEndpoint)
}

תמיכה בחזית

הספרייה משתמשת ב-ConnectionService (Android 13 API ברמה 33 ומטה) או ב-foregroundtypes (Android 14 API ברמה 34 ואילך) לתמיכה בחזית.

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

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

מידע נוסף על שירותי חזית

תמיכה מרחוק ב-Surface

מכשירים מרוחקים (שעונים חכמים, אוזניות Bluetooth, Android Auto) יכולים לנהל שיחות בלי אינטראקציה ישירה עם הטלפון. האפליקציה צריכה להטמיע פונקציות callback מסוג lambda‏ (onAnswerCall, onSetCallDisconnected, onSetCallActive, onSetCallInactive) שסופקו ל-CallsManager.addCall כדי לטפל בפעולות שהמכשירים האלה יוזמנו.

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

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

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

תוספי שיחה

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

יצירת שיחה עם תוספים

כשיוצרים שיחה, במקום להשתמש ב-CallManager#addCall כדי ליצור אותה, אפשר להשתמש ב-CallManager#addCallWithExtensions, שמעניק לאפליקציה גישה להיקף אחר שנקרא ExtensionInitializationScope. ההיקף הזה מאפשר לאפליקציה לאתחל את קבוצת התוספים האופציונליים שבהם היא תומכת. בנוסף, ההיקף הזה מספק שיטה נוספת, onCall, שמספקת CallControlScope בחזרה לאפליקציה אחרי השלמת החלפת היכולות וההפעלה של התוסף.

scope.launch {
    mCallsManager.addCallWithExtensions(
        attributes,
        onAnswer,
        onDisconnect,
        onSetActive,
        onSetInactive
    ) {
        // Initialize extension-specific code...

        // After the call has been initialized, perform in-call actions
        onCall {
            // Example: process call state updates
            callStateFlow.onEach { newState ->
                // handle call state updates and notify telecom
            }.launchIn(this)

            // Use initialized extensions...
        }
    }
}

משתתפים בשיחות תמיכה

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

mCallsManager.addCallWithExtensions(...) {
        // Initialize extensions...

        // Notifies Jetpack that this app supports the participant
        // extension and provides the initial participants state in the call.
        val participantExtension = addParticipantExtension(
            initialParticipants,
            initialActiveParticipant
        )

        // After the call has been initialized, perform in-call control actions
        onCall {
            // other in-call control and extension actions...

            // Example: update remote surfaces when the call participants change
            participantsFlow.onEach { newParticipants ->
                participantExtension.updateParticipants(newParticipants)
            }.launchIn(this)
        }
    }

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

יש גם תמיכה בפעולות אופציונליות שקשורות למשתתפי השיחה. האפליקציה יכולה להשתמש ב-ParticipantExtension#addRaiseHandSupport כדי לתמוך באפשרות של משתתפים להצביע בשיחה ולראות אילו משתתפים אחרים גם הצביעו.

mCallsManager.addCallWithExtensions(...) {
        // Initialize extensions...

        // Notifies Jetpack that this app supports the participant
        // extension and provides the initial list of participants in the call.
        val participantExtension = addParticipantExtension(initialParticipants)
        // Notifies Jetpack that this app supports the notion of participants
        // being able to raise and lower their hands.
        val raiseHandState = participantExtension.addRaiseHandSupport(
                initialRaisedHands
            ) { onHandRaisedStateChanged ->
                // handle this user's raised hand state changed updates from
                // remote surfaces.
            }

        // After the call has been initialized, perform in-call control actions
        onCall {
            // other in-call control and extension actions...

            // Example: update remote surfaces when the call participants change
            participantsFlow.onEach { newParticipants ->
                participantExtension.updateParticipants(newParticipants)
            }.launchIn(this)
            // notify remote surfaces of which of the participants have their
            // hands raised
            raisedHandsFlow.onEach { newRaisedHands ->
                raiseHandState.updateRaisedHands(newRaisedHands)
            }.launchIn(this)
        }
    }

השתקת שיחות תמיכה

התכונה'השתקת שיחה' מאפשרת למשתמש לבקש מהאפליקציה להשתיק את הקול היוצא בשיחה בלי להשתיק פיזית את המיקרופון של המכשיר. התכונה הזו מנוהלת לכל שיחה, כך ש-Jetpack מטפל במורכבות של ניהול המצב הגלובלי של השהיה בשיחות ניידות קיימות בזמן ששיחת VOIP פעילה. כך, השהיה של אודיו יוצאת פחות נוטה לשגיאות בתרחישים של שיחות מרובות, ומאפשרת גם תכונות מועילות כמו אינדיקציות של 'האם את/ה מדבר/ת?' כשהמשתמש מדבר בלי לדעת שההשתקה של השיחה מופעלת.

mCallsManager.addCallWithExtensions(...) {
        // Initialize extensions...

        // Add support for locally silencing the call's outgoing audio and
        // register a handler for when the user changes the call silence state
        // from a remote surface.
        val callSilenceExtension = addLocalCallSilenceExtension(
            initialCallSilenceState = false
        ) { newCallSilenceStateRequest ->
            // handle the user's request to enable/disable call silence from
            // a remote surface
        }

        // After the call has been initialized, perform in-call control actions
        onCall {
            // other in-call control and extension actions...

            // When the call's call silence state changes, update remote
            // surfaces of the new state.
            callSilenceState.onEach { isSilenced ->
                callSilenceExtension.updateIsLocallySilenced(isSilenced)
            }.launchIn(this)
        }
    }

סמלי שיחות תמיכה

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

mCallsManager.addCallWithExtensions(...) {
        // Initialize extensions...

        // Add support for a custom call icon to be displayed during the
        // lifetime of the call.
        val callIconExtension = addCallIconExtension(
            initialCallIconUri = initialUri
        )

        // After the call has been initialized, perform in-call control actions
        onCall {
            // other in-call control and extension actions...

            // When the call's icon changes, update remote surfaces by providing
            // the new URI.
            callIconUri.onEach { newIconUri ->
                callIconExtension.updateCallIconUri(newIconUri)
            }.launchIn(this)
        }
    }