前言:由于通话比较特殊,Android对于通话过程中音频输出设备的切换做了特殊处理,它在上层也是通过切换音频播放状态来完成切换操作的,android用CallAudioState来封装通话过程中的音频播放状态;

1.CallAudioState解析
CallAudioState的属性

CallAudioState.java
public static final int ROUTE_EARPIECE      = 0x00000001;//听筒
public static final int ROUTE_BLUETOOTH     = 0x00000002;//蓝牙
public static final int ROUTE_WIRED_HEADSET = 0x00000004;//有线耳机
public static final int ROUTE_SPEAKER       = 0x00000008;//扬声器
public static final int ROUTE_WIRED_OR_EARPIECE = ROUTE_EARPIECE | ROUTE_WIRED_HEADSET;//听筒或者耳机
public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET |ROUTE_SPEAKER;//全部状态

通话过程中的音频播放状态共有4种,分别是听筒,蓝牙,有线耳机,扬声器;

CallAudioState中的重要方法有:

CallAudioState.java
//是否静音
public boolean isMuted() {return isMuted;
}//返回音频状态
public int getRoute() {return route;
}

2.InCallService
切换通话过程中的音频播放状态需要用到InCallService,InCallService是一个代理;
InCallService是一个抽象类,需要写一个子类来继承它,看下InCallService中几个比较重要的方法;

void onCallAudioStateChanged(CallAudioState audioState);//当音频播放状态发生了切换时,就会调用该函数,该方法经常用到
void onBringToForeground(boolean showDialpad);
void onCallAdded(Call call);
void onCallRemoved(Call call);
void onCanAddCallChanged(boolean canAddCall);

上面只是罗列了比较常用的方法,更多的方法剖析请去InCallService里面查看;

3.下面就来看下几种状态之间的切换
假设现在的通话模式是扬声器,想切换到听筒或者耳机,可以调用:mInCallService.setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE);
mInCallService就是InCallService;

CallAudioState.ROUTE_WIRED_OR_EARPIECE表示:如果此时是扬声器模式,当调用CallAudioState.ROUTE_WIRED_OR_EARPIECE参数时,系统会将音频输出设备切换成听筒或者是耳机,至于最后到底是听筒还是耳机播放,这个取决于它们的优先级,如果手机当中插入了耳机,耳机的优先级会比听筒要高;

假如此时现在的通话模式是耳机,想切换到扬声器,可以调用:mInCallService.setAudioRoute(CallAudioState.ROUTE_SPEAKER);

4.我们来分析下mInCallService.setAudioRoute(CallAudioState.ROUTE_SPEAKER)的流程:

InCallService.java
public final void setAudioRoute(int route) {if (mPhone != null) {mPhone.setAudioRoute(route);}
}

会看到一个mPhone,这个Phone是这么来的呢?

InCallService.java
private final Handler mHandler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {if (mPhone == null && msg.what != MSG_SET_IN_CALL_ADAPTER) {return;}switch (msg.what) {case MSG_SET_IN_CALL_ADAPTER:String callingPackage = getApplicationContext().getOpPackageName();mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage,getApplicationContext().getApplicationInfo().targetSdkVersion);mPhone.addListener(mPhoneListener);//调用onPhoneCreated()onPhoneCreated(mPhone);break;............}}
}

mPhone是在处理MSG_SET_IN_CALL_ADAPTER消息时被创建的,那MSG_SET_IN_CALL_ADAPTER消息又是谁传递的呢?

InCallService.java
private final class InCallServiceBinder extends IInCallService.Stub {@Overridepublic void setInCallAdapter(IInCallAdapter inCallAdapter) {mHandler.obtainMessage(MSG_SET_IN_CALL_ADAPTER, inCallAdapter).sendToTarget();}@Overridepublic void addCall(ParcelableCall call) {mHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget();}@Overridepublic void updateCall(ParcelableCall call) {mHandler.obtainMessage(MSG_UPDATE_CALL, call).sendToTarget();}............
}

由于我们在后面会用到inCallAdapter,所以分析下inCallAdapter的由来;
InCallServiceBinder很明显就是个服务代理,也就是绑定服务时传递进来的,绑定服务需要ServiceConnection,那这个ServiceConnection到底是谁呢?
答案在packages/services/Telecomm/src/com/android/server/telecom/InCallController.java中,它在绑定InCallService时,调用到了setInCallAdapter()方法,代码如下:

