Source code analysis of Android 7.0 phone application (3) phone rejection process analysis

Keywords: Android Fragment

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.AnswerFragment
public 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.RIL
hangupWaitingOrBackground (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

Posted by knnakai on Tue, 26 Mar 2019 09:30:29 -0700