Android 10 dialing process sorting

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.


Link:
 

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).

  1. RILSender
    Is a Handler that sends the request to the mSenderThread thread through #send(RILRequest), and handleMessage writes the request to the mSocket.
  2. 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
  3. 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.
  4. 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).

 

 

Tags: Java Android

Posted by Gulsaes on Fri, 15 Apr 2022 05:33:45 +0930