إنشاء تطبيق هاتف افتراضي

يتيح تطبيق الهاتف التلقائي لإطار عمل اتصالات Android إبلاغ تطبيقك بحالة الاتصال عن طريق استخدام مدير الأدوار والخدمة أثناء الاتصال لإنشاء بديل لتطبيق الهاتف التلقائي على جهاز Android، ويمكنك تنفيذ واجهة برمجة التطبيقات InCallService API. يجب أن تستوفي عملية التنفيذ المتطلبات التالية:

يجب ألا يتضمن التطبيق أي إمكانية اتصال، ويجب أن يتألف فقط من واجهة مستخدم للاتصال. يجب أن تتعامل الشركة مع جميع المكالمات التي يدركها إطار عمل الاتصالات، وألا تضع افتراضات حول طبيعة المكالمات. على سبيل المثال، يجب ألا تفترض الميزة أنّ المكالمات الهاتفية مستندة إلى شريحة SIM، وألا تفرض قيود على الاتصال تستند إلى أي خدمة ConnectionService، مثل فرض قيود هاتفية على مكالمات الفيديو.

يسمح تطبيق الاتصال للمستخدمين بتلقي مكالمات صوتية أو مكالمات فيديو أو إجراؤها على أجهزتهم. تستخدم تطبيقات الاتصال واجهة المستخدم الخاصة بها لإجراء المكالمات بدلاً من استخدام واجهة تطبيق "الهاتف" التلقائية، كما هو موضّح في لقطة الشاشة التالية.

مثال على أحد تطبيقات الاتصال
مثال على تطبيق اتصال يستخدم واجهة المستخدم الخاصة به

يشتمل إطار عمل Android على حزمة android.telecom التي تحتوي على فئات تساعدك في إنشاء تطبيق اتصال وفقًا لإطار عمل الاتصالات. ويوفّر إنشاء التطبيق وفقًا لإطار عمل الاتصالات المزايا التالية:

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

نماذج البيان والأذونات

في بيان التطبيق، وضِّح أنّ تطبيقك يستخدم إذن MANAGE_OWN_CALLS، كما هو موضّح في المثال التالي:

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

لمزيد من المعلومات حول بيان أذونات التطبيق، يمكنك الاطّلاع على الأذونات.

يجب أن تعلن عن خدمة تحدِّد الفئة التي تنفِّذ فئة ConnectionService في تطبيقك. ويتطلّب النظام الفرعي للاتصالات أن تذكر الخدمة إذن BIND_TELECOM_CONNECTION_SERVICE ليكون قادرًا على الربط به. يوضّح المثال التالي كيفية الإعلان عن الخدمة في بيان التطبيق:

<service android:name="com.example.MyConnectionService"
    android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.ConnectionService" />
    </intent-filter>
</service>

لمزيد من المعلومات عن تعريف مكوّنات التطبيق، بما في ذلك الخدمات، يُرجى الاطّلاع على مكوّنات التطبيق.

تنفيذ خدمة الربط

يجب أن يوفّر تطبيق الاتصال تنفيذًا للفئة ConnectionService التي يمكن أن يرتبط بها النظام الفرعي للاتصالات. يجب أن تلغي عملية تنفيذ ConnectionService الطرق التالية:

onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)

يستدعي النظام الفرعي للاتصالات هذه الطريقة استجابةً لاستدعاء تطبيقك placeCall(Uri, Bundle) لإنشاء مكالمة صادرة جديدة. يعرض تطبيقك مثيلاً جديدًا من تنفيذ فئة Connection (لمزيد من المعلومات، يُرجى الاطّلاع على تنفيذ عملية الربط) لتمثيل المكالمة الصادرة الجديدة. يمكنك تخصيص الاتصال الصادر بشكل أكبر عن طريق تنفيذ الإجراءات التالية:

onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