InCallController.java
Intent intent = new Intent(InCallService.SERVICE_INTERFACE);intent.setComponent(mInCallServiceInfo.getComponentName());if (call != null && !call.isIncoming() && !call.isExternalCall()){intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,call.getIntentExtras());intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,call.getTargetPhoneAccount());}Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);mIsConnected = true;if (!mContext.bindServiceAsUser(intent, mServiceConnection,Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE |Context.BIND_ABOVE_CLIENT,UserHandle.CURRENT)) {Log.w(this, "Failed to connect.");mIsConnected = false;}............
}private final ServiceConnection mServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.startSession("ICSBC.oSC");synchronized (mLock) {try {Log.d(this, "onServiceConnected: %s %b %b", name, mIsBound, mIsConnected);mIsBound = true;if (mIsConnected) {// Only proceed if we are supposed to be connected.onConnected(service);}} finally {Log.endSession();}}}............
};rotected void onConnected(IBinder service) {boolean shouldRemainConnected =InCallController.this.onConnected(mInCallServiceInfo, service);if (!shouldRemainConnected) {disconnect();}
}private boolean onConnected(InCallServiceInfo info, IBinder service) {Trace.beginSection("onConnected: " + info.getComponentName());Log.i(this, "onConnected to %s", info.getComponentName());IInCallService inCallService = IInCallService.Stub.asInterface(service);mInCallServices.put(info, inCallService);try {inCallService.setInCallAdapter(new InCallAdapter(mCallsManager,mCallIdMapper,mLock,info.getComponentName().getPackageName()));} catch (RemoteException e) {Log.e(this, e, "Failed to set the in-call adapter.");Trace.endSection();return false;}............
}

终于找到这个InCallAdapter了;
继续:

Phone.java
public final void setAudioRoute(int route) {mInCallAdapter.setAudioRoute(route);
}InCallAdapter.java
public void setAudioRoute(int route) {try {mAdapter.setAudioRoute(route);} catch (RemoteException e) {}
}

这个mAdapter就是InCallController中的InCallAdapter,进入到了packages/services/Telecomm/src/com/android/server/telecom/InCallAdapter.java中;

InCallAdapter.java
public void setAudioRoute(int route) {try {Log.startSession(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, mOwnerComponentName);long token = Binder.clearCallingIdentity();try {synchronized (mLock) {mCallsManager.setAudioRoute(route);}} finally {Binder.restoreCallingIdentity(token);}} finally {Log.endSession();}
}

mCallsManager是CallsManager,进入到了CallsManager.setAudioRoute();

CallsManager.java
void setAudioRoute(int route) {Call call = getDialingCall();if (call != null && call.getStartWithSpeakerphoneOn()) {/* There is a change in audio routing preferance for the call.* So, honour the new audio routing preferance.*/call.setStartWithSpeakerphoneOn(false);}mCallAudioManager.setAudioRoute(route);
}

又进入到了CallAudioManager;

CallAudioManager.java
void setAudioRoute(int route) {Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));switch (route) {case CallAudioState.ROUTE_BLUETOOTH:mCallAudioRouteStateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH);return;case CallAudioState.ROUTE_SPEAKER:mCallAudioRouteStateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_SPEAKER);return;case CallAudioState.ROUTE_WIRED_HEADSET:mCallAudioRouteStateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_HEADSET);return;case CallAudioState.ROUTE_EARPIECE:mCallAudioRouteStateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);return;case CallAudioState.ROUTE_WIRED_OR_EARPIECE:mCallAudioRouteStateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE);return;default:Log.wtf(this, "Invalid route specified: %d", route);}
}

终于走到了根据地了,还记得我们的参数是什么吗,CallAudioState.ROUTE_SPEAKER,也就是调用mCallAudioRouteStateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_SPEAKER);

到了这里,暂停一会,分析下CallAudioManager和mCallAudioRouteStateMachine;

5.CallAudioManager,CallAudioRouteStateMachine,CallAudioModeStateMachine

CallAudioManager可以类比于AudioManager,负责通话过程中音频播放状态的逻辑;

CallAudioManager是在CallsManager被初始化的;

CallsManager.java
CallsManager(......){mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,this,new CallAudioModeStateMachine((AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE)),playerFactory, mRinger, new RingbackPlayer(playerFactory), mDtmfLocalTonePlayer);
}

