蓝牙的HFP协议定义了两个角色,一个是HF端,如蓝牙耳机、车机端等免提设备;一个是AG端,通常指的就是手机端。在Android系统里面,也有对应的两个Profile,HF端对应HeadsetClient Profile,而AG端对应Headset Profile。下面我们就通过拨号指令,梳理Headset端的处理过程。
首先来看一下整体的流程图:
1.
首先拨号的指令在HF端发出,经过两端蓝牙chip的信号传递,到达AG侧的协议栈中,在协议栈经过解析之后,已经明确了此为一个拨号的请求,于是就会通过JNI层回调到蓝牙服务中,具体是在HeadSetNativeInterface这个类里:
private void onDialCall(String number, byte[] address) { //1.JNI把协议栈的信号回调
HeadsetStackEvent event =
new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_DIAL_CALL, number,
getDevice(address));
sendMessageToService(event);
}
HeadSetNativeInterface会把消息传递给HeadSetService去处理。
HeadSetService是整个HeadsetProfile的核心所在,它继承自ProfileService,作为一个后台服务在一直运行。在本进程内,连接HeadSetNativeInterface和HeadsetStateMachine;对外部进程,通过实现Binder的本地接口,提供远程服务。
接下来看请求拨号的消息在HeadSetService的处理:
void messageFromNative(HeadsetStackEvent stackEvent) {
.......
stateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT, stackEvent); //信号发送到状态机中
}
实际上,HeadSetService只是做了中转,然后就把协议栈(STACK)中的消息给到了状态机(HeadsetStateMachine)去处理。
能够接收到拨号求情,此时状态机肯定处于HFP协议的连接状态。由于HFP协议不仅会建立基础的协议连接(Level Connection),还会在通话的时候建立通话链路(SCO链路),所以在HeadsetStateMachine状态机中,有一个ConnectedBase状态对象,它代表HFP协议连接之后所处的状态,它的子类有Connected(代表Level Connection,即无SCO链路)、AudioConnecting(代表SCO链路连接中)、AudioOn(代表SCO链路建立)、AudioDisconnecting代表SCO链路断开中)。
HeadsetStackEvent.EVENT_TYPE_DIAL_CALL类型的信号,会在ConnectedBase中得到处理:
private abstract class ConnectedBase extends HeadsetStateBase {
......
case HeadsetStackEvent.EVENT_TYPE_DIAL_CALL:
processDialCall(event.valueString);
break;
}
}
private void processDialCall(String number) {
if (!mHeadsetService.dialOutgoingCall(mDevice, dialNumber)) { //调用HeadsetService的方法
Log.w(TAG, "processDialCall, failed to dial in service");
mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
return;
}
}
于是消息又回到了HeadsetService里面去处理,核心的代码如下:
public boolean dialOutgoingCall(BluetoothDevice fromDevice, String dialNumber) {
Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
Uri.fromParts(PhoneAccount.SCHEME_TEL, dialNumber, null));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); //这里去调用telecom拨打电话
mDialingOutTimeoutEvent = new DialingOutTimeoutEvent(fromDevice); //开启一个超时检测机制
mStateMachinesThread.getThreadHandler()
.postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS);
}
这里是去调用了telecom拨打电话的接口,真正地去呼叫一个号码。这是一个异步的过程,等到电话建立之后,就会通过telecom自身的回调机制通知给到感兴趣的监听者。在HeadSet端,这个telecom状态的监听者就是BluetoothInCallService。
BluetoothInCallService继承自InCallService,而telecom的通话建立或者移除有改变都会回调到InCallService相应的方法,自然也就会回调到BluetoothInCallService继承的方法:
public class BluetoothInCallService extends InCallService{
......
public void onCallAdded(BluetoothCall call) { //有新的通话创建的时候,telecom就会回调此方法
if (call.isExternalCall()) {
return;
}
if (!mBluetoothCallHashMap.containsKey(call.getId())) {
Log.d(TAG, "onCallAdded");
CallStateCallback callback = new CallStateCallback(call.getState());
mCallbacks.put(call.getId(), callback);
call.registerCallback(callback); //注册一个此通话状态改变的监听接口
mBluetoothCallHashMap.put(call.getId(), call);
updateHeadsetWithCallState(false /* force */); //通知绑定上HeadSetService通话状态有变
}
}
}
以上是新建通话时候的回调实现,主要做了两件事,1.注册一个针对于本次通话状态改变的监听接口,例如DIALING、ALERTING、ACTIVE等通话状态 2.通知HeadSetService通话建立,当然,这里没有类似于callAdd的方法,而是通过统一的接口通知,因此这里就是通知HeadSetService有一个通话处于DIALING状态。
HeadSetService的内部的Binder接口的本地实现,负责接收外部的调用信息:
private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub
implements IProfileServiceBinder {
@Override
public void phoneStateChanged(int numActive, int numHeld, int callState, String number, //HeadSetService接收到BluetoothInCallService发过来的通话状态改变的通知
int type, String name) {
HeadsetService service = getService();
if (service == null) {
return;
}
service.phoneStateChanged(numActive, numHeld, callState, number, type, name, false);
}
}
然后把消息给到HeadSetService,HeadSetService又把消息给到HeadsetStateMachine处理:
private void phoneStateChanged(int numActive, int numHeld, int callState, String number,
int type, String name, boolean isVirtualCall) {
........
doForStateMachine(mDialingOutTimeoutEvent.mDialingOutDevice,
stateMachine -> stateMachine.sendMessage(
HeadsetStateMachine.DIALING_OUT_RESULT, 1 /* success */, 0,
mDialingOutTimeoutEvent.mDialingOutDevice)); //首先反馈请求的操作成功
.........
doForEachConnectedStateMachine(
stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
new HeadsetCallState(numActive, numHeld, callState, number, type, name)));
//把最新的通话状态作为参数传递进去
}
这里有两次数据的反馈,首先对本次连接的设备发送一个拨号请求成功的反馈信息;然后再对连接上的每个设备都发送当前的状态信息。
在HeadsetStateMachine里面,都是在ConnectedBase这个连接状态基类里面处理的两个消息:
case DIALING_OUT_RESULT: { //拨号请求的AT指令结果反馈
BluetoothDevice device = (BluetoothDevice) message.obj;
......
mNativeInterface.atResponseCode(mDevice,
message.arg1 == 1 ? HeadsetHalConstants.AT_RESPONSE_OK
: HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
}
case CALL_STATE_CHANGED: { //电话状态改变
HeadsetCallState callState = (HeadsetCallState) message.obj;
if (!mNativeInterface.phoneStateChange(mDevice, callState)) { //调用native方法,把最新电话发送到底层协议栈,然后发送给HF端设备
stateLogW("processCallState: failed to update call state " + callState);
break;
}
}
最后都是调用了JNI对应的方法,然后把消息发送到协议栈里面,协议栈通过HCI再把消息指令给到蓝牙chip,最后完成信号的发送。