يستدعي النظام الفرعي للاتصالات هذه الطريقة عندما يستدعي تطبيقك طريقة placeCall(Uri, Bundle) ولا يمكن إجراء مكالمة صادرة. استجابةً لهذا الموقف، يجب أن يُعلِم تطبيقك المستخدم (على سبيل المثال، باستخدام مربع تنبيه أو إشعار نخبي) بتعذُّر إجراء المكالمة الصادرة. قد لا يتمكّن تطبيقك من إجراء مكالمة في حال كانت هناك مكالمة طوارئ جارية أو إذا كانت هناك مكالمة جارية في تطبيق آخر لا يمكن تعليقها قبل إجراء مكالمتك.

onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)

يستدعي النظام الفرعي للاتصالات هذه الطريقة عندما يستدعي تطبيقك طريقة addNewIncomingCall(PhoneAccountHandle, Bundle) لإعلام النظام بمكالمة واردة جديدة في تطبيقك. يعرض تطبيقك مثيلاً جديدًا من تنفيذ Connection (للحصول على مزيد من المعلومات، راجِع تنفيذ عملية الربط) لتمثيل المكالمة الواردة الجديدة. يمكنك تخصيص الاتصال الوارد بشكل أكبر عن طريق تنفيذ الإجراءات التالية:

onCreateIncomingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

يستدعي النظام الفرعي للاتصالات بهذه الطريقة عندما يستدعي تطبيقك طريقة addNewIncomingCall(PhoneAccountHandle, Bundle) لإبلاغ Telecom بمكالمة واردة جديدة، ولكن لا يُسمح بالمكالمة الواردة (لمزيد من المعلومات، يُرجى الاطّلاع على قيود الاتصال). يجب أن يرفض تطبيقك المكالمة الواردة بدون تنبيه، وأن ينشر إشعارًا اختياريًا لإعلام المستخدم بالمكالمة الفائتة.

تنفيذ عملية الربط

يجب أن يُنشئ تطبيقك فئة فرعية من Connection لتمثيل الطلبات في تطبيقك. وعليك إلغاء الطرق التالية في عملية التنفيذ:

onShowIncomingCallUi()

يستدعي النظام الفرعي للاتصالات هذه الطريقة عند إضافة مكالمة واردة جديدة ويجب أن يعرض تطبيقك واجهة المستخدم للمكالمات الواردة.

onCallAudioStateChanged(CallAudioState)

يستدعي النظام الفرعي للاتصالات هذه الطريقة لإبلاغ تطبيقك بتغيير المسار أو الوضع الصوتي الحالي. ويُعرف ذلك استجابةً لتغيير تطبيقك لوضع الصوت باستخدام الطريقة setAudioRoute(int). وقد يتم استدعاء هذه الطريقة أيضًا إذا غيّر النظام مسار الصوت (على سبيل المثال، عند فصل سماعة رأس بلوتوث).

onHold()

يستدعي النظام الفرعي للاتصالات هذه الطريقة عندما يريد تعليق مكالمة. استجابةً لهذا الطلب، يجب أن يعلّق تطبيقك المكالمة ثم يستدعي طريقة setOnHold() لإعلام النظام بأنّ المكالمة معلّقة. قد يستدعي النظام الفرعي للاتصال هذه الطريقة عندما تريد خدمة أثناء الاتصال، مثل Android Auto، تعرض مكالمتك إرسال طلب مستخدم لتعليق المكالمة. يستدعي النظام الفرعي للاتصالات أيضًا هذه الطريقة إذا أجرى المستخدم مكالمة نشطة في تطبيق آخر. لمزيد من المعلومات حول الخدمات أثناء الاتصال، يُرجى الاطّلاع على InCallService.

onUnhold()

يستدعي النظام الفرعي للاتصالات هذه الطريقة عندما يريد استئناف مكالمة تم وضعها قيد الانتظار. بعد أن يستأنف تطبيقك المكالمة، يجب أن يستدعي الطريقة setActive() لإبلاغ النظام بأنّ المكالمة لم تعُد معلَّقة. قد يستدعي النظام الفرعي للاتصالات هذه الطريقة عندما تريد خدمة أثناء المكالمة، مثل Android Auto، التي تعرض مكالمتك إرسال طلب لاستئناف المكالمة. لمزيد من المعلومات حول الخدمات أثناء المكالمة، راجع InCallService.