CallAudioModeStateMachine是什么,它是处理通话过程中的音频模式的,如语音模式,通话模式等,看代码:

CallAudioModeStateMachine.java
//音频状态
private final BaseState mUnfocusedState = new UnfocusedState();//待机
private final BaseState mRingingFocusState = new RingingFocusState();//响铃
private final BaseState mSimCallFocusState = new SimCallFocusState();//Call
private final BaseState mVoipCallFocusState = new VoipCallFocusState();//COMMUNICATION
private final BaseState mOtherFocusState = new OtherFocusState();private final AudioManager mAudioManager;
private CallAudioManager mCallAudioManager;private int mMostRecentMode;
private boolean mIsInitialized = false;public CallAudioModeStateMachine(AudioManager audioManager) {super(CallAudioModeStateMachine.class.getSimpleName());mAudioManager = audioManager;mMostRecentMode = AudioManager.MODE_NORMAL;//将这些状态添加到mStateInfo中addState(mUnfocusedState);addState(mRingingFocusState);addState(mSimCallFocusState);addState(mVoipCallFocusState);addState(mOtherFocusState);//待机状态是默认状态setInitialState(mUnfocusedState);//完成CallAudioModeStateMachine的构造start();sendMessage(INITIALIZE, new MessageArgs());
}
............

CallAudioModeStateMachine继承自StateMachine,addState方法在StateMachine中:

StateMachine.java
public final void addState(State state) {mSmHandler.addState(state, null);
}private final StateInfo addState(State state, State parent) {StateInfo parentStateInfo = null;if (parent != null) {//不成立parentStateInfo = mStateInfo.get(parent);if (parentStateInfo == null) {// Recursively add our parent as it's not been added yet.parentStateInfo = addState(parent, null);}}StateInfo stateInfo = mStateInfo.get(state);if (stateInfo == null) {//创建StateInfostateInfo = new StateInfo();//以State为key,保存stateInfomStateInfo.put(state, stateInfo);}// Validate that we aren't adding the same state in two different hierarchies.if ((stateInfo.parentStateInfo != null)&& (stateInfo.parentStateInfo != parentStateInfo)) {throw new RuntimeException("state already added");}//State与stateInfo进行绑定stateInfo.state = state;stateInfo.parentStateInfo = parentStateInfo;//为nullstateInfo.active = false;//不活跃if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);return stateInfo;
}

addState()方法就是将通话过程中所有的音频状态State添加到CallAudioModeStateMachine的mStateInfo容器中;我们来看下CallAudioModeStateMachine构造函数中的start()方法:

public void start() {// mSmHandler can be null if the state machine has quit.SmHandler smh = mSmHandler;if (smh == null) return;/** Send the complete construction message */smh.completeConstruction();
}private final void completeConstruction() {int maxDepth = 0;//由于i.parentStateInfo为null.所以maxDepth = depth = 1for (StateInfo si : mStateInfo.values()) {int depth = 0;for (StateInfo i = si; i != null; depth++) {i = i.parentStateInfo;}if (maxDepth < depth) {maxDepth = depth;}}//长度为1mStateStack = new StateInfo[maxDepth];mTempStateStack = new StateInfo[maxDepth];//初始化mStateStacksetupInitialStateStack();/** Sending SM_INIT_CMD message to invoke enter methods asynchronously *///构造完成后,发送SM_INIT_CMD消息sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));}private final void setupInitialStateStack() {//mInitialState是mUnfocusedStateStateInfo curStateInfo = mStateInfo.get(mInitialState);//由于curStateInfo.parentStateInfo==null.所以在for循环结束后,//mTempStateStack只有一个mUnfocusedState所对应的StateInfo,并且mTempStateStackCount为1for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {mTempStateStack[mTempStateStackCount] = curStateInfo;curStateInfo = curStateInfo.parentStateInfo;}// Empty the StateStackmStateStackTopIndex = -1;//数据从mTempStateStack移动到mStateStack中moveTempStateStackToStateStack();
}private final int moveTempStateStackToStateStack() {//mStateStackTopIndex = -1,所以startingIndex = 0int startingIndex = mStateStackTopIndex + 1;//mTempStateStackCount = 1,所以i=0int i = mTempStateStackCount - 1;int j = startingIndex;//j = 0while (i >= 0) {if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j);mStateStack[j] = mTempStateStack[i];//mStateStack[0] = mTempStateStack[0]后跳出while循环j += 1;i -= 1;}//跳出循环后,j = 1,i = -1mStateStackTopIndex = j - 1;//mStateStackTopIndex又回到了0return startingIndex;
}

搞一个mTempStateStack,是为了层层解析StateInfo中的parentStateInfo,我们没用到parentStateInfo;
元素在mStateStack和mTempStateStack中的顺序是相反的,新添加进来的音频状态被放在mStateStack的最前面,mStateStack就是栈的数据结构;继续来看completeConstruction()中的sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj)),当构造完成之后,会发送SM_INIT_CMD消息,该消息在SmHandler中被处理:

