نظرة عامة على إطار عمل الاتصالات

يدير إطار عمل Android Telecom (المعروف أيضًا باسم "Talecom") مكالمات الصوت والفيديو على الجهاز الذي يعمل بنظام التشغيل Android. ويشمل ذلك المكالمات المستنِدة إلى شريحة SIM، مثل المكالمات التي تستخدم إطار عمل الاتصالات الهاتفية، ومكالمات بروتوكول الصوت على الإنترنت التي تستخدم واجهة برمجة التطبيقات ConnectionService.

المكوّنات الرئيسية التي تديرها شركة Telecom هي ConnectionService وInCallService.

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

يوفر تنفيذ InCallService واجهة مستخدم للمكالمات التي تديرها شركة Telecom ويسمح للمستخدم بالتحكم في هذه المكالمات والتفاعل معها. وتُعدّ عملية تنفيذ InCallService الأكثر شيوعًا تطبيق الهاتف المضمّن مع جهاز.

تعمل الاتصالات كلوحة تبديل. وهي توجّه الطلبات التي توفّرها عمليات تنفيذ ConnectionService إلى واجهات المستخدم الخاصة بالاتصال التي توفّرها تطبيقات InCallService.

قد تحتاج إلى تنفيذ واجهات برمجة تطبيقات Telecom للأسباب التالية:

إنشاء تطبيق هاتف بديل

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

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

ولمزيد من المعلومات، يمكنك الاطّلاع على InCallService.

دمج حل اتصال

لدمج حل الاتصال الذي تستخدمه في Android، لديك الخيارات التالية:

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

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

  • تنفيذ واجهة برمجة تطبيقات ConnectionService API:يسهِّل هذا الخيار تطوير حل اتصال يعتمد على تطبيق هاتف الجهاز الحالي لتوفير واجهة المستخدم للمكالمات. تشمل الأمثلة تنفيذ جهات خارجية لخدمات مكالمات SIP والاتصال عبر بروتوكول الصوت عبر الإنترنت. ولمزيد من التفاصيل، يمكنك الاطّلاع على getDefaultDialerPackage().

    توفِّر ConnectionService وحدها وسائل إجراء المكالمات فقط. ليس له واجهة مستخدم مرتبطة.

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

تصفية المكالمات

