Android 藍牙開發(八)hfp接聽、掛斷電話

轉載請註明出處:http://blog.csdn.net/vnanyesheshou/article/details/71429860

本文已授權微信公衆號 fanfan程序媛 獨家發佈 掃一掃文章底部的二維碼或在微信搜索 fanfan程序媛 即可關注

繼續研究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()代碼如下:

private void processAnswerCall() {
    if (mPhoneProxy != null) {
        try {
            //mPhoneProxy是通過bindservice 獲取的。
            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界面,向外進行撥打電話。

歡迎掃一掃關注我的微信公衆號,定期推送優質技術文章:

這裏寫圖片描述

發佈了129 篇原創文章 · 獲贊 270 · 訪問量 97萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章