تسهّل مكتبة Core-Telecom عملية دمج تطبيق الاتصال الخاص بك مع نظام Android الأساسي من خلال توفير مجموعة قوية ومتسقة من واجهات برمجة التطبيقات.
إذا أردت استكشاف عمليات تنفيذ عملية، يمكنك العثور على نماذج تطبيقات على GitHub:
- تطبيق Lightweight Sample App: مثال بسيط يوضّح كيفية استخدام واجهة برمجة التطبيقات
Core-Telecom. وهي مثالية لفهم المفاهيم الأساسية بسرعة. - تطبيق تجريبي شامل (طوّره فريق Core-Telecom): تطبيق يتضمّن المزيد من الميزات ويعرض وظائف الاتصالات المتقدّمة وأفضل الممارسات. هذا المرجع مفيد جدًا لفهم سيناريوهات الدمج المعقّدة.
إعداد 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))
إدارة نقاط نهاية الصوت للمكالمات
مراقبة نقاط نهاية الصوت وإدارتها باستخدام currentCallEndpoint وavailableEndpoints وisMuted Flow ضمن CallControlScope لا تستخدِم واجهات برمجة التطبيقات 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 على الإصدار 13 من نظام التشغيل Android (المستوى 33 لواجهة برمجة التطبيقات) والإصدارات الأقدم، أو أنواع الخدمات التي تعمل في المقدّمة على الإصدار 14 من نظام التشغيل Android (المستوى 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 عن بُعد
يمكن للأجهزة البعيدة (الساعات الذكية وسماعات الرأس التي تعمل بالبلوتوث وAndroid Auto) إدارة المكالمات بدون التفاعل مباشرةً مع الهاتف. يجب أن ينفّذ تطبيقك
تعبيرات lambda لعمليات رد الاتصال (onAnswerCall وonSetCallDisconnected وonSetCallActive وonSetCallInactive) المقدَّمة إلى CallsManager.addCall للتعامل مع الإجراءات
التي تبدأها هذه الأجهزة.
عند حدوث إجراء عن بُعد، يتم استدعاء دالة 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 للإشارة إلى أنّ تطبيقك يتيح هذه الإضافة، واستخدِم واجهات برمجة التطبيقات ذات الصلة لتعديل المساحات البعيدة عند تغيير المشاركين.
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 مع تعقيد إدارة حالة كتم الصوت العامة للمكالمات الخلوية الجارية أثناء نشاط مكالمة عبر بروتوكول الإنترنت. يؤدي ذلك إلى تقليل احتمالية حدوث أخطاء عند كتم الصوت الصادر في سيناريوهات المكالمات المتعددة، كما يتيح ميزات مفيدة، مثل مؤشرات "هل تتحدث؟" عندما يتحدث المستخدم بدون أن يدرك أنّه تم تفعيل ميزة كتم الصوت.
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 التي يجريها تطبيقك إلى سجلّ المكالمات في النظام، كي تظهر في تطبيق الاتصال في النظام ويتمكّن المستخدمون من إعادة الاتصال من هناك. لمزيد من التفاصيل، يُرجى الاطّلاع على مقالة سجلّ المكالمات الموحّد.