Telecom 框架概览

Android Telecom 框架(也简称为“Telecom”)用于管理 Android 设备上的音频和视频通话。这包括基于 SIM 卡的通话,例如使用电话框架的通话,以及实现 Core-Telecom Jetpack 库的 VoIP 通话。

Telecom 管理的主要组件是 ConnectionServiceInCallService

ConnectionService 实现基于 PSTN 等技术来将通话连接到其他方。手机上最常见的 ConnectionService 实现是电话 ConnectionService。它用于连接运营商通话。

InCallService 实现会为 Telecom 管理的通话提供界面,并允许用户控制这些通话并与之互动。InCallService 最常见的实现是与设备捆绑的电话应用。

电信运营商充当电话交换台。它会将 ConnectionService 实现提供的调用路由到 InCallService 实现提供的调用界面。

您可能出于以下原因而需要实现 Telecom API:

创建电话应用的替代应用

如需在 Android 设备上替换默认电话应用,请实现 InCallService API。您的实现必须满足以下要求:

  • 它不得具有任何通话功能,并且必须仅包含用于通话的界面。
  • 它必须处理 Telecom 框架知晓的所有通话,并且不得对通话性质做出假设。例如,它不得假定通话是基于 SIM 卡的电话通话,也不得实现基于任何一个 ConnectionService 的通话限制,例如针对视频通话强制执行电话限制。

如需了解详情,请参阅 InCallService

集成呼叫解决方案

如需将通话解决方案集成到 Android 中,您可以使用以下选项:

  • 实现自管理的 Core-Telecom Jetpack 库:此选项非常适合不想在默认的电话应用中显示其通话,也不想在其界面中显示其他通话的独立通话应用开发者。

    集成 Core-Telecom Jetpack 库后,您的应用不仅可以与设备上的系统电话通话功能互操作,还可以与集成了 Telecom 的其他独立通话应用互操作。Core-Telecom 库还会管理音频路由和焦点。如需了解详情,请参阅构建通话应用

  • 实现托管的 ConnectionService API:此选项有助于开发依赖于现有设备电话应用来提供通话界面的通话解决方案。例如,第三方实现的 SIP 通话和 VoIP 通话服务。如需了解详情,请参阅 getDefaultDialerPackage()

    ConnectionService 本身只能提供连接通话的方法。它没有关联的界面。

  • 同时实现 InCallService 和 ConnectionService API:如果您想创建自己的基于 ConnectionService 的通话解决方案(包括自己的界面),并在同一界面中显示所有其他 Android 通话,此选项非常适合。使用此方法时,InCallService 的实现不得对其显示的调用的来源做出任何假设。此外,即使默认电话应用未设置为自定义 InCallService,您的 ConnectionService 实现也必须继续正常运行。

过滤来电

在搭载 Android 10(API 级别 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());

您必须在清单文件中使用适当的 intent 过滤器和权限注册 CallScreeningService 实现,以便系统能够正确触发它。

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

重定向呼叫

搭载 Android 10 或更高版本的设备与搭载 Android 9 或更低版本的设备管理通话 intent 的方式不同。在 Android 10 及更高版本中,ACTION_NEW_OUTGOING_CALL 广播已废弃,取而代之的是 CallRedirectionService API。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 创建的 intent,因此请确保替换 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);
            }
        }
    }
}