onAnswer()

يستدعي النظام الفرعي للاتصالات هذه الطريقة لإبلاغ تطبيقك بضرورة الرد على مكالمة واردة. بعد أن يردّ التطبيق على المكالمة، يجب أن يستدعي طريقة setActive() لإعلام النظام بأنّه قد تم الردّ على المكالمة. قد يتصل النظام الفرعي للاتصالات بهذه الطريقة عندما يضيف تطبيقك مكالمة واردة جديدة وهناك بالفعل مكالمة جارية في تطبيق آخر لا يمكن تعليقها. يعرض النظام الفرعي للاتصالات واجهة المستخدم للمكالمات الواردة نيابةً عن تطبيقك في هذه الحالات. يوفر إطار العمل طريقة تحميل زائد توفر الدعم لتحديد حالة الفيديو التي يتم الرد على المكالمة فيها. لمزيد من المعلومات، راجع "onAnswer(int)".

onReject()

يستدعي النظام الفرعي للاتصالات هذه الطريقة عندما يريد رفض مكالمة واردة. بعد أن يرفض تطبيقك الطلب، يجب أن يستدعي setDisconnected(DisconnectCause) ويحدِّد REJECTED كمَعلمة. يجب بعد ذلك استدعاء طريقة destroy() لإعلام النظام بأنّ التطبيق قد عالج المكالمة. يتصل النظام الفرعي للاتصالات بهذه الطريقة عندما يرفض المستخدم مكالمة واردة من تطبيقك.

onDisconnect()

يستدعي النظام الفرعي للاتصالات هذه الطريقة عندما يريد قطع مكالمة. بعد انتهاء المكالمة، يجب أن يستدعي تطبيقك الطريقة setDisconnected(DisconnectCause) ويحدِّد LOCAL كمَعلمة للإشارة إلى أنّ طلب أحد المستخدمين قد تسبب في قطع الاتصال. يجب أن يتصل التطبيق بعد ذلك بطريقة destroy() لإبلاغ النظام الفرعي للاتصالات بأنّ التطبيق قد عالج المكالمة. قد يستدعي النظام هذه الطريقة عندما ينقطع المستخدم مكالمة من خلال خدمة أخرى أثناء المكالمة مثل Android Auto. يتصل النظام أيضًا بهذه الطريقة إذا كان يجب قطع اتصالك للسماح بإجراء مكالمات أخرى، على سبيل المثال، إذا أراد المستخدم إجراء مكالمة طوارئ. لمزيد من المعلومات عن الخدمات أثناء المكالمة، يُرجى الاطّلاع على InCallService.

التعامل مع السيناريوهات الشائعة للاتصال

إنّ الاستفادة من واجهة برمجة التطبيقات ConnectionService API في مسار الاتصال يستلزم التفاعل مع الفئات الأخرى في حزمة android.telecom. تصف الأقسام التالية سيناريوهات الاتصال الشائعة والكيفية التي يجب أن يستخدم بها التطبيق واجهات برمجة التطبيقات للتعامل معها.

الرد على المكالمات الواردة

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

ما مِن مكالمات نشطة في التطبيقات الأخرى

