This paper mainly analyses the process of refusing to answer Android calls. Let's first look at the sequence diagram of the process of refusing to answer Android calls.
Step 1: Slide the button to the rejection icon and call the onDecline method of Answer Fragment
com.android.incallui.AnswerFragmentpublic void onDecline(Context context) { getPresenter().onDecline(context); }
Finally, call the AnswerPresenteronDecline method
com.android.incallui.AnswerPresenter public void onDecline(Context context) { Log.d(this, "onDecline " + mCallId); if (mCall.getSessionModificationState() == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { InCallPresenter.getInstance().declineUpgradeRequest(context); } else { TelecomAdapter.getInstance().rejectCall(mCall.getId(), false, null); } }
Step 2: Enter the rejectCall method of Telecom Adapter
com.android.incallui.TelecomAdapter void rejectCall(String callId, boolean rejectWithMessage, String message) { android.telecom.Call call = getTelecomCallById(callId); if (call != null) { call.reject(rejectWithMessage, message); } else { Log.e(this, "error rejectCall, call not in call list: " + callId); } }
Telecom Adapter is the proxy class for incallui to communicate with telecom, where the corresponding Call object (android.telecom.Call) is extracted by callid.
Step 3: Call the reject method to Call in the framework
android.telecom.Call public void reject(boolean rejectWithMessage, String textMessage) { mInCallAdapter.rejectCall(mTelecomCallId, rejectWithMessage, textMessage); }
Here mInCallAdapter is the android.telecom.InCallAdapter class, which is the parameter passed in from outside when the Call object is created.
When telecom binds InCallService service, an AIDL interface object is passed, and InCallService generates an InCallAdapter object to save the interface object.
Step 4:InCallAdapter's rejectCall method
android.telecom.InCallAdapter
public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) { try { mAdapter.rejectCall(callId, rejectWithMessage, textMessage); } catch (RemoteException e) { } }
mAdapter is the AIDL interface between incallui and telecom
Step 5: Cross-process calls enter the telecom process. The AIDL interface implementation class is InCallAdapter. Although the class names are the same, different package names need to be noted here.
com.android.server.telecom.InCallAdapter public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) { try { Log.startSession("ICA.rC", mOwnerComponentName); long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { Log.d(this, "rejectCall(%s,%b,%s)", callId, rejectWithMessage, textMessage); Call call = mCallIdMapper.getCall(callId); if (call != null) { mCallsManager.rejectCall(call, rejectWithMessage, textMessage); } else { Log.w(this, "setRingback, unknown call id: %s", callId); } } } finally { Binder.restoreCallingIdentity(token); } } finally { Log.endSession(); } }
Here, the corresponding Call (com.android.server.telecom.Call) is also taken out according to Calld, and the rejectCall method of CallsManager is finally called into the call.
Step 6: CallsManager's rejectCall method
com.android.server.telecom.CallsManager public void rejectCall(Call call, boolean rejectWithMessage, String textMessage) { if (!mCalls.contains(call)) { Log.i(this, "Request to reject a non-existent call %s", call); } else { for (CallsManagerListener listener : mListeners) { listener.onIncomingCallRejected(call, rejectWithMessage, textMessage); } call.reject(rejectWithMessage, textMessage); } }
Here we first notify the observer of a call rejection event, such as CallAudio Manager, which is interested in the event, and its processing is to stop playing caller ringtones and caller waiting tones.
com.android.server.telecom.CallAudioManager public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) { maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call); } private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) { // Check to see if the call being answered/rejected is the only ringing call, since this // will be called before the connection service acknowledges the state change. if (mRingingCalls.size() == 0 || (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) { mRinger.stopRinging(); mRinger.stopCallWaiting(); } }
Finally, the reject method of the call object passed in before is invoked.
Step 7: Call's reject method
com.android.server.telecom.Call public void reject(boolean rejectWithMessage, String textMessage) { Preconditions.checkNotNull(mConnectionService); // Check to verify that the call is still in the ringing state. A call can change states // between the time the user hits 'reject' and Telecomm receives the command. if (isRinging("reject")) { // Ensure video state history tracks video state at time of rejection. mVideoStateHistory |= mVideoState; mConnectionService.reject(this, rejectWithMessage, textMessage); Log.event(this, Log.Events.REQUEST_REJECT); } }
Here mConnectionService is the ConnectionService Wrapper class and the proxy class for telecom and telephony communication.
Step 8: The reject method of Connection Service Wrapper
com.android.server.telecom.ConnectionServiceWrapper void reject(Call call, boolean rejectWithMessage, String message) { final String callId = mCallIdMapper.getCallId(call); if (callId != null && isServiceValid("reject")) { try { logOutgoing("reject %s", callId); if (rejectWithMessage && call.can( Connection.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION)) { mServiceInterface.rejectWithMessage(callId, message); } else { mServiceInterface.reject(callId); } } catch (RemoteException e) { } } }
Here mService Interface is the IDL interface telephony provides to telecom calls
Step 9: Cross-process calls enter the telephony process. The actual service class of the telephony process is the TelephonyConnectionService inherited from the ConnectionService class. The manifest declaration is as follows:
<service android:singleUser="true" android:name="com.android.services.telephony.TelephonyConnectionService" android:label="@string/pstn_connection_service_label" android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" > <intent-filter> <action android:name="android.telecom.ConnectionService" /> </intent-filter> </service>
The implementation of AIDL interface is the mBinder member variable of its parent ConnectionService.
android.telecom.ConnectionService private final IBinder mBinder = new IConnectionService.Stub() { @Override public void reject(String callId) { mHandler.obtainMessage(MSG_REJECT, callId).sendToTarget(); } }
Step 10-13: Send the MSG_REJECT message to the queue for processing
private void reject(String callId) { Log.d(this, "reject %s", callId); findConnectionForAction(callId, "reject").onReject(); } private Connection findConnectionForAction(String callId, String action) { if (mConnectionById.containsKey(callId)) { return mConnectionById.get(callId); } Log.w(this, "%s - Cannot find Connection %s", action, callId); return getNullConnection(); }
Find the corresponding connection object (android.telecom.Connection) according to callid and call the onReject method
Step 14: Telephone Connection inherits from connection
com.android.services.telephony.TelephonyConnection public void onReject() { Log.v(this, "onReject"); if (isValidRingingCall()) { hangup(android.telephony.DisconnectCause.INCOMING_REJECTED); } super.onReject(); }
protected void hangup(int telephonyDisconnectCode) { if (mOriginalConnection != null) { try { // Hanging up a ringing call requires that we invoke call.hangup() as opposed to // connection.hangup(). Without this change, the party originating the call will not // get sent to voicemail if the user opts to reject the call. if (isValidRingingCall()) { Call call = getCall(); if (call != null) { call.hangup(); } else { Log.w(this, "Attempting to hangup a connection without backing call."); } } else { // We still prefer to call connection.hangup() for non-ringing calls in order // to support hanging-up specific calls within a conference call. If we invoked // call.hangup() while in a conference, we would end up hanging up the entire // conference call instead of the specific connection. mOriginalConnection.hangup(); } } catch (CallStateException e) { Log.e(this, e, "Call to Connection.hangup failed with exception"); } } }
Step 15, 16: This takes the call (com.android.internal.telephony.Call) object of mOriginalConnection and calls the hangup method
protected Call getCall() { if (mOriginalConnection != null) { return mOriginalConnection.getCall(); } return null; }
Call is an abstract class, and the concrete subclass is GsmCdmaCall.
com.android.internal.telephony.GsmCdmaCall public void hangup() throws CallStateException { mOwner.hangup(this); }
mOwner is the GsmCdmaCallTracker object
Step 17: The hangup method of GsmCdmaCallTracker
com.android.internal.telephony.GsmCdmaCallTracker public void hangup(GsmCdmaCall call) throws CallStateException { if (call.getConnections().size() == 0) { throw new CallStateException("no connections in call"); } if (call == mRingingCall) { if (Phone.DEBUG_PHONE) log("(ringing) hangup waiting or background"); mCi.hangupWaitingOrBackground(obtainCompleteMessage()); } else if (call == mForegroundCall) { if (call.isDialingOrAlerting()) { if (Phone.DEBUG_PHONE) { log("(foregnd) hangup dialing or alerting..."); } hangup((GsmCdmaConnection)(call.getConnections().get(0))); } else if (isPhoneTypeGsm() && mRingingCall.isRinging()) { // Do not auto-answer ringing on CHUP, instead just end active calls log("hangup all conns in active/background call, without affecting ringing call"); hangupAllConnections(call); } else { hangupForegroundResumeBackground(); } } else if (call == mBackgroundCall) { if (mRingingCall.isRinging()) { if (Phone.DEBUG_PHONE) { log("hangup all conns in background call"); } hangupAllConnections(call); } else { hangupWaitingOrBackground(); } } else { throw new RuntimeException ("GsmCdmaCall " + call + "does not belong to GsmCdmaCallTracker " + this); } call.onHangupLocal(); mPhone.notifyPreciseCallStateChanged(); }
Because it is ringcall, mCi. hangupWaiting OrBackground (obtainCompleteMessage ()) is called here.
mCi is a CommandsInterface, or RILJ interface. It packages an EVENT_OPERATION_COMPLETE callback message and sends it to RIL.
Step 18: RIL's hangup Waiting OrBackground method
com.android.internal.telephony.RILhangupWaitingOrBackground (Message result) { RILRequest rr = RILRequest.obtain(RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, result); if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)); mEventLog.writeRilHangup(rr.mSerial, RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, -1); send(rr); }
Send RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND message to RIL layer
Step 19: mPhone.notifyPreciseCallStateChanged notifies Phone of status monitoring events
Step 20-24: Receive the RIL layer response message and process it. Finally send the callback message EVENT_OPERATION_COMPLETE to GsmCdma CallTracker
Step 25: GsmCdmaCallTracker processes the callback message EVENT_OPERATION_COMPLETE
com.android.internal.telephony.GsmCdmaCallTracker private void operationComplete() { mPendingOperations--; if (DBG_POLL) log("operationComplete: pendingOperations=" + mPendingOperations + ", needsPoll=" + mNeedsPoll); if (mPendingOperations == 0 && mNeedsPoll) { mLastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT); mCi.getCurrentCalls(mLastRelevantPoll); } else if (mPendingOperations < 0) { // this should never happen Rlog.e(LOG_TAG,"GsmCdmaCallTracker.pendingOperations < 0"); mPendingOperations = 0; } }
Again, a message is sent to RIL to actively retrieve the current Call status. The wrapped callback message is EVENT_POLL_CALLS_RESULT.
Step 26-32: RIL returns the message, and GsmCdmaCallTracker receives the EVENT_POLL_CALLS_RESULT message and processes it
protected synchronized void handlePollCalls(AsyncResult ar) { ................... for (int i = mDroppedDuringPoll.size() - 1; i >= 0 ; i--) { GsmCdmaConnection conn = mDroppedDuringPoll.get(i); //CDMA boolean wasDisconnected = false; if (conn.isIncoming() && conn.getConnectTime() == 0) { // Missed or rejected call int cause; if (conn.mCause == DisconnectCause.LOCAL) { cause = DisconnectCause.INCOMING_REJECTED; } else { cause = DisconnectCause.INCOMING_MISSED; } if (Phone.DEBUG_PHONE) { log("missed/rejected call, conn.cause=" + conn.mCause); log("setting cause to " + cause); } mDroppedDuringPoll.remove(i); hasAnyCallDisconnected |= conn.onDisconnect(cause); wasDisconnected = true; } else if (conn.mCause == DisconnectCause.LOCAL || conn.mCause == DisconnectCause.INVALID_NUMBER) { mDroppedDuringPoll.remove(i); hasAnyCallDisconnected |= conn.onDisconnect(conn.mCause); wasDisconnected = true; } if (!isPhoneTypeGsm() && wasDisconnected && unknownConnectionAppeared && conn == newUnknownConnectionCdma) { unknownConnectionAppeared = false; newUnknownConnectionCdma = null; } ................... ................... updatePhoneState(); if (unknownConnectionAppeared) { if (isPhoneTypeGsm()) { for (Connection c : newUnknownConnectionsGsm) { log("Notify unknown for " + c); mPhone.notifyUnknownConnection(c); } } else { mPhone.notifyUnknownConnection(newUnknownConnectionCdma); } } if (hasNonHangupStateChanged || newRinging != null || hasAnyCallDisconnected) { mPhone.notifyPreciseCallStateChanged(); } }
Here, Disconnect Cause. INCOMING_REJECTED is set as the cause of disconnection and the onDisconnect method of GsmCdma Connection is invoked.
Step 33: On Disconnect Method for GsmCdma Connection
com.android.internal.telephony.GsmCdmaConnection public boolean onDisconnect(int cause) { boolean changed = false; mCause = cause; if (!mDisconnected) { doDisconnect(); if (DBG) Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause); mOwner.getPhone().notifyDisconnect(this); if (mParent != null) { changed = mParent.connectionDisconnected(this); } mOrigConnection = null; } clearPostDialListeners(); releaseWakeLock(); return changed; }
doDisconnect Method Sets Disconnect Time and Call Time
private void doDisconnect() { mIndex = -1; mDisconnectTime = System.currentTimeMillis(); mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal; mDisconnected = true; clearPostDialListeners(); }
Finally, notify the registrant to disconnect the event mOwner. getPhone (). notify Disconnect (this);
Step 34, 36: Notify the phone status change event to the relevant listener
Step 35: GsmCdmaPhonme notifies the call disconnection event
com.android.internal.telephony.GsmCdmaPhone public void notifyDisconnect(Connection cn) { mDisconnectRegistrants.notifyResult(cn); mNotifier.notifyDisconnectCause(cn.getDisconnectCause(), cn.getPreciseDisconnectCause()); }
Step 37-40: Telephone Connection registers disconnect event listeners, receives and processes disconnect messages
com.android.services.telephony.TelephonyConnection void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) { Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); ...... getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null); ...... }
void updateState() { if (mOriginalConnection == null) { return; } updateStateInternal(); updateStatusHints(); updateConnectionCapabilities(); updateConnectionProperties(); updateAddress(); updateMultiparty(); }
void updateStateInternal() { if (mOriginalConnection == null) { return; } Call.State newState; // If the state is overridden and the state of the original connection hasn't changed since, // then we continue in the overridden state, else we go to the original connection's state. if (mIsStateOverridden && mOriginalConnectionState == mOriginalConnection.getState()) { newState = mConnectionOverriddenState; } else { newState = mOriginalConnection.getState(); } Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState, this); if (mConnectionState != newState) { mConnectionState = newState; switch (newState) { case IDLE: break; case ACTIVE: setActiveInternal(); break; case HOLDING: setOnHold(); break; case DIALING: case ALERTING: setDialing(); break; case INCOMING: case WAITING: setRinging(); break; case DISCONNECTED: setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( mOriginalConnection.getDisconnectCause(), mOriginalConnection.getVendorDisconnectCause())); close(); break; case DISCONNECTING: break; } } }
Disconnect Cause (android. telecom. Disconnect Cause) object is generated by the toTelecom Disconnect Cause method of Disconnect Cause Util.
Contains code, label, description, reason,toneToPlay information
Step 41, 42: Notify the external listener to disconnect the event mNotifier. notify Disconnect Cause
Step 43: Call the setDisconnected method of the parent Connection
public final void setDisconnected(DisconnectCause disconnectCause) { checkImmutable(); mDisconnectCause = disconnectCause; setState(STATE_DISCONNECTED); Log.d(this, "Disconnected with cause %s", disconnectCause); for (Listener l : mListeners) { l.onDisconnected(this, disconnectCause); } }
Callback notifies the observer that ConnectionService registered the event, and mConnectionListener receives processing
Step 44: mConnectionListener handles onDisconnected events
android.telecom.ConnectionService private final Connection.Listener mConnectionListener = new Connection.Listener() { ...... @Override public void onDisconnected(Connection c, DisconnectCause disconnectCause) { String id = mIdByConnection.get(c); Log.d(this, "Adapter set disconnected %s", disconnectCause); mAdapter.setDisconnected(id, disconnectCause); } }
Remove the corresponding callid from the connection object
Step 45: The updateAddress method of TelephonyConnection updates connection information
Step 46: setDisconnected method of Connection Service Adapter
android.telecom.ConnectionServiceAdapter void setDisconnected(String callId, DisconnectCause disconnectCause) { for (IConnectionServiceAdapter adapter : mAdapters) { try { adapter.setDisconnected(callId, disconnectCause); } catch (RemoteException e) { } } }
When telecom binds Telephone Connection Service, it sets the AIDL callback interface object to the Adapter member variable of Telephone, or Connection Service Wrapper.
Step 47: Call to the telecom process across processes, and the Adapter of Connection Service Wrapper handles setDisconnected
com.android.server.telecom.ConnectionServiceWrapper private final class Adapter extends IConnectionServiceAdapter.Stub { ...... @Override public void setDisconnected(String callId, DisconnectCause disconnectCause) { Log.startSession("CSW.sD"); long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { logIncoming("setDisconnected %s %s", callId, disconnectCause); Call call = mCallIdMapper.getCall(callId); Log.d(this, "disconnect call %s %s", disconnectCause, call); if (call != null) { mCallsManager.markCallAsDisconnected(call, disconnectCause); } else { // Log.w(this, "setDisconnected, unknown call id: %s", args.arg1); } } } finally { Binder.restoreCallingIdentity(token); Log.endSession(); } } ...... }
Remove Call (com.android.server.telecom.Call) object according to Calld and pass Call and disconnectCause to CallsManager
Step 48, 49, 50: CallsManager's markCallAsDisconnected method
com.android.server.telecom.CallsManager void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) { call.setDisconnectCause(disconnectCause); setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly"); }
Set disconnectCause for Call and call state at the same time
private void setCallState(Call call, int newState, String tag) { if (call == null) { return; } int oldState = call.getState(); Log.i(this, "setCallState %s -> %s, call: %s", CallState.toString(oldState), CallState.toString(newState), call); if (newState != oldState) { // Unfortunately, in the telephony world the radio is king. So if the call notifies // us that the call is in a particular state, we allow it even if it doesn't make // sense (e.g., STATE_ACTIVE -> STATE_RINGING). // TODO: Consider putting a stop to the above and turning CallState // into a well-defined state machine. // TODO: Define expected state transitions here, and log when an // unexpected transition occurs. call.setState(newState, tag); maybeShowErrorDialogOnDisconnect(call); Trace.beginSection("onCallStateChanged"); // Only broadcast state change for calls that are being tracked. if (mCalls.contains(call)) { updateCallsManagerState(); for (CallsManagerListener listener : mListeners) { if (Log.SYSTRACE_DEBUG) { Trace.beginSection(listener.getClass().toString() + " onCallStateChanged"); } listener.onCallStateChanged(call, oldState, newState); if (Log.SYSTRACE_DEBUG) { Trace.endSection(); } } } Trace.endSection(); } }
Finally, the callback onCallStateChanged method notifies the listener that there are many objects listening for call state changes. Let's look at InCallController's processing.
Step 51,52: onCallStateChanged method of InCallController
com.android.server.telecom.InCallController @Override public void onCallStateChanged(Call call, int oldState, int newState) { updateCall(call); }
private void updateCall(Call call, boolean videoProviderChanged) { if (!mInCallServices.isEmpty()) { ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall( call, videoProviderChanged /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar()); Log.i(this, "Sending updateCall %s ==> %s", call, parcelableCall); List<ComponentName> componentsUpdated = new ArrayList<>(); for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) { ComponentName componentName = entry.getKey(); IInCallService inCallService = entry.getValue(); componentsUpdated.add(componentName); try { inCallService.updateCall(parcelableCall); } catch (RemoteException ignored) { } } Log.i(this, "Components updated: %s", componentsUpdated); } } }
Generate ParcelableCall object based on call information and pass ParcelableCall to incallservice
updateCall Method of Step 53,54:InCallService
android.telecom.InCallService @Override public void updateCall(ParcelableCall call) { mHandler.obtainMessage(MSG_UPDATE_CALL, call).sendToTarget(); }
private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { ...... case MSG_UPDATE_CALL: mPhone.internalUpdateCall((ParcelableCall) msg.obj); break; }
Step 55: Phone's Internal Update Call method
android.telecom.Phone final void internalUpdateCall(ParcelableCall parcelableCall) { Call call = mCallByTelecomCallId.get(parcelableCall.getId()); if (call != null) { checkCallTree(parcelableCall); call.internalUpdate(parcelableCall, mCallByTelecomCallId); } }
The Phone object here is just a management class, which stores the call list information and the AIDL interface object to communicate with telecom, and removes the Call (android.telecom.Call) object through callid.
Step 56: Call's Internal Update Method
android.telecom.Call final void internalUpdate(ParcelableCall parcelableCall, Map<String, Call> callIdMap) { Details details = Details.createFromParcelableCall(parcelableCall); ...... ...... // Now we fire updates, ensuring that any client who listens to any of these notifications // gets the most up-to-date state. if (stateChanged) { fireStateChanged(mState); } if (detailsChanged) { fireDetailsChanged(mDetails); } if (cannedTextResponsesChanged) { fireCannedTextResponsesLoaded(mCannedTextResponses); } if (videoCallChanged) { fireVideoCallChanged(mVideoCallImpl); } if (parentChanged) { fireParentChanged(getParent()); } if (childrenChanged) { fireChildrenChanged(getChildren()); } // If we have transitioned to DISCONNECTED, that means we need to notify clients and // remove ourselves from the Phone. Note that we do this after completing all state updates // so a client can cleanly transition all their UI to the state appropriate for a // DISCONNECTED Call while still relying on the existence of that Call in the Phone's list. if (mState == STATE_DISCONNECTED) { fireCallDestroyed(); } }
Step 57: Convert Parcelable Call information to Detail information to determine if the call status has changed, or enter fireStateChanged
private void fireStateChanged(final int newState) { for (CallbackRecord<Callback> record : mCallbackRecords) { final Call call = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onStateChanged(call, newState); } }); } }
Step 58: Here traverse the callback listener in the Call (android.telecom.Call) object
private final List<CallbackRecord<Callback>> mCallbackRecords = new CopyOnWriteArrayList<>();
That is to say, every time InCallPresenter adds Call (android.telecom.Call), it adds a registration callback event.
com.android.incallui.InCallPresenter public void onCallAdded(final android.telecom.Call call) { if (shouldAttemptBlocking(call)) { maybeBlockCall(call); } else { mCallList.onCallAdded(call); } // Since a call has been added we are no longer waiting for Telecom to send us a call. setBoundAndWaitingForOutgoingCall(false, null); call.registerCallback(mCallCallback); }
There are two places where event callbacks are registered. One is registered when the Call (com.android.incallui.Call) object is created by transforming the Call (android.telecom.Call) in the onCallAdd method of CallList.
com.android.incallui.CallList public void onCallAdded(final android.telecom.Call telecomCall) { Trace.beginSection("onCallAdded"); final Call call = new Call(telecomCall); Log.d(this, "onCallAdded: callState=" + call.getState()); if (call.getState() == Call.State.INCOMING || call.getState() == Call.State.CALL_WAITING) { onIncoming(call, call.getCannedSmsResponses()); } else { onUpdate(call); } call.logCallInitiationType(); Trace.endSection(); }
com.android.incallui.Call public Call(android.telecom.Call telecomCall) { mTelecomCall = telecomCall; mId = ID_PREFIX + Integer.toString(sIdCounter++); updateFromTelecomCall(); mTelecomCall.registerCallback(mTelecomCallCallback); mTimeAddedMs = System.currentTimeMillis(); }
There is also the registration of the member variable mCallCallback of InCallPresenter
Here onStateChange only handles the member variable mTelecomCallback of Call (com.android.incallui.Call)
com.android.incallui.Call private final android.telecom.Call.Callback mTelecomCallCallback = new android.telecom.Call.Callback() { ...... ...... @Override public void onStateChanged(android.telecom.Call call, int newState) { Log.d(this, "TelecomCallCallback onStateChanged call=" + call + " newState=" + newState); update(); } @Override public void onCallDestroyed(android.telecom.Call call) { Log.d(this, "TelecomCallCallback onStateChanged call=" + call); call.unregisterCallback(this); } };
Step 59: update method into Call (com.android.incallui.Call)
private void update() { Trace.beginSection("Update"); int oldState = getState(); updateFromTelecomCall(); if (oldState != getState() && getState() == Call.State.DISCONNECTED) { CallList.getInstance().onDisconnect(this); } else { CallList.getInstance().onUpdate(this); } Trace.endSection(); }
Step 61-69: Since it is in the DISCONNECTED state, it enters the onDisconnect of CallList and calls back to the onDisconnect method of InCallPresenter.
com.android.incallui.InCallPresenter @Override public void onDisconnect(Call call) { maybeShowErrorDialogOnDisconnect(call); // We need to do the run the same code as onCallListChange. onCallListChange(mCallList); if (isActivityStarted()) { mInCallActivity.dismissKeyguard(false); } if (call.isEmergencyCall()) { FilteredNumbersUtil.recordLastEmergencyCallTime(mContext); } }
InCallPresenter updates call status events internally and triggers callback notifications, and details are not listed again.
Callback step 57, after executing the fireStateChanged method, is followed by fireDetails Changed and other events (if any), so let's focus on fireCallDestroyed.
android.telecom.Call private void fireCallDestroyed() { final Call call = this; if (mCallbackRecords.isEmpty()) { // No callbacks registered, remove the call from Phone's record. mPhone.internalRemoveCall(call); } for (final CallbackRecord<Callback> record : mCallbackRecords) { final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { boolean isFinalRemoval = false; RuntimeException toThrow = null; try { callback.onCallDestroyed(call); } catch (RuntimeException e) { toThrow = e; } synchronized(Call.this) { mCallbackRecords.remove(record); if (mCallbackRecords.isEmpty()) { isFinalRemoval = true; } } if (isFinalRemoval) { mPhone.internalRemoveCall(call); } if (toThrow != null) { throw toThrow; } } }); } }
Here callback.onCallDestroyed(call); notify the call destruction event
Step 60: Anti-registration of Call (android.telecom.Call) listening events
com.android.incallui.Call @Override public void onCallDestroyed(android.telecom.Call call) { Log.d(this, "TelecomCallCallback onStateChanged call=" + call); call.unregisterCallback(this); }
Step 63, 64: mPhone.internalRemoveCall(call); remove the Call (android.telecom.Call) object from the list and notify onCallRemoved event
android.telecom.Phone final void internalRemoveCall(Call call) { mCallByTelecomCallId.remove(call.internalGetCallId()); mCalls.remove(call); InCallService.VideoCall videoCall = call.getVideoCall(); if (videoCall != null) { videoCall.destroy(); } fireCallRemoved(call); }
private void fireCallRemoved(Call call) { for (Listener listener : mListeners) { listener.onCallRemoved(this, call); } }
Step 70,71: InCallService handles onCallRemoved events
com.android.incallui.InCallServiceImpl @Override public void onCallAdded(Call call) { InCallPresenter.getInstance().onCallAdded(call); }
Step 72: Enter the onCallRemoved method of InCallPresenter
com.android.incallui.InCallPresenter public void onCallRemoved(android.telecom.Call call) { mCallList.onCallRemoved(call); call.unregisterCallback(mCallCallback); }
Step 73-78: The onCallRemoved method of CallList:
com.android.incallui.CallList public void onCallRemoved(android.telecom.Call telecomCall) { if (mCallByTelecomCall.containsKey(telecomCall)) { Call call = mCallByTelecomCall.get(telecomCall); Logger.logCall(call); if (updateCallInMap(call)) { Log.w(this, "Removing call not previously disconnected " + call.getId()); } updateCallTextMap(call, null); } }
The onCallListChange Method of CallList Internal Update State Final Callback to InCallPresenter
com.android.incallui.InCallPresenter public void onCallListChange(CallList callList) { if (mInCallActivity != null && mInCallActivity.getCallCardFragment() != null && mInCallActivity.getCallCardFragment().isAnimating()) { mAwaitingCallListUpdate = true; return; } if (callList == null) { return; } mAwaitingCallListUpdate = false; InCallState newState = getPotentialStateFromCallList(callList); InCallState oldState = mInCallState; Log.d(this, "onCallListChange oldState= " + oldState + " newState=" + newState); newState = startOrFinishUi(newState); Log.d(this, "onCallListChange newState changed to " + newState); // Set the new state before announcing it to the world Log.i(this, "Phone switching state: " + oldState + " -> " + newState); mInCallState = newState; // notify listeners of new state for (InCallStateListener listener : mListeners) { Log.d(this, "Notify " + listener + " of state " + mInCallState.toString()); listener.onStateChange(oldState, mInCallState, callList); } if (isActivityStarted()) { final boolean hasCall = callList.getActiveOrBackgroundCall() != null || callList.getOutgoingCall() != null; mInCallActivity.dismissKeyguard(hasCall); } }
Step 79,80,81:InCallPresenter handles disconnected events and triggers related callbacks to update the interface, etc.
So far, the whole process of a call has been analyzed, and the general process is as follows:
InCallUI →TeleComService→TeleponyService→TelephonyFramework →RIL→
RIL→TelephonyFramework →TeleponyService→TeleComFramework→TeleComService→TeleComFramework-->InCallUI