Core-Telecom

تعمل مكتبة Core-Telecom على تبسيط عملية دمج تطبيق الاتصال بنظام Android من خلال توفير مجموعة قوية ومتسقة من واجهات برمجة التطبيقات.

إذا كنت تريد استكشاف عمليات التنفيذ العملية، يمكنك العثور على نماذج التطبيقات على 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 لإنشاء دورة حياة المكالمات وإدارتها.

إنشاء مكالمة

يحدّد عنصر 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 وطلبات إعادة الاتصال لإضافة طلب جديد إلى النظام وإدارة تعديلات السطح عن بُعد. يسمح العنصر 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))

إدارة نقاط نهاية الصوت في المكالمات

مراقبة نقاط نهاية الصوت وإدارتها باستخدام currentCallEndpoint availableEndpoints وisMuted Flow ضمن 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 (المستوى 33 لواجهة برمجة التطبيقات في Android 13 والإصدارات الأقدم) أو foregroundtypes (المستوى 34 لواجهة برمجة التطبيقات في Android 14 والإصدارات الأحدث) لتمكين العرض في المقدّمة.

كجزء من متطلبات التشغيل في المقدّمة، يجب أن ينشر التطبيق إشعارًا ليتمكّن المستخدمون من معرفة أنّ التطبيق يعمل في المقدّمة.

لضمان حصول تطبيقك على الأولوية في التنفيذ في المقدّمة، أنشئ إعلامًا عند إضافة طلب الاتصال بالنظام الأساسي. تتم إزالة الأولوية في المقدّمة عندما ينهي تطبيقك المكالمة أو عندما يصبح الإشعار غير صالح.

مزيد من المعلومات عن الخدمات التي تعمل في المقدّمة

دعم Surface عن بُعد

يمكن للأجهزة البعيدة (الساعات الذكية وسماعات الرأس التي تعمل بالبلوتوث وAndroid Auto) إدارة المكالمات بدون التفاعل المباشر مع الهاتف. يجب أن ينفذ تطبيقك دالات 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 للإشارة إلى توفّر هذه الإضافة واستخدِم واجهات برمجة التطبيقات ذات الصلة لتعديل مساحات العرض البعيدة عند تغيير المشاركين.

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