للردّ على المكالمات الواردة في حال عدم توفّر مكالمات نشطة في التطبيقات الأخرى، اتّبِع الخطوات التالية:

  1. يتلقّى تطبيقك مكالمة واردة جديدة باستخدام الآليات المعتادة.
  2. استخدِم الطريقة addNewIncomingCall(PhoneAccountHandle, Bundle) لإعلام النظام الفرعي للاتصالات بالمكالمة الواردة الجديدة.
  3. يرتبط النظام الفرعي للاتصالات بتطبيق ConnectionService لتطبيقك ويطلب مثيلاً جديدًا من الفئة Connection التي تمثّل المكالمة الواردة الجديدة باستخدام طريقة onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest).
  4. يُعلِم النظام الفرعي للاتصالات تطبيقك بضرورة عرض واجهة المستخدم للمكالمات الواردة باستخدام طريقة onShowIncomingCallUi().
  5. يعرض تطبيقك واجهة المستخدم الواردة باستخدام إشعار مرتبط بملء الشاشة. لمزيد من المعلومات، يُرجى الاطّلاع على "onShowIncomingCallUi()".
  6. يمكنك استدعاء الإجراء setActive() إذا قبل المستخدم المكالمة الواردة، أو setDisconnected(DisconnectCause) مع تحديد REJECTED لتكون المعلَمة، متبوعة باستدعاء لطريقة destroy() إذا رفض المستخدم المكالمة الواردة.

المكالمات النشطة في التطبيقات الأخرى والتي لا يمكن تجميدها

للرد على المكالمات الواردة في حال وجود مكالمات نشطة في التطبيقات الأخرى ولا يمكن تعليقها، اتّبع الخطوات التالية:

  1. يتلقّى تطبيقك مكالمة واردة جديدة باستخدام الآليات المعتادة.
  2. استخدِم الطريقة addNewIncomingCall(PhoneAccountHandle, Bundle) لإعلام النظام الفرعي للاتصالات بالمكالمة الواردة الجديدة.
  3. يرتبط النظام الفرعي للاتصالات بتطبيق ConnectionService في تطبيقك ويطلب مثيلاً جديدًا من عنصر Connection الذي يمثّل المكالمة الواردة الجديدة باستخدام طريقة onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest).
  4. يعرض النظام الفرعي للاتصالات واجهة المستخدم للمكالمات الواردة.
  5. في حال قبول المستخدم المكالمة، يتصل النظام الفرعي للاتصالات بالطريقة onAnswer(). يجب الاتصال بطريقة setActive() للإشارة إلى النظام الفرعي للاتصالات إلى أنّ المكالمة متصلة الآن.
  6. إذا رفض المستخدم المكالمة، يستدعي النظام الفرعي للاتصالات طريقة onReject(). عليك استدعاء الإجراء setDisconnected(DisconnectCause) مع تحديد REJECTED كمَعلمة متبوعة باستدعاء الطريقة destroy().

إجراء مكالمات صادرة

يتضمن تدفق إجراء مكالمة صادرة التعامل مع احتمال عدم إمكانية إجراء المكالمة بسبب القيود التي يفرضها إطار عمل الاتصالات. لمزيد من المعلومات، يُرجى الاطّلاع على قيود الاتصال.

لإجراء مكالمة صادرة، اتّبع الخطوات التالية:

  1. يبدأ المستخدم مكالمة صادرة داخل تطبيقك.
  2. استخدِم طريقة placeCall(Uri, Bundle) لإعلام النظام الفرعي للاتصالات بالمكالمة الصادرة الجديدة. يُرجى مراعاة الاعتبارات التالية لمَعلمات الطريقة:
    • تمثّل المَعلمة Uri العنوان الذي يتم إجراء الطلب عليه. بالنسبة إلى أرقام الهواتف العادية، استخدِم نظام معرِّف الموارد المنتظم (URI) tel:.
    • تسمح لك المعلَمة Bundle بتوفير معلومات عن تطبيق الاتصال من خلال إضافة الكائن PhoneAccountHandle في تطبيقك إلى عنصر EXTRA_PHONE_ACCOUNT_HANDLE الإضافي. يجب أن يوفر تطبيقك العنصر PhoneAccountHandle لكل مكالمة صادرة.
    • تسمح لك المعلَمة Bundle أيضًا بتحديد ما إذا كانت المكالمة الصادرة تتضمّن فيديو من خلال تحديد قيمة STATE_BIDIRECTIONAL في EXTRA_START_CALL_WITH_VIDEO_STATE الإضافية. ضع في اعتبارك ذلك تلقائيًا، يوجِّه النظام الفرعي للاتصالات مكالمات الفيديو إلى مكبّر الصوت.
  3. يرتبط النظام الفرعي للاتصالات بتطبيق ConnectionService في تطبيقك.
  4. إذا لم يتمكّن تطبيقك من إجراء مكالمة صادرة، يطلب النظام الفرعي للاتصالات من طريقة onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest) لإعلام تطبيقك بأنّه لا يمكن إجراء الاتصال في الوقت الحالي. يجب أن يُعلم تطبيقك المستخدم بأنه لا يمكن إجراء المكالمة.
  5. إذا كان تطبيقك قادرًا على إجراء المكالمة الصادرة، يُطلق على النظام الفرعي للاتصالات طريقة onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest). يجب أن يعرض تطبيقك مثيلاً من فئة Connection لتمثيل المكالمة الصادرة الجديدة. لمزيد من المعلومات حول السمات التي يجب إعدادها في عملية الربط، راجِع تنفيذ خدمة الربط.
  6. عند إجراء المكالمة الصادرة، يمكنك الاتصال بطريقة setActive() لإعلام النظام الفرعي للاتصالات بأنّ المكالمة نشطة.

