Android蓝牙开发【八】hfp接听、挂断电话

痞子三分冷 提交于 2019-12-05 19:56:52

继续研究hfp相关功能。蓝牙耳机可以控制手机接听、拒接、挂断电话,拨打电话等功能。本文主要分析下起这些操作的大致流程。 
在系统应用Bluetooth中com_android_bluetooth.cpp提供了多个回调方法,由hardware、协议栈回调过来。蓝牙耳机的一些控制命令都会发到这里。 

本文基于Android4.3源码。

 

 

1 接通电话

 

蓝牙耳机控制手机接通电话,回掉com_android_bluetooth.cpp中的answer_call_callback()函数,该函数主要操作是调用HeadsetStateMachine的onAnswerCall()函数,代码如下:

这里写图片描述
在onAnswerCall()中发送消息(消息类型STACK_EVENT,StackEvent事件类型EVENT_TYPE_ANSWER_CALL)向状体机,此时通话尚未接通,audio没有连接,所以此时处于Connected状态。状态机收到该消息后调用processAnswerCall()函数。processAnswerCall()代码如下:

 

 
  1. private void processAnswerCall() {

  2. if (mPhoneProxy != null) {

  3. try {

  4. //mPhoneProxy是通过bindservice 获取的。

  5. mPhoneProxy.answerCall();

  6. } catch (RemoteException e) {

  7. }

  8. } else {

  9. }

  10. }

初始化的时候会bind service,绑定的该service为系统应用Phone下的BluetoothPhoneService(AndroidManifest中该service的action为android.bluetooth.IBluetoothHeadsetPhone),代码如下:

 

 
  1. //参数为android.bluetooth.IBluetoothHeadsetPhone

  2. Intent intent = new Intent(IBluetoothHeadsetPhone.class.getName());

  3. //resolveSystemService该方法是hide的,由系统使用的特殊功能来解决系统应用程序的服务意图。

  4. intent.setComponent(intent.resolveSystemService(context.getPackageManager(), 0));

  5. if (intent.getComponent() == null || !context.bindService(intent, mConnection, 0)) {

  6. Log.e(TAG, "Could not bind to Bluetooth Headset Phone Service");

  7. }

绑定service成功回调mConnection,在其成功回调中设置的mPhoneProxy。通过mPhoneProxy来调用service中提供的接口。mPhoneProxy.answerCall()跳到BluetoothPhoneService中answerCall。

 

 
  1. public boolean answerCall() {

  2. //申请权限,修改电话状态

  3. enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);

  4. return PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());

  5. }

 

PhoneUtils调用answerCall,在这里面去接通电话。answerCall()就不具体分析了。


 

2 拒接、挂断电话

蓝牙耳机控制手机拒接、挂断电话,回掉com_android_bluetooth.cpp中的hangup_call_callback()函数,该函数主要操作是调用HeadsetStateMachine的onHangupCall()函数,代码如下:

 

 

 
  1. private void onHangupCall() {

  2. StackEvent event = new StackEvent(EVENT_TYPE_HANGUP_CALL);

  3. sendMessage(STACK_EVENT, event);

  4. }

此时HeadsetStateMachine可能处于Conneted或AudioOn状态,这两种状态收到该消息的处理一样,都是调用processHangupCall(),代码如下:

 
  1. private void processHangupCall() {

  2. if (isVirtualCallInProgress()) {

  3. //对于虚拟电话,结束。

  4. terminateScoUsingVirtualVoiceCall();

  5. } else {

  6. if (mPhoneProxy != null) {

  7. try { //挂断电话

  8. mPhoneProxy.hangupCall();

  9. } catch (RemoteException e) {

  10. }

  11. } else {

  12. }

  13. }

  14. }

对于虚拟电话则直接将其结束。真实的通话跳到BluetoothPhoneService的hangupCall。

 
  1. public boolean hangupCall() {

  2. enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);

  3. if (mCM.hasActiveFgCall()) { //挂断正在进行的通话

  4. return PhoneUtils.hangupActiveCall(mCM.getActiveFgCall());

  5. } else if (mCM.hasActiveRingingCall()) { //停止正在响铃的电话

  6. return PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());

  7. } else if (mCM.hasActiveBgCall()) { //挂断保持的电话

  8. return PhoneUtils.hangupHoldingCall(mCM.getFirstActiveBgCall());

  9. }

  10. return false;

  11. }

 

