这里选用的代码是Android 14 :http://aospxref.com/android-14.0.0_r2/
首先我们需要大致知道一个流程:telecom那边会发状态过来给Bt,然后Bt再传给底层,发送命令给对端
这样根据这个流程看代码,好抓思路,那当状态变化时,就会走到下面的流程:
http://aospxref.com/android-14.0.0_r2/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/telephony/
BluetoothInCallService.java#updateHeadsetWithCallState
/**
* Sends an update of the current BluetoothCall state to the current Headset.
* @param force {@code true} if the headset state should be sent regardless if no changes to
* the state have occurred, {@code false} if the state should only be sent if the state has changed.
*/
private void updateHeadsetWithCallState(boolean force) {
//获取当前活动的通话、响铃的通话和保持的通话
BluetoothCall activeCall = mCallInfo.getActiveCall();
BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall();
BluetoothCall heldCall = mCallInfo.getHeldCall();
int bluetoothCallState = getBluetoothCallStateForUpdate();
//检查响铃的通话信息,包括响铃地址、响铃地址类型和响铃名称
String ringingAddress = null;
int ringingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
String ringingName = null;
if (!mCallInfo.isNullCall(ringingCall) && ringingCall.getHandle() != null
&& !ringingCall.isSilentRingingRequested()) {
ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
if (ringingAddress != null) {
ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
}
ringingName = ringingCall.getCallerDisplayName();
if (TextUtils.isEmpty(ringingName)) {
ringingName = ringingCall.getContactDisplayName();
}
}
if (ringingAddress == null) {
ringingAddress = "";
}
//计算活动通话的数量、保持通话的数量以及活动通话的子通话数量
int numActiveCalls = mCallInfo.isNullCall(activeCall) ? 0 : 1;
int numHeldCalls = mCallInfo.getNumHeldCalls();
int numChildrenOfActiveCall =
mCallInfo.isNullCall(activeCall) ? 0 : activeCall.getChildrenIds().size();
// Intermediate state for GSM calls which are in the process of being swapped.
// TODO: Should we be hardcoding this value to 2 or should we check if all top level calls are held?
boolean callsPendingSwitch = (numHeldCalls == 2); ---> 检查是否有待切换的呼叫(即有两个保持的呼叫)
// For conference calls which support swapping the active BluetoothCall within the
// conference (namely CDMA calls) we need to expose that as a held BluetoothCall
// in order for the BT device to show "swap" and "merge" functionality.
boolean ignoreHeldCallChange = false;
if (!mCallInfo.isNullCall(activeCall) && activeCall.isConference()
&& !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
// Indicate that BT device should show SWAP command by indicating that there is a
// BluetoothCall on hold, but only if the conference wasn't previously merged.
numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
} else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
numHeldCalls = 1; // Merge is available, so expose via numHeldCalls.
}
for (Integer id : activeCall.getChildrenIds()) {
// Held BluetoothCall has changed due to it being combined into a CDMA conference.
// Keep track of this and ignore any future update since it doesn't really count
// as a BluetoothCall change.
if (mOldHeldCall != null && Objects.equals(mOldHeldCall.getId(), id)) {
ignoreHeldCallChange = true;
break;
}
}
}
if (mBluetoothHeadset != null
&& (force
|| (!callsPendingSwitch
&& (numActiveCalls != mNumActiveCalls
|| numChildrenOfActiveCall != mNumChildrenOfActiveCall
|| numHeldCalls != mNumHeldCalls
|| bluetoothCallState != mBluetoothCallState
|| !TextUtils.equals(ringingAddress, mRingingAddress)
|| ringingAddressType != mRingingAddressType
|| (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) {
// If the BluetoothCall is transitioning into the alerting state, send DIALING first.
// Some devices expect to see a DIALING state prior to seeing an ALERTING state
// so we need to send it first.
boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState
&& bluetoothCallState == CALL_STATE_ALERTING;
mOldHeldCall = heldCall;
mNumActiveCalls = numActiveCalls;
mNumChildrenOfActiveCall = numChildrenOfActiveCall;
mNumHeldCalls = numHeldCalls;
mBluetoothCallState = bluetoothCallState;
mRingingAddress = ringingAddress;
mRingingAddressType = ringingAddressType;
// phoneStateChanged ---> 主要关注这个
if (sendDialingFirst) {
// Log in full to make logs easier to debug.
Log.i(TAG, "updateHeadsetWithCallState "
+ "numActive " + mNumActiveCalls + ", "
+ "numHeld " + mNumHeldCalls + ", "
+ "callState " + CALL_STATE_DIALING + ", "
+ "ringing type " + mRingingAddressType);
mBluetoothHeadset.phoneStateChanged(
mNumActiveCalls,
mNumHeldCalls,
CALL_STATE_DIALING,
mRingingAddress,
mRingingAddressType,
ringingName);
}
Log.i(TAG, "updateHeadsetWithCallState "
+ "numActive " + mNumActiveCalls + ", "
+ "numHeld " + mNumHeldCalls + ", "
+ "callState " + mBluetoothCallState + ", "
+ "ringing type " + mRingingAddressType);
mBluetoothHeadset.phoneStateChanged(
mNumActiveCalls,
mNumHeldCalls,
mBluetoothCallState,
mRingingAddress,
mRingingAddressType,
ringingName);
mHeadsetUpdatedRecently = true;
}
}
如果满足以下条件之一,或者强制更新:
- 没有待切换的呼叫;
- 活动通话的数量、子通话数量、保持通话的数量、蓝牙通话状态、响铃地址、响铃地址类型或保持呼叫发生变化;
- 需要忽略保持呼叫更改。 则执行以下操作:
- 如果蓝牙通话正在进入ING状态,先发送DIALING状态;
- 更新相关变量的值;
- 调用mBluetoothHeadset.phoneStateChanged()方法,传入更新后的参数值。
继续追代码:http://aospxref.com/android-14.0.0_r2/xref/packages/modules/Bluetooth/framework/java/android/bluetooth/
BluetoothHeadset.java#phoneStateChanged
1214 public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
1215 int type, String name) {
1216 final IBluetoothHeadset service = getService(); ---> 指的就是HeadsetService类
1217 if (service == null) {
1218 Log.w(TAG, "Proxy not attached to service");
1219 if (DBG) log(Log.getStackTraceString(new Throwable()));
1220 } else if (isEnabled()) {
1221 try {
1222 service.phoneStateChanged(numActive, numHeld, callState, number, type, name,
1223 mAttributionSource); ---> 继续看这个
1224 } catch (RemoteException e) {
1225 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1226 }
1227 }
1228 }
// http://aospxref.com/android-14.0.0_r2/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/hfp/
HeadsetService.java#1810
最终看这里:numActive(活动电话数量)、numHeld(保持的电话数量)、callState(通话状态)、number(电话号码)、type(类型)、
name(名称)和isVirtualCall(是否为虚拟通话)
1809 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
1810 void phoneStateChanged(int numActive, int numHeld, int callState, String number,
1811 int type, String name, boolean isVirtualCall) {
1812 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
1813 synchronized (mStateMachines) {
1814 // Should stop all other audio mode in this case
1815 if ((numActive + numHeld) > 0 || callState != HeadsetHalConstants.CALL_STATE_IDLE) {
1816 if (!isVirtualCall && mVirtualCallStarted) {
1817 // stop virtual voice call if there is an incoming Telecom call update
1818 stopScoUsingVirtualVoiceCall();
1819 }
1820 if (mVoiceRecognitionStarted) {
1821 // stop voice recognition if there is any incoming call
1822 stopVoiceRecognition(mActiveDevice);
1823 }
1824 }
1825 if (mDialingOutTimeoutEvent != null) { ---> 现在是AG端发起,所以这个不成立
1826 // Send result to state machine when dialing starts
1827 if (callState == HeadsetHalConstants.CALL_STATE_DIALING) {
1828 getStateMachinesThreadHandler()
1829 .removeCallbacks(mDialingOutTimeoutEvent);
1830 doForStateMachine(mDialingOutTimeoutEvent.mDialingOutDevice,
1831 stateMachine -> stateMachine.sendMessage(
1832 HeadsetStateMachine.DIALING_OUT_RESULT, 1 /* success */, 0,
1833 mDialingOutTimeoutEvent.mDialingOutDevice));
1834 } else if (callState == HeadsetHalConstants.CALL_STATE_ACTIVE
1835 || callState == HeadsetHalConstants.CALL_STATE_IDLE) {
1836 // Clear the timeout event when the call is connected or disconnected
1837 if (!getStateMachinesThreadHandler()
1838 .hasCallbacks(mDialingOutTimeoutEvent)) {
1839 mDialingOutTimeoutEvent = null;
1840 }
1841 }
1842 }
1843 }
1844 getStateMachinesThreadHandler().post(() -> {
1845 boolean isCallIdleBefore = mSystemInterface.isCallIdle();
1846 mSystemInterface.getHeadsetPhoneState().setNumActiveCall(numActive);
1847 mSystemInterface.getHeadsetPhoneState().setNumHeldCall(numHeld);
1848 mSystemInterface.getHeadsetPhoneState().setCallState(callState);
1849 // Suspend A2DP when call about is about to become active ---> 暂停A2DP
1850 if (mActiveDevice != null && callState != HeadsetHalConstants.CALL_STATE_DISCONNECTED
1851 && !mSystemInterface.isCallIdle() && isCallIdleBefore) {
1852 mSystemInterface.getAudioManager().setA2dpSuspended(true);
1853 mSystemInterface.getAudioManager().setLeAudioSuspended(true);
1854 }
1855 });
//发送 CALL_STATE_CHANGED 给状态机
1856 doForEachConnectedStateMachine(
1857 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
1858 new HeadsetCallState(numActive, numHeld, callState, number, type, name)));
1859 getStateMachinesThreadHandler().post(() -> {
1860 if (callState == HeadsetHalConstants.CALL_STATE_IDLE
1861 && mSystemInterface.isCallIdle() && !isAudioOn()) {
1862 // Resume A2DP when call ended and SCO is not connected ---> 恢复A2DP
1863 mSystemInterface.getAudioManager().setA2dpSuspended(false);
1864 mSystemInterface.getAudioManager().setLeAudioSuspended(false);
1865 }
1866 });
1867 if (callState == HeadsetHalConstants.CALL_STATE_IDLE) {
1868 final HeadsetStateMachine stateMachine = mStateMachines.get(mActiveDevice);
1869 if (stateMachine == null) {
1870 Log.d(TAG, "phoneStateChanged: CALL_STATE_IDLE, mActiveDevice is Null");
1871 } else {
1872 BluetoothSinkAudioPolicy currentPolicy = stateMachine.getHfpCallAudioPolicy();
1873 if (currentPolicy != null && currentPolicy.getActiveDevicePolicyAfterConnection()
1874 == BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED) {
1875 /**
1876 * If the active device was set because of the pick up audio policy
1877 * and the connecting policy is NOT_ALLOWED, then after the call is
1878 * terminated, we must de-activate this device.
1879 * If there is a fallback mechanism, we should follow it.
1880 */
1881 removeActiveDevice();
1882 }
1883 }
1884 }
1885 }
OK,我们继续看事件到状态机里面如何处理:http://aospxref.com/android-14.0.0_r2/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java#911
911 case CALL_STATE_CHANGED: {
912 HeadsetCallState callState = (HeadsetCallState) message.obj;
913 if (!mNativeInterface.phoneStateChange(mDevice, callState)) { ---> 一看就是通过JNI往协议栈,然后发给耳机
914 stateLogW("processCallState: failed to update call state " + callState);
915 break;
916 }
917 break;
918 }
最终调用到:http://aospxref.com/android-14.0.0_r2/xref/packages/modules/Bluetooth/android/app/jni/
com_android_bluetooth_hfp.cpp#phoneStateChangeNative
878 static jboolean phoneStateChangeNative(JNIEnv* env, jobject object,
879 jint num_active, jint num_held,
880 jint call_state, jstring number_str,
881 jint type, jstring name_str,
882 jbyteArray address) {
883 std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
884 if (!sBluetoothHfpInterface) {
885 ALOGW("%s: sBluetoothHfpInterface is null", __func__);
886 return JNI_FALSE;
887 }
888 jbyte* addr = env->GetByteArrayElements(address, nullptr);
889 if (!addr) {
890 ALOGE("%s: failed to get device address", __func__);
891 jniThrowIOException(env, EINVAL);
892 return JNI_FALSE;
893 }
894 const char* number = env->GetStringUTFChars(number_str, nullptr);
895 const char* name = nullptr;
896 if (name_str != nullptr) {
897 name = env->GetStringUTFChars(name_str, nullptr);
898 }
899 bt_status_t status = sBluetoothHfpInterface->PhoneStateChange( ---> 主要看这个
900 num_active, num_held, (bluetooth::headset::bthf_call_state_t)call_state,
901 number, (bluetooth::headset::bthf_call_addrtype_t)type, name,
902 (RawAddress*)addr);
903 if (status != BT_STATUS_SUCCESS) {
904 ALOGE("Failed report phone state change, status: %d", status);
905 }
906 env->ReleaseStringUTFChars(number_str, number);
907 if (name != nullptr) {
908 env->ReleaseStringUTFChars(name_str, name);
909 }
910 env->ReleaseByteArrayElements(address, addr, 0);
911 return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
912 }
// http://aospxref.com/android-14.0.0_r2/xref/packages/modules/Bluetooth/system/btif/src/btif_hf.cc#PhoneStateChange
这个方法里内容太多了,我们就看和流程性相关的代码:
BTA_AgResult ---> bta_ag_api_result ---> bta_ag_sm_execute ---> bta_ag_better_state_machine ---> bta_ag_result ---> bta_ag_hfp_result (处理事件:BTA_AG_OUT_CALL_ORIG_RES,代码如下:)
case BTA_AG_OUT_CALL_ORIG_RES:
bta_ag_send_call_inds(p_scb, result.result);
if (result.data.audio_handle == bta_ag_scb_to_idx(p_scb) && !(p_scb->features & BTA_AG_FEAT_NOSCO)) {
if (!bta_ag_is_sco_open_allowed(p_scb, bta_ag_result_text(result.result))) {
break;
}
bta_ag_sco_open(p_scb, tBTA_AG_DATA::kEmpty); ---> 打开sco通路
}
break;
void bta_ag_sco_open(tBTA_AG_SCB* p_scb, UNUSED_ATTR const tBTA_AG_DATA& data) {
if (!sco_allowed) {
LOG(INFO) << __func__ << ": not opening sco, by policy";
return;
}
if (data.api_audio_open.force_cvsd) {
LOG(INFO) << __func__ << ": set to use fallback codec";
p_scb->codec_fallback = true;
}
/* if another scb using sco, this is a transfer */
if (bta_ag_cb.sco.p_curr_scb && bta_ag_cb.sco.p_curr_scb != p_scb) {
LOG(INFO) << __func__ << ": transfer "
<< bta_ag_cb.sco.p_curr_scb->peer_addr << " -> "
<< p_scb->peer_addr;
bta_ag_sco_event(p_scb, BTA_AG_SCO_XFER_E);
} else {
/* else it is an open */
LOG(INFO) << __func__ << ": open " << p_scb->peer_addr;
bta_ag_sco_event(p_scb, BTA_AG_SCO_OPEN_E); ---> 可以继续看这个,就是通过RF发出去
}
}
到此,我们可以看到phoneStateChanged就通过协议栈发给对端(这里是耳机)
这里我们总结下整体全部流程(包含上面的内容),对应下log:
- 首先是初始化:BluetoothHeadsetServiceJni: classInitNative: succeeds
- 然后当你在蓝牙列表界面选择某个蓝牙去连接时:
HeadsetService: connect: device=B0:F1:A3:XX:XX:XX, uid/pid=10108/0
BluetoothHeadsetServiceJni: connectHfpNative: device b0:f1:a3:xx:xx:xx
BluetoothHeadsetServiceJni: ConnectionStateCallback 2 for b0:f1:a3:xx:xx:xx
BluetoothHeadsetServiceJni: ConnectionStateCallback 3 for b0:f1:a3:xx:xx:xx —> 3 对应的就是SLC
- 底层连接成功回调到上层,此时设置当前device为active:
HeadsetStateMachine: Connected: currentDevice=B0:F1:A3:XX:XX:XX, msg=connection state changed: B0:F1:A3:XX:XX:XX: Connecting -> Connected
HeadsetStateMachine: Connected: currentDevice=B0:F1:A3:XX:XX:XX, msg=broadcastConnectionState B0:F1:A3:XX:XX:XX: 1->2
BluetoothActiveDeviceManager: handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): device B0:F1:A3:XX:XX:XX connected
BluetoothActiveDeviceManager: setHfpActiveDevice(B0:F1:A3:XX:XX:XX)
HeadsetService: setActiveDevice: device=B0:F1:A3:XX:XX:XX, uid/pid=1002/31288
HeadsetService: broadcastActiveDevice: B0:F1:A3:XX:XX:XX
注意的点是,如果要设置sco通路只能是三种方式,这个看代码解释:
phoneStateChanged这个就是我们一开始花篇幅说的一个方法,telecom就是通过这个来通知蓝牙这边去通过协议栈通知到HF侧
- 设置完当前设备为active设备后,我们此时如果在AG侧打电话,telecom就会通知走到BluetoothHeadset.java#phoneStateChanged(开篇就有这个流程介绍,这里就不重复了)。有时候我们看telecom代码时发现BluetoothRouteManager#connectBtAudio 会去调用BluetoothHeadset#connectAudio() —> HeadsetService#connectAudio 方法
// HeadsetService#connectAudio 方法看下:
int connectAudio(BluetoothDevice device) {
Log.i(TAG, "connectAudio: device=" + device + ", " + Utils.getUidPidString());
synchronized (mStateMachines) {
final HeadsetStateMachine stateMachine = mStateMachines.get(device);
if (stateMachine == null) {
Log.w(TAG, "connectAudio: device " + device + " was never connected/connecting");
return BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED;
}
int scoConnectionAllowedState = isScoAcceptable(device); ---> 这个地方就会判断是不是我们上面说的三种,不是就reject
if (scoConnectionAllowedState != BluetoothStatusCodes.SUCCESS) {
Log.w(TAG, "connectAudio, rejected SCO request to " + device);
return scoConnectionAllowedState;
}
if (stateMachine.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
Log.w(TAG, "connectAudio: profile not connected");
return BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED;
}
if (stateMachine.getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
logD("connectAudio: audio is not idle for device " + device);
return BluetoothStatusCodes.SUCCESS;
}
if (isAudioOn()) {
Log.w(TAG, "connectAudio: audio is not idle, current audio devices are "
+ Arrays.toString(getNonIdleAudioDevices().toArray()));
return BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED;
}
stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device);
}
return BluetoothStatusCodes.SUCCESS;
}
- 再回到正题,第四步,通过phoneStateChanged 就打开了sco,然后再发起connectAudio,连接成功会有如下log打印:
HeadsetService: connectAudio: device=B0:F1:A3:XX:XX:XX, uid/pid=1000/0
BluetoothHeadsetServiceJni: connectAudioNative: device b0:f1:a3:xx:xx:xx
BluetoothHeadsetServiceJni: AudioStateCallback, 1 for b0:f1:a3:xx:xx:xx
BluetoothHeadsetServiceJni: AudioStateCallback, 2 for b0:f1:a3:xx:xx:xx —>2 是 connected
- Audio 状态连接成功再告诉telecom。