إنهاء مكالمة

لإنهاء مكالمة، اتّبِع الخطوات التالية:

  1. يمكنك استدعاء setDisconnected(DisconnectCause) مع إرسال LOCAL كمَعلمة إذا أنهى المستخدم المكالمة، أو إرسال REMOTE باعتبارها مَعلمة إذا أنهى الطرف الآخر الاتصال.
  2. استدعِ الطريقة destroy().

القيود المفروضة على المكالمات

لضمان تجربة اتصال متسقة وبسيطة للمستخدمين، يفرض إطار عمل الاتصالات بعض القيود لإدارة المكالمات على الجهاز. على سبيل المثال، قد يكون المستخدم قد ثبّت تطبيقَين للاتصال يستخدمان واجهة برمجة التطبيقات ConnectionService المُدارة ذاتيًا، وهما FooTalk وBarTalk. في هذه الحالة، تنطبق القيود التالية:

  • على الأجهزة التي تعمل بالمستوى 27 من واجهة برمجة التطبيقات أو أقل، يمكن لتطبيق واحد فقط المحافظة على مكالمة جارية في أي وقت. يعني هذا القيد أنّه على الرغم من أنّ المستخدم لديه مكالمة جارية باستخدام تطبيق FooTalk، لا يمكن لتطبيق BarTalk بدء مكالمة جديدة أو تلقّيها.

    على الأجهزة التي تعمل من المستوى 28 أو أعلى من واجهة برمجة التطبيقات، إذا أعلن كل من FooTalk وBarTalk عن إذنَي CAPABILITY_SUPPORT_HOLD وCAPABILITY_HOLD، يمكن للمستخدم الاحتفاظ بأكثر من مكالمة جارية واحدة من خلال التبديل بين التطبيقَين لبدء مكالمة أخرى أو الردّ عليها.

  • إذا كان المستخدم مشاركًا في مكالمات مُدارة عادية (على سبيل المثال، باستخدام تطبيق الهاتف أو تطبيق Dialer المُدمَج)، لا يمكن للمستخدم تلقّي المكالمات التي تنشأ من تطبيقات الاتصال. وهذا يعني أنّه إذا كان المستخدم في مكالمة عادية باستخدام مشغّل شبكة الجوّال، لا يمكنه أيضًا المشاركة في مكالمة FooTalk أو BarTalk بشكل متزامن.

  • يقطع النظام الفرعي للاتصالات مكالمات تطبيقك إذا اتصل المستخدم بمكالمة طوارئ.

  • لا يمكن لتطبيقك تلقّي مكالمات أو إجرائها عندما يُجري المستخدم مكالمة طوارئ.

  • إذا كانت هناك مكالمة جارية في تطبيق آخر للاتصال عند تلقّي تطبيقك مكالمة واردة، سيؤدي الرد على المكالمة الواردة إلى إنهاء أي مكالمات جارية في التطبيق الآخر. يجب ألا يعرض التطبيق واجهة المستخدم المعتادة للمكالمات الواردة. يعرض إطار عمل الاتصالات واجهة مستخدم المكالمات الواردة ويُعلِم المستخدم بأنّ الردّ على المكالمة الجديدة سينهي المكالمات الجارية. ويعني هذا أنّه إذا كان المستخدم مشاركًا في مكالمة FooTalk وكان تطبيق BarTalk يتلقى مكالمة واردة، يُعلِم إطار عمل الاتصالات المستخدم بأنّ لديه مكالمة BarTalk قادمة وأن الردّ على مكالمة BarTalk سيؤدي إلى إنهاء مكالمة FuTalk.

