繼續研究hfp相關功能。藍牙耳機能夠控制手機接聽、拒接、掛斷電話,撥打電話等功能。本文主要分析下起這些操做的大體流程。
在系統應用Bluetooth中com_android_bluetooth.cpp提供了多個回調方法,由hardware、協議棧回調過來。藍牙耳機的一些控制命令都會發到這裏。 android
本文基於Android4.3源碼。ide
1 接通電話 |
藍牙耳機控制手機接通電話,回掉com_android_bluetooth.cpp中的answer_call_callback()函數,該函數主要操做是調用HeadsetStateMachine的onAnswerCall()函數,代碼以下:函數
在onAnswerCall()中發送消息(消息類型STACK_EVENT,StackEvent事件類型EVENT_TYPE_ANSWER_CALL)向狀體機,此時通話還沒有接通,audio沒有鏈接,因此此時處於Connected狀態。狀態機收到該消息後調用processAnswerCall()函數。processAnswerCall()代碼以下:測試
private void processAnswerCall() {
ui
if (mPhoneProxy != null) {
spa
try {
code
//mPhoneProxy是經過bindservice 獲取的。
blog
mPhoneProxy.answerCall();
接口
} catch (RemoteException e) {
事件
}
} else {
}
}
初始化的時候會bind service,綁定的該service爲系統應用Phone下的BluetoothPhoneService(AndroidManifest中該service的action爲android.bluetooth.IBluetoothHeadsetPhone),代碼以下:
//參數爲android.bluetooth.IBluetoothHeadsetPhone
Intent intent = new Intent(IBluetoothHeadsetPhone.class.getName());
//resolveSystemService該方法是hide的,由系統使用的特殊功能來解決系統應用程序的服務意圖。
intent.setComponent(intent.resolveSystemService(context.getPackageManager(), 0));
if (intent.getComponent() == null || !context.bindService(intent, mConnection, 0)) {
Log.e(TAG, "Could not bind to Bluetooth Headset Phone Service");
}
綁定service成功回調mConnection,在其成功回調中設置的mPhoneProxy。經過mPhoneProxy來調用service中提供的接口。mPhoneProxy.answerCall()跳到BluetoothPhoneService中answerCall。
public boolean answerCall() {
//申請權限,修改電話狀態
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
return PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
}
PhoneUtils調用answerCall,在這裏面去接通電話。answerCall()就不具體分析了。
2 拒接、掛斷電話 |
藍牙耳機控制手機拒接、掛斷電話,回掉com_android_bluetooth.cpp中的hangup_call_callback()函數,該函數主要操做是調用HeadsetStateMachine的onHangupCall()函數,代碼以下:
private void onHangupCall() {
StackEvent event = new StackEvent(EVENT_TYPE_HANGUP_CALL);
sendMessage(STACK_EVENT, event);
}
此時HeadsetStateMachine可能處於Conneted或AudioOn狀態,這兩種狀態收到該消息的處理同樣,都是調用processHangupCall(),代碼以下:
private void processHangupCall() {
if (isVirtualCallInProgress()) {
//對於虛擬電話,結束。
terminateScoUsingVirtualVoiceCall();
} else {
if (mPhoneProxy != null) {
try { //掛斷電話
mPhoneProxy.hangupCall();
} catch (RemoteException e) {
}
} else {
}
}
}
對於虛擬電話則直接將其結束。真實的通話跳到BluetoothPhoneService的hangupCall。
public boolean hangupCall() {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
if (mCM.hasActiveFgCall()) { //掛斷正在進行的通話
return PhoneUtils.hangupActiveCall(mCM.getActiveFgCall());
} else if (mCM.hasActiveRingingCall()) { //中止正在響鈴的電話
return PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
} else if (mCM.hasActiveBgCall()) { //掛斷保持的電話
return PhoneUtils.hangupHoldingCall(mCM.getFirstActiveBgCall());
}
return false;
}
hangupCall中會根據狀態處理通話,優先處理正在進行的通話、其次是還沒有接通的電話、最後是保持的電話。
3 更改通話音量 |
藍牙耳機更改通話的音量,回掉com_android_bluetooth.cpp中的volume_control_callback()函數,該函數主要操做是調用HeadsetStateMachine的onVolumeChnaged()函數,代碼以下:
private void onVolumeChanged(int type, int volume) {
StackEvent event = new StackEvent(EVENT_TYPE_VOLUME_CHANGED);
event.valueInt = type;
event.valueInt2 = volume;
sendMessage(STACK_EVENT, event);
}
此時HeadsetStateMachine可能處於Conneted或AudioOn狀態,這兩種狀態收到該消息的處理同樣,都是調用processVolumeEvent,代碼以下:
private void processVolumeEvent(int volumeType, int volume) {
if (volumeType == HeadsetHalConstants.VOLUME_TYPE_SPK) {
mPhoneState.setSpeakerVolume(volume);
//是否在ui上顯示
int flag = (getCurrentState() == mAudioOn) ? AudioManager.FLAG_SHOW_UI : 0;
//設置SCO通道聲音大小。
mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, flag);
} else if (volumeType == HeadsetHalConstants.VOLUME_TYPE_MIC) {
// 只是存了下該volume值,並無設置mic。
mPhoneState.setMicVolume(volume);
} else {
}
}
更改音量兩種類型,VOLUME_TYPE_MIC類型,保存了下該值,並無看到具體用該值的地方。對於VOLUME_TYPE_SPK類型的,會設置SCO聲音大小。若是此時處於AudioOn狀態,則會在UI上顯示。
4 撥打電話 |
藍牙耳機進行撥打電話,回掉com_android_bluetooth.cpp中的dial_call_callback函數,該函數主要操做是調用HeadsetStateMachine的onDialCall()函數,代碼以下:
private void onDialCall(String number) {
StackEvent event = new StackEvent(EVENT_TYPE_DIAL_CALL);
event.valueString = number;
sendMessage(STACK_EVENT, event);
}
此時HeadsetStateMachine可能處於Conneted或AudioOn狀態,這兩種狀態收到該消息的處理同樣,都是調用processDialCall,代碼以下:
private void processDialCall(String number) {
String dialNumber;
if ((number == null) || (number.length() == 0)) {
//獲取最近向外打的電話號碼
dialNumber = mPhonebook.getLastDialledNumber();
if (dialNumber == null) { //沒有最近撥打的電話,迴應error
atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
return;
}
} else if (number.charAt(0) == '>') {
//測試
} else {
// Remove trailing ';'
if (number.charAt(number.length() - 1) == ';') {
number = number.substring(0, number.length() - 1);
}
dialNumber = PhoneNumberUtils.convertPreDial(number);
}
terminateScoUsingVirtualVoiceCall(); // 終止虛擬呼叫
Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
Uri.fromParts(SCHEME_TEL, dialNumber, null));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mService.startActivity(intent); //開啓撥打電話的界面
mDialingOut = true;
sendMessageDelayed(DIALING_OUT_TIMEOUT, DIALING_OUT_TIMEOUT_VALUE);
}
藍牙耳機發過來的命令可能攜帶電話號碼,也可能不帶,對於沒有電話號碼則查詢最近的撥打電話記錄,撥打最近撥打的電話。對於有號碼,則撥打該號碼。 Intent.ACTION_CALL_PRIVILEGED(該變量是hide的,執行任何號碼的呼叫,緊急或不緊急):」android.intent.action.CALL_PRIVILEGED」 經過該action打開系統應用Phone中的OutgoingCallBroadcaster界面,向外進行撥打電話。