Android 电信框架(也简称为“Telecom”)管理 Android 设备上的音频和视频通话。这包括基于 SIM 卡的通话(例如使用电话框架的通话)以及实现 Core-Telecom
Jetpack 库的 VoIP 通话。
Telecom 管理的主要组件是 ConnectionService
和 InCallService
。
ConnectionService
的实现基于 PSTN 等技术,用于将通话连接到其他方。手机上最常见的 ConnectionService
实现是电话 ConnectionService
。它用于连接运营商通话。
InCallService
的实现为 Telecom 管理的通话提供用户界面,并允许用户控制和交互这些通话。最常见的 InCallService
实现是设备捆绑的电话应用。
Telecom 充当交换机。它将 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
实现不得对所显示通话的来源做任何假设。此外,您的ConnectionService
实现必须在未将默认电话应用设置为您的自定义InCallService
的情况下继续工作。
甄别来电
运行 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());
您必须在 manifest 文件中注册 CallScreeningService
实现,并带有适当的 intent 过滤器和权限,以便系统能够正确触发它。
<service
android:name=".ScreeningService"
android:permission="android.permission.BIND_SCREENING_SERVICE">
<intent-filter>
<action android:name="android.telecom.CallScreeningService" />
</intent-filter>
</service>
重定向通话
运行 Android 10 或更高版本的设备管理通话 Intent 的方式与运行 Android 9 或更低版本的设备不同。在 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(); } } }
您必须在 manifest 中注册此服务,以便系统能够正确启动它。
<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); } } } }