اختيار تطبيق "الهاتف" التلقائي

تطبيق برنامج الاتصال أو الهاتف التلقائي هو التطبيق الذي يوفّر واجهة المستخدم أثناء المكالمة أثناء إجراء مكالمة. ويزوّد المستخدم أيضًا بوسيلة لبدء المكالمات والاطّلاع على سجلّ المكالمات على جهازه. جهاز مزوَّد بنظام اتصال تلقائي أو تطبيق هاتف تلقائي مزود به النظام. يمكن للمستخدم اختيار تطبيق واحد لتولي هذا الدور من تطبيق النظام. في حال أراد أحد التطبيقات أن يؤدي هذا الدور، يستخدم "RoleManager" ليطلب منه تولّي الدور "RoleManager.ROLE_DIALER".

يوفِّر تطبيق الهاتف التلقائي واجهة مستخدم أثناء إجراء مكالمة، ولا يكون الجهاز في وضع السيارة (أي أنّ UiModeManager#getCurrentModeType() ليس Configuration.UI_MODE_TYPE_CAR).

لشغل دور RoleManager.ROLE_DIALER، يجب أن يستوفي التطبيق عددًا من المتطلبات:

  • يجب أن يتعامل مع الغرض من Intent#ACTION_DIAL. ويعني هذا أنّه يجب أن يوفّر التطبيق واجهة المستخدم الخاصة بلوحة الاتصال حتى يتمكن المستخدم من بدء المكالمات الصادرة.
  • يجب أن ينفِّذ واجهة برمجة التطبيقات InCallService API بشكل كامل وأن يوفر واجهة مستخدم للمكالمات الواردة، بالإضافة إلى واجهة مستخدم للمكالمات الجارية.

ملاحظة: إذا عرض التطبيق الذي يملأ RoleManager.ROLE_DIALER رمز InCallService null أثناء الربط، سيعود إطار عمل الاتصالات تلقائيًا إلى استخدام تطبيق برنامج الاتصال المُحمَّل مسبقًا على الجهاز. سيعرض النظام إشعارًا للمستخدم لإبلاغه بمواصلة مكالمته باستخدام تطبيق برنامج الاتصال المحمَّل مسبقًا. يجب ألا يعرض تطبيقك ربط null مطلقًا، لأنّ ذلك يعني أنّ التطبيق لا يستوفي متطلبات RoleManager.ROLE_DIALER.

ملاحظة: إذا كان تطبيقك يملأ RoleManager.ROLE_DIALER وأجرى تغييرات في وقت التشغيل لم يعُد يستوفي متطلبات هذا الدور، سيزيل RoleManager تطبيقك تلقائيًا من الدور ويغلق تطبيقك. على سبيل المثال، إذا كنت تستخدم PackageManager.setComponentEnabledSetting(ComponentName, int, int) لإيقاف InCallService آليًا في بيانه، لن يستوفِ التطبيق المتطلبات المتوقعة من RoleManager.ROLE_DIALER.

سيتم دائمًا استخدام برنامج الاتصال المحمَّل مُسبَقًا عندما يُجري المستخدم مكالمة طوارئ، حتى إذا شغل تطبيقك الدور "RoleManager.ROLE_DIALER". لضمان أفضل تجربة عند إجراء مكالمة طوارئ، يجب أن يستخدم برنامج الاتصال التلقائي TelecomManager.placeCall(Uri, Bundle) دائمًا لإجراء مكالمات (بما في ذلك مكالمات الطوارئ). يضمن ذلك أنّ النظام الأساسي قادر على التحقّق من أنّ الطلب وارد من برنامج الاتصال التلقائي. إذا كان هناك تطبيق برنامج اتصال غير محمَّل مسبقًا يستخدم Intent#ACTION_CALL لإجراء مكالمة طوارئ، سيتم رفعه إلى تطبيق برنامج الاتصال المُحمَّل مسبقًا باستخدام Intent#ACTION_DIAL للتأكيد، لأنّ هذه تجربة مستخدم دون المستوى الأمثل.

في ما يلي مثال على تسجيل البيان لـ InCallService. تشير البيانات الوصفية TelecomManager#METADATA_IN_CALL_SERVICE_UI إلى أنّ تنفيذ InCallService هذا تحديدًا يهدف إلى استبدال واجهة المستخدم المضمّنة في المكالمة. تشير البيانات الوصفية TelecomManager#METADATA_IN_CALL_SERVICE_RINGING إلى أنّ جهاز InCallService سيشغِّل نغمة الرنين للمكالمات الواردة. يمكنك الاطّلاع على أدناه لمعرفة مزيد من المعلومات عن عرض واجهة المستخدم للمكالمات الواردة وتشغيل نغمة الرنين في تطبيقك.

 <service android:name="your.package.YourInCallServiceImplementation"
          android:permission="android.permission.BIND_INCALL_SERVICE"
          android:exported="true">
      <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
      <meta-data android:name="android.telecom.IN_CALL_SERVICE_RINGING"
          android:value="true" />
      <intent-filter>
          <action android:name="android.telecom.InCallService"/>
      </intent-filter>
 </service>

ملاحظة: يجب عدم وضع علامة على InCallService باستخدام السمة android:exported="false"، لأنّ ذلك قد يؤدي إلى عدم الربط بعملية التنفيذ أثناء الطلبات.

بالإضافة إلى تنفيذ واجهة برمجة التطبيقات InCallService، عليك أيضًا الإفصاح عن نشاط في ملف البيان الذي يعالج هدف Intent#ACTION_DIAL. يوضّح المثال أدناه كيفية إجراء ذلك:

 <activity android:name="your.package.YourDialerActivity"
           android:label="@string/yourDialerActivityLabel">
      <intent-filter>
           <action android:name="android.intent.action.DIAL" />
           <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
      <intent-filter>
           <action android:name="android.intent.action.DIAL" />
           <category android:name="android.intent.category.DEFAULT" />
           <data android:scheme="tel" />
      </intent-filter>
 </activity>

عندما يثبّت المستخدم تطبيقك ويشغّله للمرة الأولى، يجب استخدام RoleManager لتطلب من المستخدم تحديد ما إذا كان يريد أن يكون تطبيقك هو تطبيق الهاتف التلقائي الجديد.

يوضح الرمز أدناه كيف يمكن لتطبيقك أن يطلب أن يصبح تطبيق الهاتف/الهاتف التلقائي:

 private static final int REQUEST_ID = 1;

 public void requestRole() {
     RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
     Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER);
     startActivityForResult(intent, REQUEST_ID);
 }

 public void onActivityResult(int requestCode, int resultCode, Intent data) {
     if (requestCode == REQUEST_ID) {
         if (resultCode == android.app.Activity.RESULT_OK) {
             // Your app is now the default dialer app
         } else {
             // Your app is not the default dialer app
         }
     }
 }