hangupCall中会根据状态处理通话,优先处理正在进行的通话、其次是尚未接通的电话、最后是保持的电话。


 

3 更改通话音量

蓝牙耳机更改通话的音量,回掉com_android_bluetooth.cpp中的volume_control_callback()函数,该函数主要操作是调用HeadsetStateMachine的onVolumeChnaged()函数,代码如下:

 
  1. private void onVolumeChanged(int type, int volume) {

  2. StackEvent event = new StackEvent(EVENT_TYPE_VOLUME_CHANGED);

  3. event.valueInt = type;

  4. event.valueInt2 = volume;

  5. sendMessage(STACK_EVENT, event);

  6. }

此时HeadsetStateMachine可能处于Conneted或AudioOn状态,这两种状态收到该消息的处理一样,都是调用processVolumeEvent,代码如下:
 

 
  1. private void processVolumeEvent(int volumeType, int volume) {

  2. if (volumeType == HeadsetHalConstants.VOLUME_TYPE_SPK) {

  3. mPhoneState.setSpeakerVolume(volume);

  4. //是否在ui上显示

  5. int flag = (getCurrentState() == mAudioOn) ? AudioManager.FLAG_SHOW_UI : 0;

  6. //设置SCO通道声音大小。

  7. mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, flag);

  8. } else if (volumeType == HeadsetHalConstants.VOLUME_TYPE_MIC) {

  9. // 只是存了下该volume值,并没有设置mic。

  10. mPhoneState.setMicVolume(volume);

  11. } else {

  12. }

  13. }

 

更改音量两种类型,VOLUME_TYPE_MIC类型,保存了下该值,并没有看到具体用该值的地方。对于VOLUME_TYPE_SPK类型的,会设置SCO声音大小。如果此时处于AudioOn状态,则会在UI上显示。


 

4 拨打电话

蓝牙耳机进行拨打电话,回掉com_android_bluetooth.cpp中的dial_call_callback函数,该函数主要操作是调用HeadsetStateMachine的onDialCall()函数,代码如下:

 

 
  1. private void onDialCall(String number) {

  2. StackEvent event = new StackEvent(EVENT_TYPE_DIAL_CALL);

  3. event.valueString = number;

  4. sendMessage(STACK_EVENT, event);

  5. }


此时HeadsetStateMachine可能处于Conneted或AudioOn状态,这两种状态收到该消息的处理一样,都是调用processDialCall,代码如下:

 
  1. private void processDialCall(String number) {

  2. String dialNumber;

  3. if ((number == null) || (number.length() == 0)) {

  4. //获取最近向外打的电话号码

  5. dialNumber = mPhonebook.getLastDialledNumber();

  6. if (dialNumber == null) { //没有最近拨打的电话,回应error

  7. atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);

  8. return;

  9. }

  10. } else if (number.charAt(0) == '>') {

  11. //测试

  12. } else {

  13. // Remove trailing ';'

  14. if (number.charAt(number.length() - 1) == ';') {

  15. number = number.substring(0, number.length() - 1);

  16. }

  17. dialNumber = PhoneNumberUtils.convertPreDial(number);

  18. }

  19. terminateScoUsingVirtualVoiceCall(); // 终止虚拟呼叫

  20. Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,

  21. Uri.fromParts(SCHEME_TEL, dialNumber, null));

  22. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

  23. mService.startActivity(intent); //开启拨打电话的界面

  24. mDialingOut = true;

  25. sendMessageDelayed(DIALING_OUT_TIMEOUT, DIALING_OUT_TIMEOUT_VALUE);

  26. }


蓝牙耳机发过来的命令可能携带电话号码,也可能不带,对于没有电话号码则查询最近的拨打电话记录,拨打最近拨打的电话。对于有号码,则拨打该号码。 
Intent.ACTION_CALL_PRIVILEGED(该变量是hide的,执行任何号码的呼叫,紧急或不紧急):”android.intent.action.CALL_PRIVILEGED” 
通过该action打开系统应用Phone中的OutgoingCallBroadcaster界面,向外进行拨打电话。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!