前言:由于通话比较特殊,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()) {
@Override
public 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 {
@Override
public void setInCallAdapter(IInCallAdapter inCallAdapter) {
mHandler.obtainMessage(MSG_SET_IN_CALL_ADAPTER, inCallAdapter).sendToTarget();
}
@Override
public void addCall(ParcelableCall call) {
mHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget();
}
@Override
public 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() {
@Override
public 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;
}
............
}
终于找到这个