Android 14 蓝牙 HFP(AG 侧代码流程梳理)

这里选用的代码是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;
    }
}

如果满足以下条件之一,或者强制更新:

  1. 没有待切换的呼叫;
  2. 活动通话的数量、子通话数量、保持通话的数量、蓝牙通话状态、响铃地址、响铃地址类型或保持呼叫发生变化;
  3. 需要忽略保持呼叫更改。 则执行以下操作:
  4. 如果蓝牙通话正在进入ING状态,先发送DIALING状态;
  5. 更新相关变量的值;
  6. 调用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();   ---> 指的就是HeadsetService1217          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:

  1. 首先是初始化:BluetoothHeadsetServiceJni: classInitNative: succeeds
  2. 然后当你在蓝牙列表界面选择某个蓝牙去连接时:
    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
    在这里插入图片描述
  3. 底层连接成功回调到上层,此时设置当前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侧
在这里插入图片描述

  1. 设置完当前设备为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;
 }
  1. 再回到正题,第四步,通过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
    在这里插入图片描述
  2. Audio 状态连接成功再告诉telecom。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值