public final void handleMessage(Message msg) {if (!mHasQuit) {if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {mSm.onPreHandleMessage(msg);}if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);/** Save the current message */mMsg = msg;/** State that processed the message */State msgProcessedState = null;if (mIsConstructionCompleted) {msgProcessedState = processMsg(msg);} else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)&& (mMsg.obj == mSmHandlerObj)) {//成立//mIsConstructionCompleted = true表示构造已经完成mIsConstructionCompleted = true;//调用相应音频播放模式的enter()方法invokeEnterMethods(0);} else {throw new RuntimeException("StateMachine.handleMessage: "+ "The start method not called, received msg: " + msg);}//执行音频播放模式的切换,比如从Normal进入到Call//此时的msgProcessedState为nullperformTransitions(msgProcessedState, msg);// We need to check if mSm == null here as we could be quitting.if (mDbg && mSm != null) mSm.log("handleMessage: X");if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {mSm.onPostHandleMessage(msg);}}
}

先看invokeEnterMethods(0)方法:

private final void invokeEnterMethods(int stateStackEnteringIndex) {//stateStackEnteringIndex = 0 ,mStateStackTopIndex = 0 ;for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {if (stateStackEnteringIndex == mStateStackTopIndex) {// Last enter state for transitionmTransitionInProgress = false;}if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());//i = 0,所以mStateStack[i].state是mUnfocusedState,调用mUnfocusedState.enter()mStateStack[i].state.enter();//mUnfocusedState进入活跃状态,也就是表示此时的音频播放模式是待机模式mStateStack[i].active = true;}mTransitionInProgress = false; // ensure flag set to false if no methods called
}

调用mUnfocusedState.enter(),看下代码:

