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")

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

הוספת שיחה

אפשר להשתמש ב-callsManager.addCall עם CallAttributesCompat וקריאות חוזרות כדי להוסיף שיחה חדשה למערכת ולנהל עדכונים של משטחים מרוחקים. הבלוק 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))

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

אפשר לצפות בנקודות קצה של אודיו ולנהל אותן באמצעות currentCallEndpoint,‏ availableEndpoints ו-isMuted Flow בתוך CallControlScope. אל תשתמשו בממשקי ה-API‏ AudioManager#setCommunicationDevice או AudioManager#startBluetoothSco כדי לנהל את נתיבי האודיו כשמשתמשים ב-Telecom. שימוש כזה יגרום לבעיות באודיו במהלך השיחה.

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) ובגרסאות קודמות, או בסוגים של שירותים שפועלים בחזית ב-Android 14 (רמת API‏ 34) ובגרסאות חדשות יותר, לתמיכה בשירותים שפועלים בחזית.

כדי שהשיחות יישארו פעילות כשהאפליקציה פועלת ברקע, צריך לארח את CallsManager בתוך Service שפועל בחזית (כמו LifecycleService) ולהצהיר על סוג השירות שפועל בחזית phoneCall ב-AndroidManifest.xml:

<service
    android:name=".TelecomVoipService"
    android:foregroundServiceType="phoneCall" />

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

startForeground(
    notificationId,
    notification,
    ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL
)

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

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

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

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

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

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

תוספי שיחה

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

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

כשיוצרים שיחה, במקום להשתמש ב-CallsManager.addCall כדי ליצור את השיחה, אפשר להשתמש ב-CallsManager.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)
        }
    }

הוספה ליומן השיחות של המערכת

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