Telephone application framework
Android phone module is a typical hierarchical structure design, as follows:
The telephone framework is divided into four levels: application layer, framework layer (fw), RIL (Radio Interface Layer) and modem.
Application layer: app application, including dialer apk,TeleService.apk,Telecom.apk,InCallUI.apk.
Where dialer Apk runs on COM android. In the dialer process, teleservice Apk runs in the resident process com android. In the phone process, telecom Apk runs in the system process and incallui Apk runs on COM android. Incallui process.
Framework layer: including telephone FW and telecom fw. Code s are located in frameworks / opt / telephone and frameworks / base / Telecom respectively.
RIL: HAL layer in the User Libraries layer, which provides the communication function between AP (Application Processor) and BP (Baseband Processor). RIL is usually divided into RILJ and RILC. RILJ is the RIL of java Java and code are located in the framework layer, and RILC is the real RIL layer. The RIL driver module of Android is divided into rild and libril in the hardware/ril directory So and libreference_ ril. So has three parts.
Modem: located in BP, responsible for actual wireless communication capability processing
aidl for realizing the interaction between processes:
Dialing process framework process
-
packages/apps/Dialer/java/com/android/dialer/dialpadview/DialpadFragment.java
The user clicks the dial button on the dial and starts calling the first step of the long march. The onclick method of dialpadfragment will respond to the click event.
@Override public void onClick(View view) { int resId = view.getId(); if (resId == R.id.dialpad_floating_action_button) { view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); handleDialButtonPressed(); }
Find the layout file of the L layout interface corresponding to the dialing interface dialer/dialpadview/res/layout/dialpad_fragment.xm l. It contains the floating button to open the dial, which is defined as follows
Run in this class: handleDialButtonPressed(); Method to determine whether the number is empty and illegal
There was no such step before 10. Call dialerutils directly Startactivitywitherrortoast() method
Carry Intent Action_ The call's Intent Action will go to the TelecomUtil placeCall process, otherwise it will go directly to the context startActivity(intent);
public static void startActivityWithErrorToast( final Context context, final Intent intent, int msgId) { try { if ((Intent.ACTION_CALL.equals(intent.getAction()))) { placeCallOrMakeToast(context, intent); } else { context.startActivity(intent);
The startActivityWithErrorToast() method calls the following method
hasCallPhonePermission() will check whether there is call permission and manifest permission. CALL_ Phone) permission:
private static void placeCallOrMakeToast(Context context, Intent intent) { final boolean hasCallPermission = TelecomUtil.placeCall(context, intent); if (!hasCallPermission) { Toast.makeText(context, "Cannot place call without Phone permission", Toast.LENGTH_SHORT) .show(); } }
public static boolean placeCall(Context context, Intent intent) { if (hasCallPhonePermission(context)) { getTelecomManager(context).placeCall(intent.getData(), intent.getExtras()); return true; } return false; }
TelecomManager.placeCall mainly obtains the Binder interface of TelecomService and enters into the interior of TelecomService (system process) across processes. Com android. The work of the dialer process is temporarily completed
private static TelecomManager getTelecomManager(Context context) { return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); }
Telecom Manager obtains the ITelecomService service and calls its placeCall method to continue to deliver intent and send a call request, which will involve the first cross process service call.
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()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#placeCall", e); } } }
Where, getTelecomService() method
private ITelecomService getTelecomService() { if (mTelecomServiceOverride != null) { return mTelecomServiceOverride; } return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE)); }
Call the placeCall method of ITelecomService. Here is the aidl call. We need to find the place to receive. According to the naming rules, it should be TelecomServiceImpl:
Create UserCallIntentProcessorFactory and call processIntent method to process the call:
private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() { @Override public void placeCall(Uri handle, Bundle extras, String callingPackage) { try { synchronized (mLock) { final UserHandle userHandle = Binder.getCallingUserHandle(); long token = Binder.clearCallingIdentity(); try { final Intent intent = new Intent(Intent.ACTION_CALL, handle); mUserCallIntentProcessorFactory.create(mContext, userHandle) .processIntent( intent, callingPackage, hasCallAppOp && hasCallPermission); } } } } }
processIntent determines whether it is a call request:
public void processIntent(Intent intent, String callingPackageName, boolean canCallNonEmergency, boolean isLocalInvocation) { // Ensure call intents are not processed on devices that are not capable of calling. if (!isVoiceCapable()) { return; } String action = intent.getAction(); if (Intent.ACTION_CALL.equals(action) || Intent.ACTION_CALL_PRIVILEGED.equals(action) || Intent.ACTION_CALL_EMERGENCY.equals(action)) { processOutgoingCallIntent(intent, callingPackageName, canCallNonEmergency, isLocalInvocation); } }
- Intent.ACTION_CALL: you can make ordinary calls
public static final String ACTION_CALL = "android.intent.action.CALL";
- Intent.ACTION_CALL_PRIVILEGED: you can dial any type of number
public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";
- Intent.ACTION_CALL_EMERGENCY: you can make an emergency call
public static final String ACTION_CALL_EMERGENCY = "android.intent.action.CALL_EMERGENCY";
processOutgoingCallIntent method:
private void processOutgoingCallIntent(Intent intent, String callingPackageName, boolean canCallNonEmergency, boolean isLocalInvocation) { ------ ------ int videoState = intent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY); Log.d(this, "processOutgoingCallIntent videoState = " + videoState); intent.putExtra(CallIntentProcessor.KEY_IS_PRIVILEGED_DIALER, isDefaultOrSystemDialer(callingPackageName)); // Save the user handle of current user before forwarding the intent to primary user. intent.putExtra(CallIntentProcessor.KEY_INITIATING_USER, mUserHandle); sendIntentToDestination(intent, isLocalInvocation, callingPackageName); }
sendIntentToDestination method:
private boolean sendIntentToDestination(Intent intent, boolean isLocalInvocation, String callingPackage) { intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, false); intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); if (isLocalInvocation) { // We are invoking this from TelecomServiceImpl, so TelecomSystem is available. Don't // bother trampolining the intent, just sent it directly to the call intent processor. // TODO: We should not be using an intent here; this whole flows needs cleanup. Log.i(this, "sendIntentToDestination: send intent to Telecom directly."); synchronized (TelecomSystem.getInstance().getLock()) { TelecomSystem.getInstance().getCallIntentProcessor().processIntent(intent, callingPackage); } } else { // We're calling from the UserCallActivity, so the TelecomSystem is not in the same // process; we need to trampoline to TelecomSystem in the system server process. Log.i(this, "sendIntentToDestination: trampoline to Telecom."); TelecomManager tm = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); tm.handleCallIntent(intent); } return true; }
Android 10 removes the previous broadcast mechanism here
Call the getTelecomSystem method to return the TelecomSystem object, call getCallIntentProcessor(), return the CallIntentProcessor object, and then call the processIntent method. Base note
public void processIntent(Intent intent, String callingPackage) { final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false); Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall); Trace.beginSection("processNewCallCallIntent"); if (isUnknownCall) { processUnknownCallIntent(mCallsManager, intent); } else { processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage); } Trace.endSection(); }
processOutgoingCallIntent method, Call the startOutgoingCall() method of CallsManager to create a Call, and then new NewOutgoingCallIntentBroadcaster, Call processIntent() method:
CallsManager creates a Call link and listens for Call status
Let's first analyze the startOutgoingCall() method
Analyze the processIntent() method of the new newoutgoing callintentbroadcaster
static void processOutgoingCallIntent( Context context, CallsManager callsManager, Intent intent, String callingPackage) { -------- // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns // First execute the code here to update the ui interface CompletableFuture<Call> 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 { // Actually, send the upper layer information to telephone and ril sendNewOutgoingCallIntent(context, call, callsManager, intent); } finally { Log.endSession(); } } }); }
The sendNewOutgoingCallIntent method is defined as follows:‘
static void sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager, Intent intent) { // Asynchronous calls should not usually be made inside a BroadcastReceiver because once // onReceive is complete, the BroadcastReceiver's process runs the risk of getting // killed if memory is scarce. However, this is OK here because the entire Telecom // process will be running throughout the duration of the phone call and should never // be killed. final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false); NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster( context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(), isPrivilegedDialer); // If the broadcaster comes back with an immediate error, disconnect and show a dialog. NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall(); if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) { disconnectCallAndShowErrorDialog(context, call, disposition.disconnectCause); return; } broadcaster.processCall(disposition); }
public void processCall(CallDisposition disposition) { if (disposition.callImmediately) { boolean speakerphoneOn = mIntent.getBooleanExtra( TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false); int videoState = mIntent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY); placeOutgoingCallImmediately(mCall, disposition.callingAddress, null, speakerphoneOn, videoState); // Don't return but instead continue and send the ACTION_NEW_OUTGOING_CALL broadcast // so that third parties can still inspect (but not intercept) the outgoing call. When // the broadcast finally reaches the OutgoingCallBroadcastReceiver, we'll know not to // initiate the call again because of the presence of the EXTRA_ALREADY_CALLED extra. }
Where: placeoutgoing callimmediately Code:
private void placeOutgoingCallImmediately(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn, int videoState) { Log.i(this, "Placing call immediately instead of waiting for OutgoingCallBroadcastReceiver"); // Since we are not going to go through "Outgoing call broadcast", make sure // we mark it as ready. mCall.setNewOutgoingCallIntentBroadcastIsDone(); mCallsManager.placeOutgoingCall(call, handle, gatewayInfo, speakerphoneOn, videoState); }
The startOutgoingCall method is executed first, and then the broadcaster Processcall() method
remarks:
Here, first set up a branch and analyze the callsmanager Startoutgoingcall method
Dialing process of CallsManager
CallsManager. The main logic of statoutgoingcall is to create, update, and save Call objects
startOutgoingCall creates a new (or reused) call, binds callsManager to listen to the call, and then notifies the listener of callsManager to add a call
Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) { Call call = getNewOutgoingCall(handle); // If MMI number if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) { // Let CallsManager monitor the call behavior call.addListener(this); } else if (!mCalls.contains(call)) { // Ensure that the Call will not be added repeatedly (getNewOutgoingCall has a reuse mechanism) // Add a call, and then the callsManager notifies the listener that a call has been added addCall(call); } return call; }
addCall method: addCall adds a call, and then callsManager notifies the listener that a call is added
The CallsManager object will save multiple Call objects into the mCalls collection, and the Call object will set the Listener object to
CallsManager, mutual reference between objects. The CallsManager object sends the oncaladded message through mlisteners
Callback. So what exactly are mlisteners? Extract the key logic in the attribute definition and construction method of the CallsManager class
Series,
Constructor for CallsManager
CallsManager( Context context, TelecomSystem.SyncRoot lock, ContactsAsyncHelper contactsAsyncHelper, CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory, MissedCallNotifier missedCallNotifier, PhoneAccountRegistrar phoneAccountRegistrar, HeadsetMediaButtonFactory headsetMediaButtonFactory, ProximitySensorManagerFactory proximitySensorManagerFactory, InCallWakeLockControllerFactory inCallWakeLockControllerFactory) { // .... // When the CallsManager is constructed, a series of listeners will be added to the CallsManager. When the CallsManager changes, they will be notified mListeners.add(statusBarNotifier); mListeners.add(mCallLogManager); mListeners.add(mPhoneStateBroadcaster); // Focus on incalcontroller, which will be printed in the log mListeners.add(mInCallController); mListeners.add(mRinger); mListeners.add(new RingbackPlayer(this, playerFactory)); mListeners.add(new InCallToneMonitor(playerFactory, this)); // Monitor whether the mobile phone is in Bluetooth, limited headset, earpiece and speaker mode mListeners.add(mCallAudioManager); mListeners.add(missedCallNotifier); // Voice prompt sound mListeners.add(mDtmfLocalTonePlayer); mListeners.add(mHeadsetMediaButton); mListeners.add(mProximitySensorManager); // ... }
The focus is on incalcontroller, because incalcontroller listens to CallsManager. After receiving the message from CallsManager, incalcontroller returns to the original com android. The dialer process notifies the UI of changes, that is, incalcontroller is the system process that actively communicates with com android. Dialer is the bridge of the process. The following explains in detail how to communicate with com when incalcontroller android. The of the dialer process.
InCallController is used to control the logic and UI of the phone App
lnCallController. Oncaladded message callback
public void onCallAdded(Call call) { // First judge whether the service is bound. For the first time, bind it first if (!isBoundAndConnectedToServices()) { Log.i(this, "onCallAdded: %s; not bound or connected.", call); // We are not bound, or we're not connected. bindToServices(call); // Binding service } else { // We are bound, and we are connected. adjustServiceBindingsForEmergency();
The bindToService() method:
@VisibleForTesting public void bindToServices(Call call) { if (mInCallServiceConnection == null) { InCallServiceConnection dialerInCall = null; InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent(); Log.i(this, "defaultDialer: " + defaultDialerComponentInfo); if (defaultDialerComponentInfo != null && !defaultDialerComponentInfo.getComponentName().equals( mSystemInCallComponentName)) { // Focus on the created InCallServiceBindingConnection object dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo); } Log.i(this, "defaultDialer: " + dialerInCall); InCallServiceInfo systemInCallInfo = getInCallServiceComponent( mSystemInCallComponentName, IN_CALL_SERVICE_TYPE_SYSTEM_UI); EmergencyInCallServiceConnection systemInCall = new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall); systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall()); InCallServiceConnection carModeInCall = null; InCallServiceInfo carModeComponentInfo = getCarModeComponent(); if (carModeComponentInfo != null && !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) { carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo); } mInCallServiceConnection = new CarSwappingInCallServiceConnection(systemInCall, carModeInCall); } mInCallServiceConnection.setCarMode(shouldUseCarModeUI()); // Actually try binding to the UI InCallService. If the response //Execute the real operation of the InCa ll Service service of the binding call interface and judge the returned result if (mInCallServiceConnection.connect(call) == InCallServiceConnection.CONNECTION_SUCCEEDED) { // Only connect to the non-ui InCallServices if we actually connected to the main UI // one. //Only when we successfully connect to the call interface service can we execute the IncallService service without interface connectToNonUiInCallServices(call); mBindingFuture = new CompletableFuture<Boolean>().completeOnTimeout(false, mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay( mContext.getContentResolver()), TimeUnit.MILLISECONDS); } else { Log.i(this, "bindToServices: current UI doesn't support call; not binding."); } }
In the above classes, InCalIServiceConnection is the internal public class of lnCallController,
The internal classes of servicecallncallnconnection and servicecallncallnconnection are private,
All inherit from lnCallServiceConnection class.
mInCallServiceConnection.connect(call) actually calls the connect method of lnCallServiceBindingConnection object,
@Override public int connect(Call call) { if (mIsConnected) { Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request."); return CONNECTION_SUCCEEDED; } if (call != null && call.isSelfManaged() && !mInCallServiceInfo.isSelfManagedCallsSupported()) { Log.i(this, "Skipping binding to %s - doesn't support self-mgd calls", mInCallServiceInfo); mIsConnected = false; return CONNECTION_NOT_SUPPORTED; } // Indicates the binding in callservice SERVICE_ Interface service 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_ALLOW_BACKGROUND_ACTIVITY_STARTS, UserHandle.CURRENT)) { Log.w(this, "Failed to connect."); mIsConnected = false; } if (call != null && mIsConnected) { call.getAnalytics().addInCallService( mInCallServiceInfo.getComponentName().flattenToShortString(), mInCallServiceInfo.getType()); } return mIsConnected ? CONNECTION_SUCCEEDED : CONNECTION_FAILED; }
There is a {mserviceconnection object in the above code
Take a look at the serviceconnection mserviceconnection object
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(); } } }
onConnected method: there is another onConnected method
protected void onConnected(IBinder service) { boolean shouldRemainConnected = InCallController.this.onConnected(mInCallServiceInfo, service); if (!shouldRemainConnected) { // Sometimes we can opt to disconnect for certain reasons, like if the // InCallService rejected our initialization step, or the calls went away // in the time it took us to bind to the InCallService. In such cases, we go // ahead and disconnect ourselves. disconnect(); } }
The above code logic reflects the whole process of binding services. First, create the lnCallServiceBindingConnection object. At the same time, an mServiceConnection object will be created synchronously. This object is an anonymous ServiceConnection type and overrides the onServiceConnected and onServiceConnected methods
; Next, create the action lncallservice The intent object of serviceinterface, and updated some key information of PhoneAccount and Call; Then, the bindServiceAsUser method of the Android system is invoked to bind the service. Finally, the finishing work after the binding service is successful --- onConnected system callback,
Finally, a call to {incalcontroller this. The main processing logic in the method is as follows:
private boolean onConnected(InCallServiceInfo info, IBinder service) { Trace.beginSection("onConnected: " + info.getComponentName()); Log.i(this, "onConnected to %s", info.getComponentName()); // Get IInCallService service and save IInCallService inCallService = IInCallService.Stub.asInterface(service); mInCallServices.put(info, inCallService); try { //Call the service method to add iadapter, and incalladapter implements iincalladapter Aidl interface 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; } // Upon successful connection, send the state of the world to the service. List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls()); Log.i(this, "Adding %s calls to InCallService after onConnected: %s, including external " + "calls", calls.size(), info.getComponentName()); int numCallsSent = 0; //Send the previously saved Call object through inCallService for (Call call : calls) { try { //Note that there is the conversion of Call object, which will be com android. server. telecom. Convert Call to strideable // Object passed by Android telecom. ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall( call, true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(), info.isExternalCallsSupported(), includeRttCall, info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI); inCallService.addCall(parcelableCall); // Track the call if we don't already know about it. addCall(call); } catch (RemoteException ignored) { Log.e(this, ignored, "add call fialed"); } } try { // Notify Call of changes in relevant status inCallService.onCallAudioStateChanged(mCallsManager.getAudioState()); inCallService.onCanAddCallChanged(mCallsManager.canAddCall()); } catch (RemoteException ignored) { Log.e(this, ignored, "changed event failed"); } mBindingFuture.complete(true); Log.i(this, "%s calls sent to InCallService.", numCallsSent); Trace.endSection(); return true; }
lnCallController starts the second cross process access in the dialing process by binding services from the system of Telecom application_ The serve process returns to the dialer application's com android . Dialer process.
com.android.dialer process
The implementation of incalserviceimpl incalservice is responsible for communicating with Telecom and updating the UI
Used to receive updates about calls from the Telecom component. This service is bound to Telecom while there exist calls which potentially require UI. This includes ringing (incoming), dialing (outgoing), and active calls. When the last call is disconnected, Telecom will unbind to the service triggering InCallActivity (via CallList) to finish soon after.
Used to receive updates about calls from telecommunications components. When there are calls that may require UI, this service is bound to Telecom. This includes ringing (incoming), dialing (outgoing), and active calls. When the last call is disconnected, the Telecom will unbind the service and trigger InCallActivity (through CallList) to complete in a short time.
The core function of InCallServiceImpl is to return an InCallServiceBinder when its parent class InCallService and InCallServiceImpl are bound. Therefore, the interface obtained by the system process is this. Before binding, incallpresenter will be initialized. Incallpresenter is globally unique (single example), which is responsible for updating the UI of the phone APP. (MVP mode)
@Override public IBinder onBind(Intent intent) { Trace.beginSection("InCallServiceImpl.onBind"); final Context context = getApplicationContext(); final ContactInfoCache contactInfoCache = ContactInfoCache.getInstance(context); AudioModeProvider.getInstance().initializeAudioState(this); InCallPresenter.getInstance() .setUp( context, CallList.getInstance(), new ExternalCallList(), new StatusBarNotifier(context, contactInfoCache), new ExternalCallNotifier(context, contactInfoCache), contactInfoCache, new ProximitySensor( context, AudioModeProvider.getInstance(), new AccelerometerListener(context)), new FilteredNumberAsyncQueryHandler(context), speakEasyCallManager); InCallPresenter.getInstance().onServiceBind(); // Set the mServiceBound property of the service binding state InCallPresenter.getInstance().maybeStartRevealAnimation(intent); // Open Activity TelecomAdapter.getInstance().setInCallService(this);// Save service returnToCallController = new ReturnToCallController(this, ContactInfoCache.getInstance(context)); feedbackListener = FeedbackComponent.get(context).getCallFeedbackListener(); CallList.getInstance().addListener(feedbackListener); IBinder iBinder = super.onBind(intent); // Call the onBind method of the parent class InCallService Trace.endSection(); return iBinder; }
@Override 421 public IBinder onBind(Intent intent) { 422 return new InCallServiceBinder(); 423 }
Go to InCallServiceBinder
InCallServiceBinder implements iincallservice Aidl's interfaces, which send Handler messages to the service
The received service request is converted into asynchronous processing mode
/** Manages the binder calls so that the implementor does not need to deal with it. */ // InCallServiceBinder implements iincallservice Aidl's interfaces, which send Handler messages to the service //The received service request is converted into asynchronous processing mode 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(); }
mHandler sends a Handler message to convert the synchronous call of llnCallService into asynchronous processing, and continues to analyze its implementation logic in the subsequent setlnCallAdapter # and # addCall interface responses.
In InCallPresenter MVP mode, control logic and UI
The maystartrevealanimation of InCallPresenter will start InCallActivity, that is, the interface of dialing in progress (or the interface of incoming calls)
public void maybeStartRevealAnimation(Intent intent) { if (intent == null || inCallActivity != null) { return; } final Bundle extras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); if (extras == null) { // Incoming call, just show the in-call UI directly. return; } if (extras.containsKey(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS)) { // Account selection dialog will show up so don't show the animation. return; } final PhoneAccountHandle accountHandle = intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT); setBoundAndWaitingForOutgoingCall(true, accountHandle); if (shouldStartInBubbleModeWithExtras(extras)) { LogUtil.i("InCallPresenter.maybeStartRevealAnimation", "shouldStartInBubbleMode"); // Show bubble instead of in call UI return; } final Intent activityIntent = InCallActivity.getIntent(context, false, true, false /* forFullScreen */); activityIntent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint); context.startActivity(activityIntent); }
Where: getInCallIntent is to create an Intent to start InCallActivity
public static Intent getIntent( Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) { Intent intent = new Intent(Intent.ACTION_MAIN, null); intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK); intent.setClass(context, InCallActivity.class); if (showDialpad) { intent.putExtra(IntentExtraNames.SHOW_DIALPAD, true); } intent.putExtra(IntentExtraNames.NEW_OUTGOING_CALL, newOutgoingCall); intent.putExtra(IntentExtraNames.FOR_FULL_SCREEN, isForFullScreen); return intent; }
So far, the interface of dialing in progress is up. Next, check the implementation of InCallServiceBinder, setInCallAdapter and addCall.
InCallServiceBinder receives the message and forwards it directly to mHandler
/** Manages the binder calls so that the implementor does not need to deal with it. */ 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(); }
Take a look at the mHandler object, in case MSG_ SET_ IN_ CALL_ Phone object created in adapter
Phone at the UI level, phone is unique, regardless of type, and there is only one number
private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { // ... switch (msg.what) { case MSG_SET_IN_CALL_ADAPTER: // setInCallAdapter triggers the creation of com android. Dialer's unique phone object // That is, on COM android. All operations of the dialer process are sent to the system process (framework layer) through the mInCallAdapter in the phone mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj)); // Core, let incalservice monitor the status changes of Phone mPhone.addListener(mPhoneListener); onPhoneCreated(mPhone); break; case MSG_ADD_CALL: mPhone.internalAddCall((ParcelableCall) msg.obj); break; case MSG_UPDATE_CALL: mPhone.internalUpdateCall((ParcelableCall) msg.obj); break; // ... // Omit a large number of message types } }
mPhone.internalAddCall creates a local Telecom Call (different from the Call of system process, hereinafter referred to as TCall) to notify InCallService
final void internalAddCall(ParcelableCall parcelableCall) { Call call = new Call(this, parcelableCall.getId(), mInCallAdapter, parcelableCall.getState()); mCallByTelecomCallId.put(parcelableCall.getId(), call); mCalls.add(call); checkCallTree(parcelableCall); call.internalUpdate(parcelableCall, mCallByTelecomCallId); // Core, notify InCallService that a Call has been added //private void fireCallAdded(Call call) { // for (Listener listener : mListeners) { // listener.onCallAdded(this, call); // } //} fireCallAdded(call); }
The mListeners in the fireCallAdded method are represented as follows
This is actually an empty implementation. The specific implementation is in the subclass. Continue to follow up to the subclass InCallServiceImpl #
private Phone.Listener mPhoneListener = new Phone.Listener() { /** ${inheritDoc} */ @Override public void onAudioStateChanged(Phone phone, AudioState audioState) { InCallService.this.onAudioStateChanged(audioState); } . . . . . // /** ${inheritDoc} */ @Override public void onCallAdded(Phone phone, Call call) { InCallService.this.onCallAdded(call); }
mPhoneListener.onCallAdded => InCallServiceImpl.onCallAdded
public void onCallAdded(Call call) { // Core, main process //CallList.getInstance().onCallAdded(call); // Change of binding state of TCall InCallPresenter.getInstance().onCallAdded(call); }
public void onCallAdded(final android.telecom.Call call) { LatencyReport latencyReport = new LatencyReport(call); if (shouldAttemptBlocking(call)) { maybeBlockCall(call, latencyReport); } else { if (call.getDetails().hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) { mExternalCallList.onCallAdded(call); } else { latencyReport.onCallBlockingDone(); //Call list calls oncaladded mCallList.onCallAdded(mContext, call, latencyReport); } } // Since a call has been added we are no longer waiting for Telecom to send us a call. setBoundAndWaitingForOutgoingCall(false, null); call.registerCallback(mCallCallback); }
129 public void onCallAdded( 130 final Context context, final android.telecom.Call telecomCall, LatencyReport latencyReport) { 131 Trace.beginSection("CallList.onCallAdded"); 216 if (call.getState() == DialerCallState.INCOMING 217 || call.getState() == DialerCallState.CALL_WAITING) { 218 if (call.isActiveRttCall()) { 219 if (!call.isPhoneAccountRttCapable()) { 220 RttPromotion.setEnabled(context); 221 } 222 Logger.get(context) 223 .logCallImpression( 224 DialerImpression.Type.INCOMING_RTT_CALL, 225 call.getUniqueCallId(), 226 call.getTimeAddedMs()); 227 } // Handle incoming calls 228 onIncoming(call); 229 } else { // Process dialing 230 if (call.isActiveRttCall()) { 231 Logger.get(context) 232 .logCallImpression( 233 DialerImpression.Type.OUTGOING_RTT_CALL, 234 call.getUniqueCallId(), 235 call.getTimeAddedMs()); 236 } 237 onUpdateCall(call); // Core code 238 notifyGenericListeners();
CallList maintain phone list
CallList maintains a Call list (Call in CallList is called LCall for short, which is different from TCall above), and each LCall maintains a TCall
- TCall is maintained by Phone
- LCall is maintained by CallList
- TCall and LCall correspond one-to-one
Continue tracking the notifyGenericListeners method
633 private void notifyGenericListeners() { 634 Trace.beginSection("CallList.notifyGenericListeners"); 635 for (Listener listener : listeners) { 636 listener.onCallListChange(this); 637 } 638 Trace.endSection(); 639 }
Tracking listeners
Let's find out who listens to CallListInCallPresenter and implements calllist Listener interface, so incallpresenter Oncalllistchange will be triggered
public class InCallPresenter implements CallList.Listener, AudioModeProvider.AudioModeListener {
onCallListChange method traced
public void onCallListChange(CallList callList) { // ... // According to the new status, decide whether to open InCallActivity or close InCallActivity to ensure that it will not be opened again newState = startOrFinishUi(newState); for (InCallStateListener listener : mListeners) { // All listeners listening to InCallPresenter will be notified. listener.onStateChange(oldState, mInCallState, callList); } }
The above involves the "InCallStateListener" listener, which is an interface that defines a method onStateChange
Let's talk about InCallActivity again
InCallActivity shows dialing or incoming call status
InCallActivity distinguishes different states by displaying or hiding fragments. The Fragment in progress is DialpadFragment, and the Presenter corresponding to DialpadFragment is dialpadpresenter, onuiread
InCallPresenter.java directly controls InCallActivity. At the same time, it controls AnswerPresenter through some listeners, such as IncomingCallListener/ CanAddCallListener/ InCallDetailsListener, etc.
AnswerPresenter controls the display of AnswerFragment, which is part of the InCallActivity interface;
VideoCallPresenter controls the display of VideoCallFragment and is a part of the InCallActivity interface;
CallCardPresenter controls the display of CallCardFragment, which is part of the InCallActivity interface;
CallButtonPresenter controls the display of CallButtonFragment and is a part of the InCallActivity interface;
These presenters generally implement the method corresponding to the listener of InCallPresenter.
Of which:
CallCardFragment: used to display contact information, call time, etc;
CallButtonFragment: the control button at the bottom of the call interface.
DialpadFragment: dial display control.
AnswerFragment: incoming call control, which is used to operate answer / reject / short message quick reply.
Conference manager fragment: the interface of conference call.
VideoCallFragment: the middle note control is invoked in CallCardFragment.
as follows
public void onUiReady(DialpadUi ui) { super.onUiReady(ui); InCallPresenter.getInstance().addListener(this); call = CallList.getInstance().getOutgoingOrActive(); }
So dialpadfragment Onstatechange will be triggered, followed by UI update (MVP)
private static final Map<Integer, Character> displayMap = new ArrayMap<>(); /** Set up the static maps */ static { // Map the buttons to the display characters displayMap.put(R.id.one, '1'); displayMap.put(R.id.two, '2'); displayMap.put(R.id.three, '3'); displayMap.put(R.id.four, '4'); displayMap.put(R.id.five, '5'); displayMap.put(R.id.six, '6'); displayMap.put(R.id.seven, '7'); displayMap.put(R.id.eight, '8'); displayMap.put(R.id.nine, '9'); displayMap.put(R.id.zero, '0'); displayMap.put(R.id.pound, '#'); displayMap.put(R.id.star, '*'); }
To sum up: the incalcontroller of the system process receives the message from the CallsManager and sends it to com android. The dialer process updates the status of the dialing interface step by step, and the whole process has been completed.
The second branch: callsmanager placeOutgoingCall
Execute the broadcaster Processcall() method
public void processCall(CallDisposition disposition) { if (disposition.callImmediately) { boolean speakerphoneOn = mIntent.getBooleanExtra( TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false); int videoState = mIntent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY); placeOutgoingCallImmediately(mCall, disposition.callingAddress, null, speakerphoneOn, videoState);
Among them placeOutgoingCallImmediately Method
private void placeOutgoingCallImmediately(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn, int videoState) { Log.i(this, "Placing call immediately instead of waiting for OutgoingCallBroadcastReceiver"); // Since we are not going to go through "Outgoing call broadcast", make sure // we mark it as ready. mCall.setNewOutgoingCallIntentBroadcastIsDone(); mCallsManager.placeOutgoingCall(call, handle, gatewayInfo, speakerphoneOn, videoState); }
CallsManager.placeOutgoingCall
2048 @VisibleForTesting 2049 public void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, 2050 boolean speakerphoneOn, int videoState) { 2051 if (call == null) { 2052 // don't do anything if the call no longer exists 2053 Log.i(this, "Canceling unknown call."); 2054 return; 2055 } 2056 2057 final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayAddress(); 2058 2059 if (gatewayInfo == null) { 2060 Log.i(this, "Creating a new outgoing call with handle: %s", Log.piiHandle(uriHandle)); 2061 } else { 2062 Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s", 2063 Log.pii(uriHandle), Log.pii(handle)); 2064 } 2065 2066 call.setHandle(uriHandle); 2067 call.setGatewayInfo(gatewayInfo); 2068 2069 final boolean useSpeakerWhenDocked = mContext.getResources().getBoolean( 2070 R.bool.use_speaker_when_docked); 2071 final boolean useSpeakerForDock = isSpeakerphoneEnabledForDock(); 2072 final boolean useSpeakerForVideoCall = isSpeakerphoneAutoEnabledForVideoCalls(videoState); 2073 2074 // Auto-enable speakerphone if the originating intent specified to do so, if the call 2075 // is a video call, of if using speaker when docked 2076 call.setStartWithSpeakerphoneOn(speakerphoneOn || useSpeakerForVideoCall 2077 || (useSpeakerWhenDocked && useSpeakerForDock)); 2078 call.setVideoState(videoState); 2079 2080 if (speakerphoneOn) { 2081 Log.i(this, "%s Starting with speakerphone as requested", call); 2082 } else if (useSpeakerWhenDocked && useSpeakerForDock) { 2083 Log.i(this, "%s Starting with speakerphone because car is docked.", call); 2084 } else if (useSpeakerForVideoCall) { 2085 mInVideoMode = true; // UNISOC: add for bug1152803 2086 Log.i(this, "%s Starting with speakerphone because its a video call.", call); 2087 } 2088 2089 if (call.isEmergencyCall()) { 2090 new AsyncEmergencyContactNotifier(mContext).execute(); 2091 } 2092 2093 final boolean requireCallCapableAccountByHandle = mContext.getResources().getBoolean( 2094 com.android.internal.R.bool.config_requireCallCapableAccountForHandle); 2095 final boolean isOutgoingCallPermitted = isOutgoingCallPermitted(call, 2096 call.getTargetPhoneAccount()); 2097 final String callHandleScheme = 2098 call.getHandle() == null ? null : call.getHandle().getScheme(); 2099 if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) { 2100 // If the account has been set, proceed to place the outgoing call. 2101 // Otherwise the connection will be initiated when the account is set by the user. 2102 if (call.isSelfManaged() && !isOutgoingCallPermitted) { 2103 notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call); 2104 } else { 2105 if (call.isEmergencyCall()) { 2106 // Drop any ongoing self-managed calls to make way for an emergency call. 2107 disconnectSelfManagedCalls("place emerg call" /* reason */); 2108 } 2109 2110 call.startCreateConnection(mPhoneAccountRegistrar); 2111 } 2112 } else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts( 2113 requireCallCapableAccountByHandle ? callHandleScheme : null, false, 2114 call.getInitiatingUser()).isEmpty()) { 2115 // If there are no call capable accounts, disconnect the call. 2116 markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.CANCELED, 2117 "No registered PhoneAccounts")); 2118 markCallAsRemoved(call); 2119 } 2120 }
Call is a dialing maintained by CallsManager or an incoming call instance
Call.startCreateConnection
void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) { if (mCreateConnectionProcessor != null) { Log.w(this, "mCreateConnectionProcessor in startCreateConnection is not null. This is" + " due to a race between NewOutgoingCallIntentBroadcaster and " + "phoneAccountSelected, but is harmlessly resolved by ignoring the second " + "invocation."); return; } mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this, phoneAccountRegistrar, mContext); mCreateConnectionProcessor.process(); }
CreateConnectionProcessor.process ==> CreateConnectionProcessor.attemptNextPhoneAccount
@VisibleForTesting public void process() { Log.v(this, "process"); clearTimeout(); mAttemptRecords = new ArrayList<>(); if (mCall.getTargetPhoneAccount() != null) { mAttemptRecords.add(new CallAttemptRecord( mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount())); } if (!mCall.isSelfManaged()) { adjustAttemptsForConnectionManager(); adjustAttemptsForEmergency(mCall.getTargetPhoneAccount()); } mAttemptRecordIterator = mAttemptRecords.iterator(); attemptNextPhoneAccount(); }
attemptNextPhoneAccount method
private ConnectionServiceWrapper mService; private void attemptNextPhoneAccount() { //. . . // if (mCallResponse != null && attempt != null) { Log.i(this, "Trying attempt %s", attempt); PhoneAccountHandle phoneAccount = attempt.connectionManagerPhoneAccount; mService = mRepository.getService(phoneAccount.getComponentName(), phoneAccount.getUserHandle()); if (mService == null) { Log.i(this, "Found no connection service for attempt %s", attempt); attemptNextPhoneAccount(); } else { mConnectionAttempt++; mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount); mCall.setTargetPhoneAccount(attempt.targetPhoneAccount); mCall.setConnectionService(mService); setTimeoutIfNeeded(mService, attempt); if (mCall.isIncoming()) { mService.createConnection(mCall, CreateConnectionProcessor.this); } else { // Start to create the connection for outgoing call after the ConnectionService // of the call has gained the focus. mCall.getConnectionServiceFocusManager().requestFocus( mCall, new CallsManager.RequestCallback(new CallsManager.PendingAction() { @Override public void performAction() { Log.d(this, "perform create connection"); mService.createConnection( mCall, CreateConnectionProcessor.this); } })); } } } else { Log.v(this, "attemptNextPhoneAccount, no more accounts, failing"); DisconnectCause disconnectCause = mLastErrorDisconnectCause != null ? mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR); notifyCallConnectionFailure(disconnectCause); }
ConnectionServiceWrapper is responsible for communicating with telephone
mService.createConnection(mCall, CreateConnectionProcessor.this); Start linking
void createConnection(final Call call, final CreateConnectionResponse response) { // Once the link is created successfully, onSuccess will be called BindCallback callback = new BindCallback() { @Override public void onSuccess() { try { /// M: For VoLTE @{ boolean isConferenceDial = call.isConferenceDial(); // Conference call if (isConferenceDial) { logOutgoing("createConference(%s) via %s.", call, getComponentName()); mServiceInterface.createConference( call.getConnectionManagerPhoneAccount(), callId, new ConnectionRequest( call.getTargetPhoneAccount(), call.getHandle(), extras, call.getVideoState()), call.getConferenceParticipants(), call.isIncoming()); // Non Conference (call here) } else { // Creating links through remote interfaces mServiceInterface.createConnection( call.getConnectionManagerPhoneAccount(), callId, new ConnectionRequest( call.getTargetPhoneAccount(), call.getHandle(), extras, call.getVideoState()), call.isIncoming(), call.isUnknown()); } /// @} } catch (RemoteException e) { } } @Override public void onFailure() { } }; mBinder.bind(callback, call); }
mBinder is the Binder2 object, and Binder2 is the internal class of ServiceBinder, the parent class of ConnectionServiceWrapper. Therefore, the bind() method of Binder2 class of the internal class of ServiceBinder is called here. First create a ServiceConnection object, and then bind a remote service.
If the binding is successful, the onServiceConnected() method of ServiceBinderConnection, an internal class of ServiceBinder, is called.
Two things have been done here:
1). Call back the setServiceInterface() method of ConnectionServiceWrapper through the setBinder() method, and call back through mserviceinterface = iconnectionservice Stub. asInterface(binder);
This line of code gets a remote server object mServiceInterface.
2). Then call the handleSuccessfulConnection() method to call back the onSuccess() method of the callback, which will return to the createConnection() method of the ConnectionServiceWrapper. Call connectionservice The createConnection() method of mBinder in Java then calls the createConnection() method through message passing.
bind method of mBider
void bind(BindCallback callback, Call call) { if (mServiceConnection == null) { Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName); ServiceConnection connection = new ServiceBinderConnection(call); // Bind if (mUserHandle != null) { isBound = mContext.bindServiceAsUser(serviceIntent, connection, bindingFlags, mUserHandle); } else { isBound = mContext.bindService(serviceIntent, connection, bindingFlags); } if (!isBound) { handleFailedConnection(); return; } } else { } }
After binding is successful, onServiceConnected of ServiceBinderConnection will be triggered
@Override public void onServiceConnected(ComponentName componentName, IBinder binder) { try { if (binder != null) { mServiceDeathRecipient = new ServiceDeathRecipient(componentName); try { binder.linkToDeath(mServiceDeathRecipient, 0); mServiceConnection = this; // Will trigger connectionservicewrapper setServiceInterface ==> ConnectionServiceWrapper.addConnectionServiceAdapter adopt mServiceInterface,Provide an interface for the bound service to access itself setBinder(binder); // Trigger onSuccess of callback in bind (bindcallback, call call) handleSuccessfulConnection(); } catch (RemoteException e) { Log.w(this, "onServiceConnected: %s died."); if (mServiceDeathRecipient != null) { mServiceDeathRecipient.binderDied(); } } } } } finally { Log.endSession(); } }
In the whole binding process, only two things are done,
One is to provide remote services with their own interfaces,
The second is to use the remote interface to create a call link.
These two things are carried out across processes. The interface that the remote service accesses itself is connectionservicewrapper Adapter is a Binder.
ConnectionServiceWrapper. The adapter # provides a set of operations to update the Call status. Because the current analysis is the Call making process, analyze setDialing first
Inherited from iconnectionserviceadapter. By internal class Adapter Stub, you can see that cross process access will take place
public class ConnectionServiceWrapper extends ServiceBinder implements ConnectionServiceFocusManager.ConnectionServiceFocus { private final class Adapter extends IConnectionServiceAdapter.Stub {
Call connectionservice The createConnection() method of mBinder in Java then calls the createConnection() method through message passing.
private void handleSuccessfulConnection() { // Make a copy so that we don't have a deadlock inside one of the callbacks. Set<BindCallback> callbacksCopy = new ArraySet<>(); synchronized (mCallbacks) { callbacksCopy.addAll(mCallbacks); mCallbacks.clear(); } for (BindCallback callback : callbacksCopy) { callback.onSuccess(); } }
Callback the onSuccess() method of callback by calling the handleSuccessfulConnection() method, which will return to the createConnection() method of ConnectionServiceWrapper.
1094 public void createConnection(final Call call, final CreateConnectionResponse response) { 1095 Log.d(this, "createConnection(%s) via %s.", call, getComponentName()); 1096 BindCallback callback = new BindCallback() { 1097 @Override 1098 public void onSuccess() { 1099 String callId = mCallIdMapper.getCallId(call); 1100 mPendingResponses.put(callId, response); 1101 ///---------// 1146 1150 // Core code 1151 try { 1152 mServiceInterface.createConnection( 1153 call.getConnectionManagerPhoneAccount(), 1154 callId, 1155 connectionRequest, 1156 call.shouldAttachToExistingConnection(), 1157 call.isUnknown(), 1158 Log.getExternalSession()); 1159 1160 } catch (RemoteException e) { 1161 Log.e(this, e, "Failure to createConnection -- %s", getComponentName()); 1162 mPendingResponses.remove(callId).handleCreateConnectionFailure( 1163 new DisconnectCause(DisconnectCause.ERROR, e.toString())); 1164 } 1165 } 1166 1167 @Override 1168 public void onFailure() { 1169 Log.e(this, new Exception(), "Failure to call %s", getComponentName()); 1170 response.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.ERROR)); 1171 } 1172 }; 1173 1174 mBinder.bind(callback, call); 1175 }
private IConnectionService mServiceInterface;
The createConnection() method creates different connections by determining whether to call or go,
Call onCreateOutgoingConnection() in case of power failure,
TelephonyConnectionService is an instance of ConnectionService, so enter TelephonyConnectionService onCreateOutgoingConnection() method of Java.
private void createConnection( final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming, boolean isUnknown) { boolean isLegacyHandover = request.getExtras() != null && request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER, false); boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean( TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false); Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " + "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b", callManagerAccount, callId, request, isIncoming, isUnknown, isLegacyHandover, isHandover); Connection connection = null; if (isHandover) { PhoneAccountHandle fromPhoneAccountHandle = request.getExtras() != null ? (PhoneAccountHandle) request.getExtras().getParcelable( TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT) : null; if (!isIncoming) { connection = onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request); } else { connection = onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request); } } else { connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request) : isIncoming ? onCreateIncomingConnection(callManagerAccount, request) : onCreateOutgoingConnection(callManagerAccount, request); }
TelephonyConnectionService is an instance of ConnectionService, so enter TelephonyConnectionService onCreateOutgoingConnection() method of Java.
@Override public Connection onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request) { //Many judgments are made here, and the failed connection is returned (for example, the dialing number is empty, the sim card is not specified, and it is set to 4G only) ...... // 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 (!isEmergencyNumber) { final Connection resultConnection = getTelephonyConnection(request, numberToDial, false, handle, phone); return placeOutgoingConnection(request, resultConnection, phone); } else { final Connection resultConnection = getTelephonyConnection(request, numberToDial, true, handle, phone); CompletableFuture<Boolean> phoneFuture = delayDialForDdsSwitch(phone); phoneFuture.whenComplete((result, error) -> { if (error != null) { Log.w(this, "onCreateOutgoingConn - delayDialForDdsSwitch exception= " + error.getMessage()); } Log.i(this, "onCreateOutgoingConn - delayDialForDdsSwitch result = " + result); placeOutgoingConnection(request, resultConnection, phone); }); return resultConnection; } ...... //If none of the above is true, execute here placeOutgoingConnection(connection, phone, request); }
Continue tracking the placeOutgoingConnection() method to handle the dialing process
private void placeOutgoingConnection( TelephonyConnection connection, Phone phone, ConnectionRequest request) { placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras()); }
Enter another placeOutgoingConnection() method
private void placeOutgoingConnection( TelephonyConnection connection, Phone phone, int videoState, Bundle extras) { String number = connection.getAddress().getSchemeSpecificPart(); boolean isAddParticipant = (extras != null) && extras .getBoolean(TelephonyProperties.ADD_PARTICIPANT_KEY, false); Log.d(this, "placeOutgoingConnection isAddParticipant = " + isAddParticipant); updatePhoneAccount(connection, phone); com.android.internal.telephony.Connection originalConnection = null; try { if (phone != null) { if (isAddParticipant) { phone.addParticipant(number);// Do some processing for emergency numbers return; } else { // Core code originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder() //call .setVideoState(videoState) .setIntentExtras(extras) .setRttTextStream(connection.getRttTextStream()) .build()); } } } catch (CallStateException e) { // Failure handling Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e); int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE; if (e.getError() == CallStateException.ERROR_OUT_OF_SERVICE) { cause = android.telephony.DisconnectCause.OUT_OF_SERVICE; } else if (e.getError() == CallStateException.ERROR_POWER_OFF) { cause = android.telephony.DisconnectCause.POWER_OFF; } connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( cause, e.getMessage(), phone.getPhoneId())); connection.clearOriginalConnection(); connection.destroy(); return; } if (originalConnection == null) { // Failure handling int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE; // On GSM phones, null connection means that we dialed an MMI code if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { Log.d(this, "dialed MMI code"); int subId = phone.getSubId(); Log.d(this, "subId: "+subId); telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI; final Intent intent = new Intent(this, MMIDialogActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); if (SubscriptionManager.isValidSubscriptionId(subId)) { intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); } startActivity(intent); } Log.d(this, "placeOutgoingConnection, phone.dial returned null"); connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( telephonyDisconnectCause, "Connection is null", phone.getPhoneId())); connection.clearOriginalConnection(); connection.destroy(); } else { connection.setOriginalConnection(originalConnection); // The TelephonyConnection monitors the phone. When updating, it takes it from the originalConnection. //TelephonyConnection. The implementation of setoriginalconnection is mainly to register mHandler with //In the listener list of phone, the change of phone will trigger the handleMessage in mHandler to be called. }
After calling the dial() method of Phone, you enter the Telephony Framework layer. In Android 7.0, GSMPhone and CDMAPhone are all integrated into GsmCdmaPhone. Continue to see dial() in GsmCdmaPhone
@Override public Connection dial(String dialString, UUSInfo uusInfo, int videoState, Bundle intentExtras) throws CallStateException { ....... //IMS phone is a class that deals specifically with VoLTE if (videoState == VideoProfile.STATE_AUDIO_ONLY) { if (DBG) Rlog.d(LOG_TAG, "Trying IMS PS call");// return imsPhone.dial(dialString, uusInfo, videoState, intentExtras); } else { if (SystemProperties.get("persist.mtk_vilte_support").equals("1")) { //Volte IMS PS video call supported return imsPhone.dial(dialString, uusInfo, videoState, intentExtras); } else { //cs video call return dialInternal(dialString, uusInfo, videoState, intentExtras); /// @} } ....... if (isPhoneTypeGsm()) { /// M: CC: For 3G VT only @{ //return dialInternal(dialString, null, VideoProfile.STATE_AUDIO_ONLY, intentExtras); return dialInternal(dialString, null, videoState, intentExtras); /// @} } else { return dialInternal(dialString, null, videoState, intentExtras); } }
Continue to look at the dialInternal() method. Here are two
@Override protected Connection dialInternal(String dialString, DialArgs dialArgs) throws CallStateException { return dialInternal(dialString, dialArgs, null); } protected Connection dialInternal(String dialString, DialArgs dialArgs, ResultReceiver wrappedCallback) throws CallStateException { // Need to make sure dialString gets parsed properly String newDialString = PhoneNumberUtils.stripSeparators(dialString); if (isPhoneTypeGsm()) { // handle in-call MMI first if applicable if (handleInCallMmiCommands(newDialString)) { return null; } // Only look at the Network portion for mmi String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString); GsmMmiCode mmi = GsmMmiCode.newFromDialString(networkPortion, this, mUiccApplication.get(), wrappedCallback); if (DBG) logd("dialInternal: dialing w/ mmi '" + mmi + "'..."); if (mmi == null) { return mCT.dialGsm(newDialString, dialArgs.uusInfo, dialArgs.intentExtras); } else if (mmi.isTemporaryModeCLIR()) { return mCT.dialGsm(mmi.mDialingNumber, mmi.getCLIRMode(), dialArgs.uusInfo, dialArgs.intentExtras); } else { mPendingMMIs.add(mmi); mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null)); mmi.processCode(); return null; } } else { // Core method return mCT.dial(newDialString, dialArgs.intentExtras); } }
mCT is the gsmcdmacaltracker object. Let's next look at dial() in gsmcdmacaltracker
Call according to different systems
public Connection dial(String dialString, Bundle intentExtras) throws CallStateException { if (isPhoneTypeGsm()) { return dialGsm(dialString, CommandsInterface.CLIR_DEFAULT, intentExtras); } else { return dialCdma(dialString, CommandsInterface.CLIR_DEFAULT, intentExtras); } }
Continue tracking diaCdma code
315 public synchronized Connection dialGsm(String dialString, int clirMode, UUSInfo uusInfo, 316 Bundle intentExtras) 317 throws CallStateException { 369 370 371 if ( mPendingMO.getAddress() == null || mPendingMO.getAddress().length() == 0 372 || mPendingMO.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) { 373 // Phone number is invalid 374 mPendingMO.mCause = DisconnectCause.INVALID_NUMBER; 375 376 // handlePollCalls() will notice this call not present 377 // and will mark it as dropped. 378 pollCallsWhenSafe(); 379 } else { 380 // Always unmute when initiating a new call 381 setMute(false); 382 //The type of mCi is CommandsInterface, which is used as a parameter when creating a phone. In fact, it executes RIL dial() 383 mCi.dial(mPendingMO.getAddress(), mPendingMO.isEmergencyCall(), 384 mPendingMO.getEmergencyNumberInfo(), mPendingMO.hasKnownUserIntentEmergency(), 385 clirMode, uusInfo, obtainCompleteMessage()); 386 } 387 388 if (mNumberConverted) { 389 mPendingMO.setConverted(origNumber); 390 mNumberConverted = false; 391 } 392 393 updatePhoneState(); 394 mPhone.notifyPreciseCallStateChanged(); 395 396 return mPendingMO; 397 }
mCi is the instance of RIL, and mCi is the CommandsInterface type, which is obtained in the construction of GsmCdmaPhone
public static void makeDefaultPhone(Context context) { synchronized (sLockProxyPhones) { ...... sCommandsInterfaces[i] = new RIL(context, networkModes[i], cdmaSubscription, i); ...... phone = new GsmCdmaPhone(context, sCommandsInterfaces[i], sPhoneNotifier, i, PhoneConstants.PHONE_TYPE_CDMA_LTE, TelephonyComponentFactory.getInstance()); ...... }
@Override public void dial(String address, boolean isEmergencyCall, EmergencyNumber emergencyNumberInfo, boolean hasKnownUserIntentEmergency, int clirMode, UUSInfo uusInfo, Message result) { riljLog("dial" + "> " + "isEmergencyCall " + isEmergencyCall + " emergencyNumberInfo: " + emergencyNumberInfo + " mRadioVersion " + mRadioVersion); if (isEmergencyCall && mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4) && emergencyNumberInfo != null) { emergencyDial(address, emergencyNumberInfo, hasKnownUserIntentEmergency, clirMode, uusInfo, result); return; } IRadio radioProxy = getRadioProxy(result); if (radioProxy != null) { RILRequest rr = obtainRequest(RIL_REQUEST_DIAL, result, mRILDefaultWorkSource); Dial dialInfo = new Dial(); dialInfo.address = convertNullToEmptyString(address); dialInfo.clir = clirMode; if (uusInfo != null) { UusInfo info = new UusInfo(); info.uusType = uusInfo.getType(); info.uusDcs = uusInfo.getDcs(); info.uusData = new String(uusInfo.getUserData()); dialInfo.uusInfo.add(info); } if (RILJ_LOGD) { // Do not log function arg for privacy riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)); } try { radioProxy.dial(rr.mSerial, dialInfo); } catch (RemoteException | RuntimeException e) { handleRadioProxyExceptionForRR(rr, "dial", e); } } }
Bottom communication process between RIL and call module
RIL communication is mainly composed of RILSender and RILReceiver. The carrier used for communication transmission is RILRequest,
Registrant(RegistrantList).
- RILSender
Is a Handler that sends the request to the mSenderThread thread through #send(RILRequest), and handleMessage writes the request to the mSocket. - RILReceiver
Unlimited polling in run. Once the data returned by the bottom layer of the call is read, it is handed over to #processResponse(Parcel) for processing.
The response is divided into response with request and response without request (i.e. feedback after state change)
1.RIL_REQUEST_xxxx has a request
2.RIL_UNSOL_xxxx no request - RILRequest
Member variables:
1.mSerial request serial number, unique, to ensure the consistency of request and feedback.
2.mRequest request type, i.e. RIL_xxxx.
3. The handle used by mresult to send the result of the request is provided by the caller of RIL request. - Registrant
A series of registrantlists (collections of registrants) are maintained in RIL. Each collection represents a type of state change. For the response with request, RIL sends the result through the corresponding mResult, but for the response without request, RIL tells the corresponding RegistrantList (#notifyRegistrants) of the feedback notification, and RegistrantList will notify each Registrant. The direct parent class of RIL defines these registrantlists and provides methods to register to RegistrantList (eg.#registerForCallStateChanged, #unregisterForCallStateChanged). Therefore, if you want to monitor the change of call state, Then you need to register and listen to the corresponding RegistrantList (to listen, you need to provide a handler and an int and object. The handler is used to send data. Int distinguishes the monitored events, and the object is additional information).