Telephony学习之拨号流程梳理 作者: LYC 时间: 2021-02-05 分类: Android # Telephony学习之拨号流程梳理 从拨打电话开始,依照流程分析系统是如何工作,一步步调用,最终将电话拨打出去的。 [TOC] ## 拨号界面 在DialtactsActivity.java文件中,这个文件所在的代码位置是/packages/apps/Dialer/java/com/android/dialer/app/,它将会显示拨号等界面。 拨号界面的具体显示在/packages/apps/Dialer/java/com/android/dialer/dialpadview/中的DialpadFragment.java文件中,它将会对点击拨号按钮进行响应。 自然地,跟进到onClick方法中。 ``` java if (resId == R.id.dialpad_floating_action_button) { view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); handleDialButtonPressed(); } ``` 然后跟进到当前类的方法handleDialButtonPressed()中。首先会进行一些条件的判断,例如输入文本是否为空,是否是已经被屏蔽的号码(不知道有什么用),如果判断都不满足,继续-> ``` java if(isDigitsEmpty()){} else { if (number != null && !TextUtils.isEmpty(prohibitedPhoneNumberRegexp) && number.matches(prohibitedPhoneNumberRegexp)) {} else { // 走这里 PreCall.start(getContext(), new CallIntentBuilder(number, CallInitiationType.Type.DIALPAD)); hideAndClearDialpad(); } } ``` 那先看new CallIntentBuilder(),这是一个匿名内部类。寻找它的位置和构造方法。很显然的,它就在隔壁/packages/apps/Dialer/java/com/android/dialer/callintent/中,这个文件有好几个构造方法,但是最终都会调用参数为Uri,CallSpecificAppData的构造方法(当然传入的String参数也会被转换成Uri) ``` java public CallIntentBuilder(@NonNull Uri uri, @NonNull CallSpecificAppData callSpecificAppData) { // 接着跟进 CallSpecificAppData.Builder builder = CallSpecificAppData.newBuilder(callSpecificAppData).method1().method2();//etc // 最后调用这个 this.callSpecificAppData = builder.build(); } ``` > 值得注意的是,它构建了一个CallSpecificAppData.Builder,但是我并不能找到CallSpecificAppData的位置和对应的构造方法。暂且不管,build通过建造者模式设置了一大堆参数,最后调用了build构建。但是从方法上看,它有可能build了两次? > 更值得一提的是,根据书中的说法,build调用的是本地的方法,但是真的吗? 稍微跟进build方法 ``` java // 因为它会构建Intent,并将其返回,这和书中说的一致 public Intent build() { // 构建Intent对象,添加Action、添加Extra附加信息等 } ``` 最终将这个匿名内部类作为参数传递了下去。于是我们接着返回看PreCall.start()方法。传入的参数为context和CallIntentBuilder。 ``` java @NonNull @MainThread Intent buildIntent(Context context, CallIntentBuilder builder); static Intent getIntent(Context context, CallIntentBuilder builder) { return PreCallComponent.get(context).getPreCall().buildIntent(context, builder); } static void start(Context context, CallIntentBuilder builder) { DialerUtils.startActivityWithErrorToast(context, getIntent(context, builder)); } ``` PreCall是一个接口,而start是它的一个静态方法。继续跟进DialerUtils.startActivityWithErrorToast()。 > 有一个小小的疑问是为什么要这么写呢——在一个接口里定义一个静态方法。 它根据getIntent获取Intent,中间细节暂不研究。 > PreCall.start()后还有一个方法hideAndClearDialpad(),看名称可能是隐藏拨号盘以及清空拨号盘数据之类的,不管。 > PS: VS Code可以直接跳转,赞美! 继续跟进DialerUtils.startActivityWithErrorToast()。这个类位置/packages/apps/Dialer/java/com/android/dialer/util/DialerUtils.java,同样的,它的startActivityWithErrorToast方法也有重载。 ``` java public static void startActivityWithErrorToast( final Context context, final Intent intent, int msgId) { // 跟进...... } ``` 首先根据Intent.Action进行判断,如果不是ACTION_CALL说明不是call,执行startActivity,不管。而另一条分支则继续执行拨号请求,它最终先调用placeCallOrMakeToast(context, intent),该方法接着调用TelecomUtil.placeCall方法。 ``` java private static void placeCallOrMakeToast(Context context, Intent intent) { final boolean hasCallPermission = TelecomUtil.placeCall(context, intent); // ... } ``` 查找TelecomUtil文件,同样就在隔壁,查找placeCall方法。 ``` java public abstract class TelecomUtil { public static boolean placeCall(Context context, Intent intent) { if (hasCallPhonePermission(context)) { getTelecomManager(context).placeCall(intent.getData(), intent.getExtras()); return true; } return false; } //接着看getTelecomManager方法 private static TelecomManager getTelecomManager(Context context) { return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); } //getSystemService的具体调用不细研究,最终通过AIDL跨进程调用Service端,其最终实现在/framework/base代码库中 } ``` ## 拨号处理 TelecomManager.java在/frameworks/base/telecomm/java/android/telecom/下,这时已经跳转到/framework/base中。 > 值得一提的是,TelecomManager.java程序是程序的静态关系,它会运行在启动它的进程中。 > 而且虽然它是单例模式,但是多个进程也会生成多个TelecomManager对象(不难理解) 它的placeCall方法如下: ``` java @RequiresPermission(anyOf = {android.Manifest.permission.CALL_PHONE, android.Manifest.permission.MANAGE_OWN_CALLS}) public void placeCall(Uri address, Bundle extras) { ITelecomService service = getTelecomService(); if (service != null) { if (address == null) { Log.w(TAG, "Cannot place call to empty address."); } try { service.placeCall(address, extras == null ? new Bundle() : extras, mContext.getOpPackageName(), mContext.getAttributionTag()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#placeCall", e); } } } //getTelecomService private ITelecomService getTelecomService() { if (mTelecomServiceOverride != null) { return mTelecomServiceOverride; } return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE)); } ``` ITelecomService接口位置:/frameworks/base/telecomm/java/com/android/internal/telecom/ITelecomService.aidl。这是一个aidl文件,意味着要跨进程调用了。 > aidl原理和使用方法,待学习 > bind原理和使用 而实现这个接口的位置在/packages/services/Telecomm/src/com/android/server/telecom/TelecomServiceImpl.java > 注意:又跳了回来,不过跳到了services。 > 并且根据其Manifest.xml文件的介绍,它将运行在系统进程空间之中(?) ### 小结 Dialer应用进程中,从DialpadFragment开始,进行拨号流程. ``` java onClick() ->handleDialButtonPressed() ->... ->PreCall.start() ->DialerUtils.startActivityWithErrorToast() ->placeCallOrMakeToast() ->TelecomUtil.placeCall() ->getTelecomManager() ->(TelecomManager) context.getSystemService() ->TelecomManager.placeCall() ->getTelecomService() ->ITelecomService.Stub.asInterface() ->ITelecomService ->跨进程调用 ->TelecomServiceImpl.placeCall ``` ### 继续 TelecomServiceImpl类定义了```private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub(){...}```,证明这是响应请求。 ``` java // placeCall public void placeCall(Uri handle, Bundle extras, String callingPackage, String callingFeatureId) { // follow it // 中间的方法调用、值的设定不完全明确 // ... synchronized (mLock) { // ... mUserCallIntentProcessorFactory.create(mContext, userHandle) .processIntent( intent, callingPackage, isSelfManaged || (hasCallAppOp && hasCallPermission), true /* isLocalInvocation */); } // ... } ``` placeCall传入的参数和之前调用它所传入的参数类型是一致的,侧面证明这正是它的调用位置。 同样来看processIntent的参数: intent根据hasCallPrivilegedPermission来判断是ACTION_CALL_PRIVILEGED还是ACTION_CALL。hasCallPrivilegedPermission从字面解释是通话特权权限,这和通话权限hasCallPermission有什么区别和联系? mUserCallIntentProcessorFactory是一个UserCallIntentProcessorFactory变量,从名称上看这是一个工厂。而UserCallIntentProcessorFactory是一个接口,并且还是一个工厂模式的工厂方法。 > 它调用create方法,而create方法在哪呢??? > 工厂模式学习 ``` java public interface UserCallIntentProcessorFactory { UserCallIntentProcessor create(Context context, UserHandle userHandle); } ``` 暂且不管上述问题,create将返回一个UserCallIntentProcessor对象,这说明processIntent在UserCallIntentProcessor中实现。 ``` java public void processIntent(Intent intent, String callingPackageName, boolean canCallNonEmergency, boolean isLocalInvocation) { // 执行processOutgoingCallIntent方法 processOutgoingCallIntent(intent, callingPackageName, canCallNonEmergency,isLocalInvocation); } // processOutgoingCallIntent方法 private void processOutgoingCallIntent(Intent intent, String callingPackageName, boolean canCallNonEmergency, boolean isLocalInvocation) { // 省略省略 sendIntentToDestination(intent, isLocalInvocation, callingPackageName); } // sendIntentToDestination方法 // 传入的isLocalInvocation确实是true private boolean sendIntentToDestination(Intent intent, boolean isLocalInvocation, String callingPackage) { // 因为是true所以走这里 synchronized (TelecomSystem.getInstance().getLock()) { TelecomSystem.getInstance().getCallIntentProcessor().processInten(intent, callingPackage); } // 那何时是false } ``` TelecomSystem.getInstance()返回的是一个INSTALL,这是一个TelecomSystem类型的变量,它的初始化位置是setInstance。而这个方法什么时候被调用就不知道了。所以实际调用的是TelecomSystem.getCallIntentProcessor()方法,返回mCallIntentProcessor。这是一个CallIntentProcessor,这个变量在类被构造时被初始化,但是同样不知道这个类在此处的构造是什么时候。所以最终的最终调用的是CallIntentProcessor的processIntent方法。 继续追踪。 ``` java public void processIntent(Intent intent, String callingPackage) { final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false); Trace.beginSection("processNewCallCallIntent"); if (isUnknownCall) { processUnknownCallIntent(mCallsManager, intent); } else { processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage, mDefaultDialerCache); } Trace.endSection(); } // isUnknownCall在正常拨号中自然不是false,因此调用processOutgoingCallIntent方法。 static void processOutgoingCallIntent( Context context, CallsManager callsManager, Intent intent, String callingPackage, DefaultDialerCache defaultDialerCache) { // ... // If the broadcaster comes back with an immediate error, disconnect and show a dialog. NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster. evaluateCall(); // 静态方法,很多if,参数都是hasXXX或者equals目测是用了判断各个参数是否正确的。 // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns CompletableFuture callFuture = callsManager .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser, intent, callingPackage); final Session logSubsession = Log.createSubsession(); callFuture.thenAccept((call) -> { if (call != null) { Log.continueSession(logSubsession, "CIP.sNOCI"); try { broadcaster.processCall(call, disposition); } finally { Log.endSession(); } } }); } ``` 可以很明确的看到前一个调用callsManager.startOutgoingCall(),后一个调用broadcaster.processCall(),先追踪后一个.```NewOutgoingCallIntentBroadcaster broadcaster```创建了broadcaster,这是一个NewOutgoingCallIntentBroadcaster变量,那么它的processCall方法调用也就在这个类里面了。 它的参数是call、disposition,call在之前一直都有,而disposition是一个CallDisposition类型的变量,它在processIntent方法中被初始化过。它的具体用法尚不清楚,但是根据注释,如果广播返回了一个即时错误,就会断连和显示对话框。 ``` java public void processCall(Call call, CallDisposition disposition) { if (disposition.callImmediately) { // ... placeOutgoingCallImmediately(mCall, disposition.callingAddress, null, speakerphoneOn, videoState); } if (disposition.requestRedirection) { // ... } if (disposition.sendBroadcast) { // ... } } ``` 从它的方法也可以进一步推断,它是用来部署某些措施的(我在说什么?)。看样子应该是调用placeOutgoingCallImmediately方法,立即拨打电话。这个方法中再调用mCall.setNewOutgoingCallIntentBroadcastIsDone()。 **最终调用mCallsManager.placeOutgoingCall(call, handle, gatewayInfo, speakerphoneOn, videoState)**。很明显,这是一个CallsManager变量中的方法。 ### 回顾 可以看到CallIntentProcessor的processIntent方法中,callFuture最终调用的是CallsManager中的startOutgoingCall方法。而callFuture.thenAccept中最终调用的同样是CallsManager中的placeOutgoingCall方法。推测前者负责拨号前的准备工作,后者将继续传递拨号请求。 ### CallsManager #### startOutgoingCall ``` java private CompletableFuture startOutgoingCall(List participants, PhoneAccountHandle requestedAccountHandle, Bundle extras, UserHandle initiatingUser, Intent originalIntent, String callingPackage, boolean isConference) { if (call == null) { call = new Call( // new一个Call,利用传入的Uri参数(Uri在Call对象增加连接服务时可能会发生变化,但是在大多数情况是保持不变的?) ); } } ``` 这个方法很长,不知道怎么看下去。但可以明确的是,它最终将会返回一个```CompletableFuture```类型的变量。虽然在方法中返回的位置很多,说明它根据很多参数来判断到底要返回哪个```CompletableFuture```。 > 值得注意的是,此处源码和书中有所区别,书中还会调用addCall,原理是有可能重复使用保存过的Call对象,所以需要检查,如果没有则需要增加一个对象。 ##### addCall 虽然这个方法在现在的源码中似乎不是很重要,但还是看一下。 这个方法会循环调用onCallAdded方法,回调通知增加了Call对象 ``` java call.addListener(this); mCalls.add(call); for (CallsManagerListener listener : mListeners) { listener.onCallAdded(call); } ``` listener是一个CallsManagerListener变量,CallsManagerListener是一个接口。call将CallsManager添加到Listener,mCalls集合将call添加进去,CallsManager对象通过mlistener发出onCallAdded消息回调。CallsManager的构造函数中又将mListeners中添加对象。 ``` java mListeners.add(mInCallWakeLockController); mListeners.add(statusBarNotifier); mListeners.add(mCallLogManager); mListeners.add(mPhoneStateBroadcaster); mListeners.add(mInCallController); mListeners.add(mCallAudioManager); mListeners.add(mCallRecordingTonePlayer); mListeners.add(missedCallNotifier); mListeners.add(mDisconnectedCallNotifier); mListeners.add(mHeadsetMediaButton); mListeners.add(mProximitySensorManager); mListeners.add(audioProcessingNotification); ``` 追踪mListeners.add(mInCallController),InCallController.onCallAdded消息回调,调用bindToService方法。调用mInCallServiceConnection.connect方法,InCallServiceConnection是内部公有类,调用connect。 > 剩下暂且略过 > 结论是startOutgoingCalls通过绑定服务和调用服务接口,启动和更新Dialer应用中的InCallActivity,展示出通话界面,但拨号请求并未发送到BP Modem处理。 #### placeOutgoingCall ``` java public void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn, int videoState) { // 判断,往call里塞东西(set),还会根据get的结果进行判断和设置一些暂时不明确用途的变量,eg: PhoneAccount account = mPhoneAccountRegistrar.getPhoneAccount(call.getTargetPhoneAccount(), call.getInitiatingUser()); // 最后调用 if (mPendingMOEmerCall == null) { // If the account has been set, proceed to place the outgoing call. // Otherwise the connection will be initiated when the account is // set by the user. // 上文注释说的很清楚,如果account被用户设置就会调用方法来初始化connection call.startCreateConnection(mPhoneAccountRegistrar); } } ``` ``` java void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) { // ... 初始化mCreateConnectionProcessor,参数为call,ConnectionServiceRepository,CreateConnectionResponse,PhoneAccountRegistrar,Context // 而传参中有两个this,后者CreateConnectionResponse是一个接口,而Call文件实现了CreateConnectionResponse,所以,为啥呢 mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this, phoneAccountRegistrar, mContext); mCreateConnectionProcessor.process(); } ``` 然后调用attemptNextPhoneAccount();追踪这个方法 ``` java private void attemptNextPhoneAccount() { // 中间涉及到很多方法调用和不知名变量,例如: CallAttemptRecord attempt = null; if (mAttemptRecordIterator.hasNext()) { attempt = mAttemptRecordIterator.next(); } if (mCallResponse != null && attempt != null) { mService = mRepository.getService(phoneAccount.getComponentName(), phoneAccount.getUserHandle()); // 这些都不管了,关键是 mService.createConnection(mCall, CreateConnectionProcessor.this); // mService.createConference(mCall, CreateConnectionProcessor.this);这是判断的另一个结果 } } ``` mService是一个ConnectionServiceWrapper类型的变量,mRepository是一个ConnectionServiceRepository类型的变量,它在CreateConnectionProcessor类的构造函数被调用时被初始化。他们借助于传入的参数构造mService对象。 追踪到ConnectionServiceWrapper.createConnection.核心代码 ``` java @VisibleForTesting public void createConnection(final Call call, final CreateConnectionResponse response) { BindCallback callback = new BindCallback() { @Override public void onSuccess() { // 主要还是要看成功的结果 ConnectionRequest connectionRequest = new ConnectionRequest.Builder() .setAccountHandle(call.getTargetPhoneAccount()) .setAddress(call.getHandle()) .setExtras(extras) .setVideoState(call.getVideoState()) .setTelecomCallId(callId) // For self-managed incoming calls, if there is another ongoing call Telecom // is responsible for showing a UI to ask the user if they'd like to answer // this new incoming call. .setShouldShowIncomingCallUi( !mCallsManager.shouldShowSystemIncomingCallUi(call)) .setRttPipeFromInCall(call.getInCallToCsRttPipeForCs()) .setRttPipeToInCall(call.getCsToInCallRttPipeForCs()) .build(); if (mServiceInterface != null) { try { mServiceInterface.createConnection( call.getConnectionManagerPhoneAccount(), callId, connectionRequest, call.shouldAttachToExistingConnection(), call.isUnknown(), Log.getExternalSession(TELECOM_ABBREVIATION)); } } } @Override public void onFailure() { } } mBinder.bind(callback, call); } ``` mServiceInterface是一个IConnectionService接口(懂得都懂),这又是一个bind机制aidl跨进程调用。它在framework代码库中。mServiceInterface是IConnectionService 对象,通过aidl 机制 binder调用createConnection。服务端在Framework ConnectionService.java中:mBinder。 > **先看mBinder.bind()**。它在ServiceBinder中。 ``` java final class Binder2 { void bind(BindCallback callback, Call call) { mCallbacks.add(callback); if (mServiceConnection == null) { Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName); ServiceConnection connection = new ServiceBinderConnection(call); isBound = mContext.bindServiceAsUser(serviceIntent, connection, bindingFlags, mUserHandle); } } } ``` > ServiceBinder是ConnectionServiceWrapper的父类,Binder是ServiceBinder的内部类。在ConnectionServiceWrapper被创建时,ServiceBinder和mBinder也被同步创建了。mServiceAction指定了将要绑定的服务,它在子类调用构造函数时调用了super.时被构造,传入的值是ConnectionService.SERVICE_INTERFACE。 ConnectionService在framework/base/telecomm/java/android/telecom/ConnectionService.java中。*这和前面的跨进程调用又一致了*。总之,这是第二次跨进程服务访问,绑定的服务对象在/packages/services/Telephony中。先分析绑定成功之后的处理逻辑,还在ServiceBinder.java中 ``` java private final class ServiceBinderConnection implements ServiceConnection { // 这是一个内部类 @Override public void onServiceConnected(ComponentName componentName, IBinder binder) { setBinder(binder); handleSuccessfulConnection(); } @Override public void onServiceDisconnected(ComponentName componentName) { handleDisconnect(); } } // setBinder方法 private void setBinder(IBinder binder) { // 通过调用方法保存binder对象 // 而且setServiceInterface是个抽象方法,最终在子类实现,ServiceBinder的子类是ConnectionServiceWrapper setServiceInterface(binder); } ``` > 父类设计好方法,并将实现方法设置为抽象方法,而子类实现该方法。这种设计思想的优势在哪里? ``` java private IConnectionService mServiceInterface; @Override protected void setServiceInterface(IBinder binder) { addConnectionServiceAdapter(mAdapter); } private void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) { mServiceInterface.addConnectionServiceAdapter(adapter, Log.getExternalSession()); } ``` adapter是IConnectionServiceAdapter类型的变量,说明在绑定成功后会进行一次跨进程调用。而IConnectionServiceAdapter接口定义的文件位置/frameworks/base/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl。判断当前绑定的服务将通过Adapter接口调用去更新Telecom中的通话状态。 继续查看handleSuccessfulConnection()的逻辑。 ``` java private void handleSuccessfulConnection() { for (BindCallback callback : callbacksCopy) { callback.onSuccess(); } } public abstract class ServiceBinder { interface BindCallback { void onSuccess(); void onFailure(); } } ``` 可以看到连接成功后先调用setBinder(),后调用handleSuccessfulConnection(),在这个方法中调用了 callback.onSuccess(),而它的实现方法又在子类ConnectionServiceWrapper中,所以又回到了onSuccess()方法中。"一种循环" 这段代码上文已经贴过,再贴一次。 ``` java mServiceInterface.createConnection( call.getConnectionManagerPhoneAccount(), callId, connectionRequest, call.shouldAttachToExistingConnection(), call.isUnknown(), Log.getExternalSession(TELECOM_ABBREVIATION)); ``` 这就是第二次跨进程服务访问。**继续追踪ConnectionService**,位置/frameworks/base/telecomm/java/android/telecom/ConnectionService.java。这个文件将会是service.telephony.TelephonyConnectionService的父类,也就是它实现的根本。 ``` java public abstract class ConnectionService extends Service { // 按照之前看到的逻辑,应该是 private final IBinder mBinder = new IConnectionService.Stub() { // 先调add后调create,但都是差不多的 @Override public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter, Session.Info sessionInfo) { // ... // handle机制,开始将服务调用的同步方式转化为异步处理方式。 mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, args).sendToTarget(); } @Override public void createConnection(){ mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget(); } } @Override public final IBinder onBind(Intent intent) { return mBinder; } // 注意区别两个方法的位置 private void createConnection( final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming, boolean isUnknown) { // ... // 这里创建M0 通话Connection // 主要在services/Telephony/TelephonyConnectionService.java实现,也就是子类 Connection connection = null; connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request) : isIncoming ? onCreateIncomingConnection(callManagerAccount, request) : onCreateOutgoingConnection(callManagerAccount, request); // ...跨进程调用Telecom服务接口,通过Connection已经成功创建 mAdapter.handleCreateConnectionComplete( callId, request, new ParcelableConnection( request.getAccountHandle(), connection.getState(), // 省略conncetion的其他信息... ) ); } // handle机制 private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_ADD_CONNECTION_SERVICE_ADAPTER: { IConnectionServiceAdapter adapter = (IConnectionServiceAdapter) args.arg1; mAdapter.addAdapter(adapter);//再嵌套一层Adapter onAdapterAttached();// 验证Apater是否可用 // ... } case MSG_CREATE_CONNECTION: { // 省略其他,调用这个方法,但是这个方法不是mBinder中的方法,而是ConnectionService中的方法。 createConnection(); } // ... } } } } ``` #### 小结一下 从ConnectionServiceWrapper的createConnection开始,先执行mBinder.bind()绑定服务,bind的实现位置在父类ServiceBinder中,这其中核心代码是调用isBound = mContext.bindService().绑定服务成功之后执行ServiceBinder中的内部类ServiceBinderConnection的方法onServiceConnected。在这其中setBinder()后handleSuccessfulConnection().前者调用抽象方法setServiceInterface(),该方法在子类中实现,调用addConnectionServiceAdapter,跨进程调用mServiceInterface.addConnectionServiceAdapter().后者调用callback.onSuccess(),回调执行onSuccess().然后就是上文建造者调用build和跨进程mServiceInterface.createConnection()。 #### onCreateOutgoingConnection 该方法在ConnectionService中被定义,并return null,而子类TelephonyConnecitonService复写了该方法。 ``` java @Override public Connection onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request) { if (isEmergencyNumber) { // 先对紧急呼叫进行判断处理 } // Get the right phone object from the account data passed in. final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber, /* Note: when not an emergency, handle can be null for unknown callers */ handle == null ? null : handle.getSchemeSpecificPart()); // 如果获取失败则不能打电话 if() { // ... } else { final Connection resultConnection = getTelephonyConnection(request, numberToDial, true, handle, phone); mDdsSwitchHandler.post(new Runnable() { @Override public void run() { boolean result = delayDialForDdsSwitch(phone); Log.i(this, "onCreateOutgoingConn - delayDialForDdsSwitch result = " + result); placeOutgoingConnection(request, resultConnection, phone); } }); return resultConnection; } } private Connection placeOutgoingConnection( TelephonyConnection connection, Phone phone, int videoState, Bundle extras) { com.android.internal.telephony.Connection originalConnection = null; //调用Phone Dial命令,下发拨号流程 // 执行phone.dial originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder() .setVideoState(videoState) .setIntentExtras(extras) .setRttTextStream(connection.getRttTextStream()) .build()); connection.setOriginalConnection(originalConnection); } ``` 然后跟踪phone.dial方法的调用,发现phone是com.android.internal.telephony.GsmCdmaPhone类型对象。而这东西在/frameworks/opt/telephony/src/java/com/android/internal/telephony/中。 > 首先怎么发现的phone是GsmCdmaPhone类型对象,也许可以根据import的内容进行判断,但是phone的声明也是Phone而已,这是向下转型吗,为什么要进行向下转型呢?其实opt代码库已经是本文涉及到的第三个代码库了,它的作用又是什么呢? ### GsmCdmaPhone ``` java // 不觉明历 @Override public Connection dial(String dialString, @NonNull DialArgs dialArgs) throws CallStateException { Phone.checkWfcWifiOnlyModeBeforeDial(mImsPhone, mPhoneId, mContext); if ((useImsForCall && (!isMmiCode || isPotentialUssdCode)) || (isMmiCode && useImsForUt) || useImsForEmergency) { try { if (DBG) logd("Trying IMS PS call"); return imsPhone.dial(dialString, dialArgs); } catch (CallStateException e) { if (DBG) logd("IMS PS call exception " + e + "useImsForCall =" + useImsForCall + ", imsPhone =" + imsPhone); // Do not throw a CallStateException and instead fall back to Circuit switch // for emergency calls and MMI codes. if (Phone.CS_FALLBACK.equals(e.getMessage()) || isEmergency) { logi("IMS call failed with Exception: " + e.getMessage() + ". Falling back " + "to CS."); } else { CallStateException ce = new CallStateException(e.getError(), e.getMessage()); ce.setStackTrace(e.getStackTrace()); throw ce; } } } if (mSST != null && mSST.mSS.getState() == ServiceState.STATE_OUT_OF_SERVICE && mSST.mSS.getDataRegistrationState() != ServiceState.STATE_IN_SERVICE && !isEmergency) { throw new CallStateException("cannot dial in current state"); } // Check non-emergency voice CS call - shouldn't dial when POWER_OFF if (mSST != null && mSST.mSS.getState() == ServiceState.STATE_POWER_OFF /* CS POWER_OFF */ && !VideoProfile.isVideo(dialArgs.videoState) /* voice call */ && !isEmergency /* non-emergency call */ && !(isMmiCode && useImsForUt) /* not UT */ /* If config_allow_ussd_over_ims is false, USSD is sent over the CS pipe instead */ && !isPotentialUssdCode) { throw new CallStateException( CallStateException.ERROR_POWER_OFF, "cannot dial voice call in airplane mode"); } // Check for service before placing non emergency CS voice call. // Allow dial only if either CS is camped on any RAT (or) PS is in LTE/NR service. if (mSST != null && mSST.mSS.getState() == ServiceState.STATE_OUT_OF_SERVICE /* CS out of service */ && !(mSST.mSS.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE && ServiceState.isPsOnlyTech( mSST.mSS.getRilDataRadioTechnology())) /* PS not in LTE/NR */ && !VideoProfile.isVideo(dialArgs.videoState) /* voice call */ && !isEmergency /* non-emergency call */ /* If config_allow_ussd_over_ims is false, USSD is sent over the CS pipe instead */ && !isPotentialUssdCode) { throw new CallStateException( CallStateException.ERROR_OUT_OF_SERVICE, "cannot dial voice call in out of service"); } if (DBG) logd("Trying (non-IMS) CS call"); if (isDialedNumberSwapped && isEmergency) { // Triggers ECM when CS call ends only for test emergency calls using // ril.test.emergencynumber. mIsTestingEmergencyCallbackMode = true; mCi.testingEmergencyCall(); } if (isPhoneTypeGsm()) { return dialInternal(dialString, new DialArgs.Builder<>() .setIntentExtras(dialArgs.intentExtras) .build()); } else { return dialInternal(dialString, dialArgs); } } @UnsupportedAppUsage public GsmCdmaCallTracker mCT; protected Connection dialInternal(String dialString, DialArgs dialArgs, ResultReceiver wrappedCallback) throws CallStateException { // ... return mCT.dial(newDialString, dialArgs.intentExtras); } ``` 追踪GsmCdmaCallTracker.dial. ``` java public Connection dial(String dialString, Bundle intentExtras) throws CallStateException { return dialGsm(dialString, CommandsInterface.CLIR_DEFAULT, intentExtras); } // 注意同步锁 public synchronized Connection dialGsm(String dialString, int clirMode, UUSInfo uusInfo, Bundle intentExtras) throws CallStateException { mCi.dial(mPendingMO.getAddress(), mPendingMO.isEmergencyCall(), mPendingMO.getEmergencyNumberInfo(), mPendingMO.hasKnownUserIntentEmergency(), clirMode, uusInfo, obtainCompleteMessage()); // 更新通话状态,并发出通知 updatePhoneState(); mPhone.notifyPreciseCallStateChanged(); return mPendingMO; } ``` mCi是RIL对象,是CommandsInterface对象, RIL.java 继承实现,其代码在/frameworks/opt/telephony/src/java/com/android/internal/telephony/ ``` java //基本log可以通过check "> DIAL",来查看拨号是否成功往下发到modem // case RIL_REQUEST_DIAL: // return "DIAL"; // 最终通过此函数,调用RIL 接口下发拨号命令 @Override public void dial(String address, boolean isEmergencyCall, EmergencyNumber emergencyNumberInfo, boolean hasKnownUserIntentEmergency, int clirMode, UUSInfo uusInfo, Message result) { // ... } ``` 至此,跟踪拨号流程已经到了HAL层,而不同的芯片厂家对它将有不同的实现。 同时本文也全部结束 以上 标签: Telephony
Happy New Year