إنّ الأجهزة التي تعمل بنظام التشغيل Android 10 (المستوى 29 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث تسمح لتطبيقك بتمييز المكالمات الواردة من الأرقام غير المدرَجة في دفتر عناوين المستخدم باعتبارها مكالمات محتملة غير مرغوب فيها. يمكن للمستخدمين اختيار أن يتم رفض المكالمات غير المرغوب فيها تلقائيًا. لتوفير المزيد من الشفافية للمستخدمين عند تفويت مكالمات، يتم تسجيل معلومات عن هذه المكالمات المحظورة في سجلّ المكالمات. ويلغي استخدام واجهة برمجة التطبيقات Android 10 API شرط الحصول على إذن READ_CALL_LOG من المستخدم لإتاحة وظائف فحص المكالمات وإظهار رقم المتّصل.

يمكنك استخدام تطبيق CallScreeningService لفحص المكالمات. استدعِ الدالة onScreenCall() لأي مكالمات واردة أو صادرة جديدة عندما لا يكون الرقم مدرَجًا في قائمة جهات اتصال المستخدم. يمكنك الاطّلاع على الكائن Call.Details للحصول على معلومات عن المكالمة. على وجه التحديد، تشتمل دالة getCallerNumberVerificationStatus() على معلومات من مزوّد الشبكة حول الرقم الآخر. إذا تعذّر إثبات صحة رقم التأمين الاجتماعي، هذا مؤشر جيد على أنّ المكالمة واردة من رقم غير صالح أو من مكالمة محتمَلة غير مرغوب فيها.

Kotlin

class ScreeningService : CallScreeningService() {
    // This function is called when an ingoing or outgoing call
    // is from a number not in the user's contacts list
    override fun onScreenCall(callDetails: Call.Details) {
        // Can check the direction of the call
        val isIncoming = callDetails.callDirection == Call.Details.DIRECTION_INCOMING

        if (isIncoming) {
            // the handle (e.g. phone number) that the Call is currently connected to
            val handle: Uri = callDetails.handle

            // determine if you want to allow or reject the call
            when (callDetails.callerNumberVerificationStatus) {
                Connection.VERIFICATION_STATUS_FAILED -> {
                    // Network verification failed, likely an invalid/spam call.
                }
                Connection.VERIFICATION_STATUS_PASSED -> {
                    // Network verification passed, likely a valid call.
                }
                else -> {
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED.
                }
            }
        }
    }
}

Java

class ScreeningService extends CallScreeningService {
    @Override
    public void onScreenCall(@NonNull Call.Details callDetails) {
        boolean isIncoming = callDetails.getCallDirection() == Call.Details.DIRECTION_INCOMING;

        if (isIncoming) {
            Uri handle = callDetails.getHandle();

            switch (callDetails.getCallerNumberVerificationStatus()) {
                case Connection.VERIFICATION_STATUS_FAILED:
                    // Network verification failed, likely an invalid/spam call.
                    break;
                case Connection.VERIFICATION_STATUS_PASSED:
                    // Network verification passed, likely a valid call.
                    break;
                default:
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED
            }
        }
    }
}

يمكنك ضبط الدالة onScreenCall() لطلب استدعاء respondToCall() لإخبار النظام بكيفية الاستجابة إلى الاستدعاء الجديد. تستخدِم هذه الدالة معلَمة CallResponse يمكنك استخدامها لتوجيه النظام بحظر المكالمة أو رفضها كما لو كان المستخدم قد فعل ذلك أو كتم صوتها. يمكنك أيضًا مطالبة النظام بتخطي إضافة هذه المكالمة إلى سجل مكالمات الجهاز تمامًا.

Kotlin

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
val response = CallResponse.Builder()
    // Sets whether the incoming call should be blocked.
    .setDisallowCall(false)
    // Sets whether the incoming call should be rejected as if the user did so manually.
    .setRejectCall(false)
    // Sets whether ringing should be silenced for the incoming call.
    .setSilenceCall(false)
    // Sets whether the incoming call should not be displayed in the call log.
    .setSkipCallLog(false)
    // Sets whether a missed call notification should not be shown for the incoming call.
    .setSkipNotification(false)
    .build()

// Call this function to provide your screening response.
respondToCall(callDetails, response)

Java

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
CallResponse.Builder response = new CallResponse.Builder();
// Sets whether the incoming call should be blocked.
response.setDisallowCall(false);
// Sets whether the incoming call should be rejected as if the user did so manually.
response.setRejectCall(false);
// Sets whether ringing should be silenced for the incoming call.
response.setSilenceCall(false);
// Sets whether the incoming call should not be displayed in the call log.
response.setSkipCallLog(false);
// Sets whether a missed call notification should not be shown for the incoming call.
response.setSkipNotification(false);

// Call this function to provide your screening response.
respondToCall(callDetails, response.build());

يجب تسجيل تنفيذ CallScreeningService في ملف البيان باستخدام فلتر الأهداف المناسب والإذن المناسب حتى يتمكن النظام من تشغيله بشكل صحيح.

<service
    android:name=".ScreeningService"
    android:permission="android.permission.BIND_SCREENING_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallScreeningService" />
    </intent-filter>
</service>

إعادة توجيه مكالمة

تعمل الأجهزة التي تعمل بالإصدار 10 من نظام التشغيل Android أو الإصدارات الأحدث على إدارة أهداف المكالمات بشكلٍ مختلف عن الأجهزة التي تعمل بنظام التشغيل Android 9 أو بإصدار أقدم. على نظام التشغيل Android 10 والإصدارات الأحدث، تم إيقاف البث ACTION_NEW_OUTGOING_CALL نهائيًا واستبداله بواجهة برمجة التطبيقات CallRedirectionService. توفِّر CallRedirectionService واجهات يمكنك استخدامها لتعديل المكالمات الصادرة التي يجريها نظام Android الأساسي. على سبيل المثال، قد تلغي التطبيقات التابعة لجهات خارجية المكالمات وتعيد توجيهها عبر بروتوكول الصوت على الإنترنت (VoIP).

Kotlin

class RedirectionService : CallRedirectionService() {
    override fun onPlaceCall(
        handle: Uri,
        initialPhoneAccount: PhoneAccountHandle,
        allowInteractiveResponse: Boolean
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        val callShouldProceed = true
        val callShouldRedirect = false
        when {
            callShouldProceed -> {
                placeCallUnmodified()
            }
            callShouldRedirect -> {
                // Update the URI to point to a different phone number or modify the
                // PhoneAccountHandle and redirect.
                redirectCall(handle, initialPhoneAccount, true)
            }
            else -> {
                cancelCall()
            }
        }
    }
}

Java

class RedirectionService extends CallRedirectionService {
    @Override
    public void onPlaceCall(
            @NonNull Uri handle,
            @NonNull PhoneAccountHandle initialPhoneAccount,
            boolean allowInteractiveResponse
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        // Your app should implement this logic to determine the redirection.
        boolean callShouldProceed = true;
        boolean callShouldRedirect = false;
        if (callShouldProceed) {
            placeCallUnmodified();
        } else if (callShouldRedirect) {
            // Update the URI to point to a different phone number or modify the
            // PhoneAccountHandle and redirect.
            redirectCall(handle, initialPhoneAccount, true);
        } else {
            cancelCall();
        }
    }
}

يجب تسجيل هذه الخدمة في البيان حتى يتمكن النظام من تشغيلها بشكل صحيح.

<service
    android:name=".RedirectionService"
    android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallRedirectionService"/>
    </intent-filter>
</service>

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

يجب التحقق مما إذا كان هذا الدور مسندًا إلى تطبيقك عند تشغيل المستخدم للتطبيق حتى تتمكن من طلبه حسب الحاجة. أنت تطلق هدفًا تم إنشاؤه من خلال RoleManager، لذا احرص على إلغاء دالة onActivityResult() للتعامل مع اختيار المستخدم.

Kotlin

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            val roleManager = getSystemService(Context.ROLE_SERVICE) as RoleManager
            // Check if the app needs to register call redirection role.
            val shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION)
            if (shouldRequestRole) {
                val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION)
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE)
            }
        }
    }

    companion object {
        private const val REDIRECT_ROLE_REQUEST_CODE = 1
    }
}

Java

class MainActivity extends AppCompatActivity {
    private static final int REDIRECT_ROLE_REQUEST_CODE = 0;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            RoleManager roleManager = (RoleManager) getSystemService(Context.ROLE_SERVICE);
            // Check if the app needs to register call redirection role.
            boolean shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION);
            if (shouldRequestRole) {
                Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION);
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE);
            }
        }
    }
}