15、电话与通讯应用:Telecom API集成、CarPhoneState、通话界面设计、蓝牙电话适配

电话功能在车载系统里,说实话,是个「看起来简单,做起来坑多」的模块。用户期望的就是「上车、打电话、挂断」,但背后涉及到的技术栈,从Telecom框架到蓝牙协议栈,再到多屏幕交互,每一步都可能让你头疼。我最早接触Android Automotive的电话模块时,以为跟手机端差不多,结果发现车载场景下,很多手机上的「常识」都不太适用。

这一章,我们重点聊聊四个核心话题:Telecom API怎么集成、CarPhoneState怎么用、通话界面怎么设计、以及蓝牙电话怎么适配。嗯,内容不少,我们一个一个来。

15.1 Telecom API集成:别自己造轮子

Android系统本身提供了Telecom框架,用来管理通话。说白了,它就是通话的「调度中心」。你不需要自己去管理音频焦点、通话状态、来电通知这些事,Telecom帮你做了。

在车载场景下,集成Telecom API的核心是注册一个ConnectionService。我习惯把它理解成「通话的代理」——系统把通话事件发给你,你决定怎么展示、怎么响应用户操作。

核心要点:不要直接操作TelephonyManager来管理通话。用ConnectionService,系统会帮你处理好音频路由、通话保持、多方通话等复杂逻辑。

一个典型的ConnectionService实现大概长这样:

public class CarConnectionService extends ConnectionService {
    @Override
    public Connection onCreateOutgoingConnection(
            PhoneAccountHandle connectionManagerAccount,
            ConnectionRequest request) {
        // 创建呼出通话
        CarConnection connection = new CarConnection();
        connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
        connection.setInitializing();
        return connection;
    }

    @Override
    public Connection onCreateIncomingConnection(
            PhoneAccountHandle connectionManagerAccount,
            ConnectionRequest request) {
        // 创建来电通话
        CarConnection connection = new CarConnection();
        connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
        connection.setRinging();
        return connection;
    }
}

这里有个坑,我曾经踩过:Connection对象的状态管理一定要跟Telecom框架保持一致。比如你手动调了setActive(),但Telecom那边还没确认,就会出现状态不同步的问题。我的建议是,尽量用框架提供的回调方法,不要自己「偷偷」改状态。

15.2 CarPhoneState:车载特有的通话状态

手机端有TelephonyManager监听通话状态,但在车载上,Google推荐用CarPhoneState。为什么?因为车载系统可能没有SIM卡(比如通过蓝牙连接手机),这时候TelephonyManager拿不到数据。

CarPhoneState是Android Automotive的一个系统服务,它聚合了来自不同来源的通话状态——可能是内置eSIM,也可能是蓝牙手机。你只需要订阅它的回调,就能拿到统一的通话状态。

用法很简单:

Car car = Car.createCar(context);
CarPhoneState phoneState = car.getCarManager(CarPhoneState.class);

phoneState.registerCallback(new CarPhoneStateCallback() {
    @Override
    public void onCallStateChanged(int state, String phoneNumber) {
        // state: CALL_STATE_IDLE, CALL_STATE_RINGING, CALL_STATE_OFFHOOK
        switch (state) {
            case CarPhoneState.CALL_STATE_RINGING:
                // 显示来电界面
                break;
            case CarPhoneState.CALL_STATE_OFFHOOK:
                // 通话中
                break;
            case CarPhoneState.CALL_STATE_IDLE:
                // 空闲
                break;
        }
    }
});

小技巧:在车载上,CarPhoneStateonCallStateChanged回调可能比手机端慢一点(因为要经过蓝牙传输)。所以UI的响应不要完全依赖这个回调,可以结合TelecomManageraddListener做双重保障。

15.3 通话界面设计:安全第一,操作第二

车载通话界面跟手机最大的区别是什么?是「驾驶安全」。你不能让用户盯着屏幕找挂断按钮,也不能让用户去点一个很小的图标。

我个人习惯把通话界面分成三个区域:

  • 顶部区域:显示联系人信息、通话时长、通话状态。字体要大,至少24sp以上。
  • 中部区域:核心操作按钮。挂断、接听、静音、键盘。按钮尺寸建议不小于64dp。
  • 底部区域:辅助信息,比如通话录音提示、多方通话列表。

这里有一个设计原则,我称之为「拇指可达区」——在方向盘附近或者中控台靠近驾驶员的一侧,放置最常用的操作(挂断、接听)。不常用的操作(比如拨号键盘)放在副驾侧或者二级菜单里。

注意:通话界面不要使用动画过渡效果,尤其是旋转、缩放这类容易引起眩晕的动画。我曾经见过一个项目,来电界面用了「翻转」动画,结果用户反馈说开车时看着头晕。后来改成了简单的淡入淡出。

代码层面,建议用CarUiController来管理通话界面的生命周期。不要直接启动Activity,而是通过CarUiControllershowDialogshowFragment来展示。这样系统可以更好地管理多窗口和驾驶模式。

15.4 蓝牙电话适配:最头疼的部分

蓝牙电话适配,说白了就是让车载系统通过蓝牙跟手机通话。这里涉及两个协议:HFP(Hands-Free Profile)和PBAP(Phone Book Access Profile)。

HFP负责音频传输和通话控制,PBAP负责同步通讯录。嗯,这两个协议在Android Automotive里都有对应的实现。

我遇到过最典型的问题是:蓝牙连接后,通话音频没有路由到车载扬声器。原因通常是AudioManager的音频焦点没有正确设置。解决方案是:

AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
// 设置音频路由到蓝牙 SCO 通道
audioManager.setBluetoothScoOn(true);
// 请求音频焦点
int result = audioManager.requestAudioFocus(
        afChangeListener,
        AudioManager.STREAM_VOICE_CALL,
        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);

另一个常见问题是通讯录同步不完整。PBAP协议有大小限制,有些手机会截断通讯录。我的建议是:不要完全依赖PBAP,可以同时使用ContactsContract读取本地联系人作为补充。

避坑指南:我曾经在一个项目里,蓝牙电话的「接听」按钮点了没反应。查了两天才发现,是因为ConnectionService没有正确处理onAnswer()回调。记住,蓝牙电话的接听/挂断操作,最终都要通过Connection对象的onAnswer()onDisconnect()来触发,而不是直接调用TelephonyManageranswerRingingCall()

15.5 知识体系总览

下面这张图,我把电话与通讯模块的核心逻辑画了出来。你可以看到,从用户操作到最终通话建立,中间经过了Telecom框架、CarPhoneState、蓝牙协议栈等多个层级。

电话与通讯模块知识体系 用户交互层 通话界面(UI) | 按钮操作 | 状态显示 业务逻辑层 ConnectionService | CarPhoneState | 通话状态管理 音频焦点管理 | 多方通话控制 框架与协议层 Telecom API | Bluetooth HFP | Bluetooth PBAP 音频路由 | 通讯录同步 硬件层(蓝牙芯片 / 音频编解码)

你看,从用户点击「接听」到最终蓝牙耳机里传出声音,中间经过了四层处理。每一层都有各自的职责,也都有各自的坑。我的经验是:调试时从底层往上查,先确认蓝牙连接正常,再检查音频路由,最后看UI响应。

好了,电话与通讯这块的内容就聊到这儿。记住,车载电话的核心不是功能多,而是「稳」。用户开车时,一个稳定的通话体验比什么都重要。


公众号:蓝海资料掘金营,微信deep3321