الوصول إلى InCallService للأجهزة القابلة للارتداء

    إذا كان تطبيقك تطبيقًا مصاحبًا تابعًا لجهة خارجية وكان يريد الوصول إلى واجهات برمجة تطبيقات InCallService، يمكن تنفيذ ما يلي:

    1. تقديم إذن MANAGE_ONGOING_CALLS في ملف البيان
    2. يمكنك ربط الجهاز بجهاز قابل للارتداء عبر واجهة برمجة تطبيقات CompanionDeviceManager كتطبيق مصاحب. يمكنك الاطّلاع على: https://developer.android.com/guide/topics/connectivity/companion-device-pairing
    3. تنفيذ InCallService هذه باستخدام إذن BIND_INCALL_SERVICE

جارٍ عرض إشعار المكالمة الواردة

عندما يتلقّى تطبيقك مكالمة واردة جديدة عبر InCallService#onCallAdded(Call)، يكون مسؤولاً عن عرض واجهة المستخدم للمكالمة الواردة. من المفترض أن يتم نشر هذه الإشعارات باستخدام واجهات برمجة تطبيقات NotificationManager لنشر إشعار جديد بالمكالمات الواردة.

عندما يذكر تطبيقك البيانات الوصفية TelecomManager#METADATA_IN_CALL_SERVICE_RINGING، يكون مسؤولاً عن تشغيل نغمة الرنين للمكالمات الواردة. يجب أن ينشئ تطبيقك NotificationChannel التي تحدد نغمة الرنين المطلوبة. مثال:

 NotificationChannel channel = new NotificationChannel(YOUR_CHANNEL_ID, "Incoming Calls",
          NotificationManager.IMPORTANCE_MAX);
 // other channel setup stuff goes here.

 // We'll use the default system ringtone for our incoming call notification channel.  You can
 // use your own audio resource here.
 Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
 channel.setSound(ringtoneUri, new AudioAttributes.Builder()
          // Setting the AudioAttributes is important as it identifies the purpose of your
          // notification sound.
          .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
          .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
      .build());

 NotificationManager mgr = getSystemService(NotificationManager.class);
 mgr.createNotificationChannel(channel);