private class UnfocusedState extends BaseState {@Overridepublic void enter() {if (mIsInitialized) {Log.i(LOG_TAG, "Abandoning audio focus: now UNFOCUSED");//放弃通话过程中的音频焦点,通话过程中的音频播放也是要获取焦点的,在呼叫或铃声结束,或者呼叫被拒绝或未应答时需要释放焦点,//这和播放音乐时的获取焦点类似mAudioManager.abandonAudioFocusForCall();//设置当前的音频播放模式为待机模式,这就与UnfocusedState对应了mAudioManager.setMode(AudioManager.MODE_NORMAL);//用mMostRecentMode保存最新的音频播放模式mMostRecentMode = AudioManager.MODE_NORMAL;//将音频焦点类型传递给CallAudioRouteStateMachinemCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);}}............
}

继续来看:performTransitions(msgProcessedState, msg)执行音频播放模式的切换操作,

private void performTransitions(State msgProcessedState, Message msg) {/*** If transitionTo has been called, exit and then enter* the appropriate states. We loop on this to allow* enter and exit methods to use transitionTo.*/State orgState = mStateStack[mStateStackTopIndex].state;............State destState = mDestState;if (destState != null) {/*** Process the transitions including transitions in the enter/exit methods*/while (true) {if (mDbg) mSm.log("handleMessage: new destination call exit/enter");/*** Determine the states to exit and enter and return the* common ancestor state of the enter/exit states. Then* invoke the exit methods then the enter methods.*/StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);// flag is cleared in invokeEnterMethods before entering the target statemTransitionInProgress = true;//退出旧的音频状态,调用State.exit()invokeExitMethods(commonStateInfo);int stateStackEnteringIndex = moveTempStateStackToStateStack();//进入新的音频状态,调用State.enter()invokeEnterMethods(stateStackEnteringIndex);/*** Since we have transitioned to a new state we need to have* any deferred messages moved to the front of the message queue* so they will be processed before any other messages in the* message queue.*/moveDeferredMessageAtFrontOfQueue();if (destState != mDestState) {// A new mDestState so continue loopingdestState = mDestState;} else {// No change in mDestState so we're donebreak;}}mDestState = null;}............
}

分析了这么多,来总结下CallAudioModeStateMachine,简单点说CallAudioModeStateMachine封装了4种通话过程中的音频播放模式,以及它们之间的切换操作;

6.来走一遍UnfocusedState切换到的流程

由于CallAudioRouteStateMachine和CallAudioModeStateMachine类似,所以CallAudioRouteStateMachine就不再分析了;
假设当前是待机模式,要从待机模式切换到响铃模式;从CallAudioManager.onCallAdded()开始:

CallAudioManager.java
public void onCallAdded(Call call) {if (shouldIgnoreCallForAudio(call)) {return; // Don't do audio handling for calls in a conference, or external calls.}addCall(call);
}private void addCall(Call call) {if (mCalls.contains(call)) {Log.w(LOG_TAG, "Call TC@%s is being added twice.", call.getId());return; // No guarantees that the same call won't get added twice.}Log.d(LOG_TAG, "Call added with id TC@%s in state %s", call.getId(),CallState.toString(call.getState()));if (mCallStateToCalls.get(call.getState()) != null) {mCallStateToCalls.get(call.getState()).add(call);}updateForegroundCall();mCalls.add(call);onCallEnteringState(call, call.getState());
}private void onCallEnteringState(Call call, int state) {switch (state) {case CallState.ACTIVE:case CallState.CONNECTING:onCallEnteringActiveDialingOrConnecting();break;case CallState.RINGING://走这个onCallEnteringRinging();break;case CallState.ON_HOLD:onCallEnteringHold();break;case CallState.PULLING:onCallEnteringActiveDialingOrConnecting();break;case CallState.DIALING:onCallEnteringActiveDialingOrConnecting();playRingbackForCall(call);break;}
}private void onCallEnteringRinging() {if (mRingingCalls.size() == 1) {mCallAudioModeStateMachine.sendMessageWithArgs(CallAudioModeStateMachine.NEW_RINGING_CALL,makeArgsForModeStateMachine());}
}

进入到CallAudioModeStateMachine:

public void sendMessageWithArgs(int messageCode, MessageArgs args) {//messageCode是CallAudioModeStateMachine.NEW_RINGING_CALLsendMessage(messageCode, args);
}public void sendMessage(int what, Object obj) {// mSmHandler can be null if the state machine has quit.SmHandler smh = mSmHandler;if (smh == null) return;smh.sendMessage(obtainMessage(what, obj));
}

进入到SmHandler:

public final void handleMessage(Message msg) {if (!mHasQuit) {if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {//准备工作mSm.onPreHandleMessage(msg);}/** Save the current message */mMsg = msg;/** State that processed the message */State msgProcessedState = null;if (mIsConstructionCompleted) {//成立/** Normal path *///处理msgmsgProcessedState = processMsg(msg);} else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)&& (mMsg.obj == mSmHandlerObj)) {mIsConstructionCompleted = true;invokeEnterMethods(0);} else {throw new RuntimeException("StateMachine.handleMessage: "+ "The start method not called, received msg: " + msg);}//执行音频播放模式的切换,比如从Normal进入到CallperformTransitions(msgProcessedState, msg);if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {//结束mSm.onPostHandleMessage(msg);}}
}

先看processMsg(msg):

private final State processMsg(Message msg) {//curStateInfo为UnfocusedState所对应的StateInfoStateInfo curStateInfo = mStateStack[mStateStackTopIndex];if (mDbg) {mSm.log("processMsg: " + curStateInfo.state.getName());}if (isQuit(msg)) {//不成立transitionTo(mQuittingState);} else {while (!curStateInfo.state.processMessage(msg)) {//UnfocusedState处理msg/*** Not processed*/curStateInfo = curStateInfo.parentStateInfo;if (curStateInfo == null) {/*** No parents left so it's not handled*/mSm.unhandledMessage(msg);break;}if (mDbg) {mSm.log("processMsg: " + curStateInfo.state.getName());}}}return (curStateInfo != null) ? curStateInfo.state : null;
}

看UnfocusedState.processMessage(),还记得msg是什么吗?CallAudioModeStateMachine.NEW_RINGING_CALL;

public boolean processMessage(Message msg) {if (super.processMessage(msg) == HANDLED) {return HANDLED;}MessageArgs args = (MessageArgs) msg.obj;switch (msg.what) {case NO_MORE_ACTIVE_OR_DIALING_CALLS:// Do nothing.return HANDLED;case NO_MORE_RINGING_CALLS:// Do nothing.return HANDLED;case NO_MORE_HOLDING_CALLS:// Do nothing.return HANDLED;case NEW_ACTIVE_OR_DIALING_CALL:transitionTo(args.foregroundCallIsVoip? mVoipCallFocusState : mSimCallFocusState);return HANDLED;case NEW_RINGING_CALL://切换到mRingingFocusStatetransitionTo(mRingingFocusState);return HANDLED;case NEW_HOLDING_CALL:// This really shouldn't happen, but transition to the focused state anyway.Log.w(LOG_TAG, "Call was surprisingly put into hold from an unknown state." +" Args are: \n" + args.toString());transitionTo(mOtherFocusState);return HANDLED;case TONE_STARTED_PLAYING:// This shouldn't happen either, but perform the action anyway.Log.w(LOG_TAG, "Tone started playing unexpectedly. Args are: \n"+ args.toString());return HANDLED;default:// The forced focus switch commands are handled by BaseState.return NOT_HANDLED;}
}public final void transitionTo(IState destState) {mSmHandler.transitionTo(destState);
}

要从UnfocusedState切换到RingingFocusState了,继续看SmHandler:

private final void transitionTo(IState destState) {if (mTransitionInProgress) {Log.wtf(mSm.mName, "transitionTo called while transition already in progress to " +mDestState + ", new target state=" + destState);}//使用mDestState保存要切换的目标State,所以mDestState就是mRingingFocusStatemDestState = (State) destState;if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName());
}

再次回到SmHandler的handleMessage()中的“msgProcessedState = processMsg(msg)”,processMsg(msg)已经执行结束了,并返回了UnfocusedState给msgProcessedState;
再看performTransitions(msgProcessedState, msg);

private void performTransitions(State msgProcessedState, Message msg) {//此时的msgProcessedState为UnfocusedState//orgState为UnfocusedStateState orgState = mStateStack[mStateStackTopIndex].state;boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != mSmHandlerObj);if (mLogRecords.logOnlyTransitions()) {/** Record only if there is a transition */if (mDestState != null) {mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState,orgState, mDestState);}} else if (recordLogMsg) {/** Record message */mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, orgState,mDestState);}//destState为RingingFocusStateState destState = mDestState;if (destState != null) {/*** Process the transitions including transitions in the enter/exit methods*/while (true) {if (mDbg) mSm.log("handleMessage: new destination call exit/enter");/*** Determine the states to exit and enter and return the* common ancestor state of the enter/exit states. Then* invoke the exit methods then the enter methods.*/StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);// flag is cleared in invokeEnterMethods before entering the target statemTransitionInProgress = true;invokeExitMethods(commonStateInfo);//返回的stateStackEnteringIndex为1,mStateStack中有了两个元素,下标为0的是UnfocusedState,下标为1的是RingingFocusState,//mStateStackTopIndex变成了1int stateStackEnteringIndex = moveTempStateStackToStateStack();//进入新的音频状态,调用RingingFocusState.enter()invokeEnterMethods(stateStackEnteringIndex);moveDeferredMessageAtFrontOfQueue();if (destState != mDestState) {// A new mDestState so continue loopingdestState = mDestState;} else {// No change in mDestState so we're donebreak;}}mDestState = null;}............}

终于进入RingingFocusState.enter()了:

private class RingingFocusState extends BaseState {@Overridepublic void enter() {Log.i(LOG_TAG, "Audio focus entering RINGING state");if (mCallAudioManager.startRinging()) {//请求焦点mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);//响铃模式mAudioManager.setMode(AudioManager.MODE_RINGTONE);//设置焦点mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.RINGING_FOCUS);} else {Log.i(LOG_TAG, "Entering RINGING but not acquiring focus -- silent ringtone");}mCallAudioManager.stopCallWaiting();}............
}

结束了,和播放音频类似,通话过程中的也是需要获取焦点,只是变成了mAudioManager.abandonAudioFocusForCall();
音频播放模式还是调用mAudioManager.setMode()方法;

不过这个代码设计很巧妙,它可以应用在所有的状态切换中,等会在编程设计模块里面会详细分析这种设计思想;

我们发现最终调用的还是 mAudioManager.setMode(),所以CallAudioManager做的只是一些封装操作而已;

7.上层的模式切换以及设备切换的api调用

在平时的日常开发中,我们在设置音频模式或者切换音频设备时总是报错,我们看下源码是怎么设置和切换的,可以借鉴一下:

①.设置音频模式:

(1)待机模式:
mAudioManager.abandonAudioFocusForCall();
mAudioManager.setMode(AudioManager.MODE_NORMAL);
mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);(2)响铃模式:
①进入响铃:
if (mCallAudioManager.startRinging()) {//请求焦点mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);//响铃模式mAudioManager.setMode(AudioManager.MODE_RINGTONE);//设置焦点mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.RINGING_FOCUS);
} else {Log.i(LOG_TAG, "Entering RINGING but not acquiring focus -- silent ringtone");
}mCallAudioManager.stopCallWaiting();②退出响铃:
mCallAudioManager.stopRinging();(3)通话模式:
mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mMostRecentMode = AudioManager.MODE_IN_CALL;
mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);(4)语音视频模式:
mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);

②切换输出设备

(1)听筒:
setSpeakerphoneOn(false);
setBluetoothOn(false);
CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,mAvailableRoutes);
//CallsManager会调用onCallAudioStateChanged(),通知那些对输出设备切换感兴趣的对象
setSystemAudioState(newState, true);其中:
private void setSpeakerphoneOn(boolean on) {mAudioManager.setSpeakerphoneOn(on);mStatusBarNotifier.notifySpeakerphone(on);
}private void setBluetoothOn(boolean on) {if (mBluetoothRouteManager.isBluetoothAvailable()) {if (on != mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()) {Log.i(this, "connecting bluetooth %s", on);if (on) {mBluetoothRouteManager.connectBluetoothAudio(null /*TODO: add real address*/);} else {mBluetoothRouteManager.disconnectBluetoothAudio();}}}
}(2)耳机:
setSpeakerphoneOn(false);
setBluetoothOn(false);(3)Active蓝牙:
mStatusBarNotifier.notifySpeakerphone(false);
setBluetoothOn(true);(4)Ringing蓝牙:
setSpeakerphoneOn(false);(5)Quiescent蓝牙:
setBluetoothOn(false);(6)Active扬声器:
setSpeakerphoneOn(true);
setBluetoothOn(false);

8.总结

分析代码发现,CallAudioManager通过CallAudioModeStateMachine和CallAudioRouteStateMachine这两个类,保存了当前的通话音频状态以及通话音频的输出设备,当音频状态和输出设备发生变化的时候,CallAudioManager会保存新的状态,并且通知外界发生了改变,音频状态和输出设备的切换,底层原理还会调用mAudioManager.setMode()和mAudioManager.setSpeakerphoneOn(on)等AudioService的方法;

关于AudioService.setMode()和AudioSerice.setSpeakerphoneOn(on)等的内容,前面的博客已经做了剖析

android音频系统(7):通话过程中的音频输出设备切换相关推荐

  1. 通话过程中设置seekbar的最小值

    在通话过程中,按音量键会弹出一个SeekBar控制听筒音量,为了保证通话过程中听筒一直有声音,android默认设计按音量下键是不能调到最小的,但是手动拖动进度条却可以把拖动进度条到0,而听筒还会有声 ...

  2. 讯时网关通话过程中的功能键失灵,已解决

    环境: elastix 1.6 讯时MX60-16S口网关 症状: 在分机通话过程中,按asterisk的功能键失灵,比如转接键##,三方通话*11, 起初怀疑是讯时网关的功能键与asterisk的冲 ...

  3. java模拟一个军队作战,()系统在作战过程中帮助指挥和参谋人员实施各项作战业务,辅助指挥人员对部队和武器实施指挥控制...

    ()系统在作战过程中帮助指挥和参谋人员实施各项作战业务,辅助指挥人员对部队和武器实施指挥控制 答:指挥控制 社交化电子商务是基于人际关系网络,利用互联网社交工具,从事商品或服务销售的经营行为,是新型电 ...

  4. VOS通话过程中无声的处理办法

    vos通话过程中无声或者拨通提示,大概总结了一下,有几种可能情况,100%解决问题: 1.阿里云VOS,需设置DMZ\腾讯云VOS不需要设置DMZ 2.落地VOS,开启媒体转发 3.ims内外DHCP ...

  5. HFP A2DP,通话过程中打开蓝牙,A2DP连接失败

    操作步骤: 1.测试机事先与车载配对 2.向辅助机打电话 3.在通话过程中,打开蓝牙,让测试机自动连接车载. 问题原因: 在该特殊设备上,通话过程中连接bluetooth后会马上发出sco建立,建立C ...

  6. 通话过程中显示呼叫失败_电销行业如何破局?深海捷云呼叫中心协助企业通过挑战...

    呼叫中心在电销销售过程中发挥着重要的作用,呼叫中心不仅是承载电销的重要渠道,也是电销服务的重要平台.在监管力度不断加大的背景下,电销行业呼叫中心该如何破局? 现在部分电销行业进入调整期,增长压力越来越 ...

  7. android 4.0 电话录音,ANDROID音频系统散记之四:4.0音频系统HAL初探

    昨天(2011-11-15)发布了Android4.0的源码,今天download下来,开始挺进4.0时代.简单看了一下,发现音频系统方面与2.3的有较多地方不同,下面逐一描述. 一.代码模块位置 1 ...

  8. 通话过程中显示呼叫失败_iphone怎么总是打电话出现呼叫失败求大神解救

    2019-01-09 手机刷机失败怎么解救 导致酷派 8750刷机失败的原因:1.酷派 8750的Recovery版本不对有的机友用官方Recovery去刷第三方ROM,是无法刷入的.另外,较为老版本 ...

  9. ANDROID音频系统散记之四:4.0音频系统HAL初探

    昨天(2011-11-15)发布了Android4.0的源码,今天download下来,开始挺进4.0时代.简单看了一下,发现音频系统方面与2.3的有较多地方不同,下面逐一描述. 一.代码模块位置 1 ...

最新文章

  1. Java项目:在线美食网站系统(java+SSM+jsp+mysql+maven)
  2. 等值连接_干货:16种等值线图的解读与应用,用快速规律解题
  3. Visual Studio 2013开发 mini-filter driver step by step 内核代码段分页alloc_text的使用(9)
  4. MATLAB中常用的排列、组合、阶乘函数
  5. 非常好用的两个PHP函数 serialize()和unserialize()
  6. 【MM模块】 Goods Receipt 收货 1
  7. tracepro应用实例详解_离心铸造工艺(实例)
  8. [python 进阶] 9. 符合Python风格的对象
  9. vue完全编程方式与react在书写和运用上的异同
  10. 团队作业-第二周-测试计划
  11. 以下()不是python的数值运算操作符_python运算符与数据类型
  12. Java学习之InputStream中read()与read(byte[] b)
  13. (转)证券投资及财富管理市场创新趋势
  14. switch/case语句中,每个case后{}的意义
  15. 数字图像处理第三次试验:图像复原、图像分割
  16. 谷歌google chrome浏览器Chrome版本太旧无法更新chrome无法更新至最新版本怎么办
  17. java学习笔记----Mybatis-Plus
  18. 推挽电路介绍、设计与分析
  19. 合并两个有序数组(给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。)
  20. 《Intriguing properties of neural networks》代码实现——Pytorch

热门文章

  1. HOOFR: 增强型仿生特征提取器
  2. 彻底理解 Linux 的搜索工具: grep 和 awk
  3. M1 芯片版 Mac 抹掉重装系统教程
  4. 如何用Procmon.exe来监视SQLSERVER的logwrite大小
  5. 专门卖游戏辅助的平台_各大游戏售卖平台
  6. SSIS - Excel Destination无法接受大于255个字符长度的字符字段(转载)
  7. Bootstrap抽样和Monte Carlo思想
  8. 苹果cms首页文件html,苹果cms首页视频不更新怎么解决
  9. 金科科技学院计算机系统,金陵科技学院教务管理系统登录入口二本公办院校
  10. idea 推送代码报‘error: The following untracked working tree ……’