وعندما يتلقّى التطبيق مكالمة واردة جديدة، فإنّه ينشئ رمز Notification للمكالمة الواردة ويربطه بقناة إشعارات المكالمات الواردة. ويمكنك تحديد PendingIntent في الإشعار، ما سيؤدي إلى تشغيل واجهة المستخدم للمكالمات الواردة في وضع ملء الشاشة. سيعرض إطار عمل مدير الإشعارات الإشعار الذي تلقّيته كإشعار تنبيه إذا كان المستخدم يستخدم الهاتف بشكل نشط. في حال عدم استخدام الهاتف، يتم استخدام واجهة المستخدم للمكالمات الواردة في وضع ملء الشاشة بدلاً من الهاتف. مثال:

 // Create an intent which triggers your fullscreen incoming call user interface.
 Intent intent = new Intent(Intent.ACTION_MAIN, null);
 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
 intent.setClass(context, YourIncomingCallActivity.class);
 PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 // Build the notification as an ongoing high priority item; this ensures it will show as
 // a heads up notification which slides down over top of the current content.
 final Notification.Builder builder = new Notification.Builder(context);
 builder.setOngoing(true);
 builder.setPriority(Notification.PRIORITY_HIGH);
 // Set notification content intent to take user to the fullscreen UI if user taps on the
 // notification body.
 builder.setContentIntent(pendingIntent);
 // Set full screen intent to trigger display of the fullscreen UI when the notification
 // manager deems it appropriate.
 builder.setFullScreenIntent(pendingIntent, true);
 // Setup notification content.
 builder.setSmallIcon( yourIconResourceId );
 builder.setContentTitle("Your notification title");
 builder.setContentText("Your notification content.");
 // Use builder.addAction(..) to add buttons to answer or reject the call.
 NotificationManager notificationManager = mContext.getSystemService(
     NotificationManager.class);
 notificationManager.notify(YOUR_CHANNEL_ID, YOUR_TAG, YOUR_ID, builder.build());
```