Android 7.0 挂断电话流程分析

1、图形显示

这里写图片描述

挂断电话分为本地挂断和远程对方挂断

2、本地挂断
1)、点击按钮 

先看按键的监听事件 CallCardFragment.java 中有对按钮的监听事件


    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        mPulseAnimation =
                AnimationUtils.loadAnimation(view.getContext(), R.anim.call_status_pulse);

        mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber);
        mConnectedAddress = (TextView) view.findViewById(R.id.connectedAddress);
        mPrimaryName = (TextView) view.findViewById(R.id.name);
        mNumberLabel = (TextView) view.findViewById(R.id.label);
        mPrimarySimIndication = (TextView) view.findViewById(R.id.simIndication);
        mPrimaryLocation = (TextView) view.findViewById(R.id.location);
        mSecondaryCallInfo = view.findViewById(R.id.secondary_call_info);
        mSecondaryCallProviderInfo = view.findViewById(R.id.secondary_call_provider_info);
        mCallCardContent = view.findViewById(R.id.call_card_content);
        mPhotoLarge = (ImageView) view.findViewById(R.id.photoLarge);
        mPhotoLarge.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getPresenter().onContactPhotoClick();
            }
        });

        mContactContext = view.findViewById(R.id.contact_context);
        mContactContextTitle = (TextView) view.findViewById(R.id.contactContextTitle);
        mContactContextListView = (ListView) view.findViewById(R.id.contactContextInfo);
        // This layout stores all the list header layouts so they can be easily removed.
        mContactContextListHeaders = new LinearLayout(getView().getContext());
        mContactContextListView.addHeaderView(mContactContextListHeaders);

        mCallStateIcon = (ImageView) view.findViewById(R.id.callStateIcon);
        mCallStateVideoCallIcon = (ImageView) view.findViewById(R.id.videoCallIcon);
        mWorkProfileIcon = (ImageView) view.findViewById(R.id.workProfileIcon);
        mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel);
        mHdAudioIcon = (ImageView) view.findViewById(R.id.hdAudioIcon);
        mForwardIcon = (ImageView) view.findViewById(R.id.forwardIcon);
        mCallNumberAndLabel = view.findViewById(R.id.labelAndNumber);
        mCallLocationAndType = view.findViewById(R.id.locationAndType);
        mCallTypeLabel = (TextView) view.findViewById(R.id.callTypeLabel);
        mRecordingLabel = (TextView) view.findViewById(R.id.recordinglabel);
        mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime);
        mPrimaryCallCardContainer = view.findViewById(R.id.primary_call_info_container);
        mPrimaryCallInfo = (ViewGroup) view.findViewById(R.id.primary_call_banner);
        mCallButtonsContainer = view.findViewById(R.id.callButtonFragment);
        mPhotoSmall = (ImageView) view.findViewById(R.id.photoSmall);
        mPhotoSmall.setVisibility(View.GONE);
        mInCallMessageLabel = (TextView) view.findViewById(R.id.connectionServiceMessage);
        mProgressSpinner = view.findViewById(R.id.progressSpinner);

        mFloatingActionButtonContainer = view.findViewById(
                R.id.floating_end_call_action_button_container);
        mFloatingActionButton = (ImageButton) view.findViewById(
                R.id.floating_end_call_action_button);
        mFloatingActionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            //调用的是这一个函数
                getPresenter().endCallClicked();
            }
        });
        mFloatingActionButtonController = new FloatingActionButtonController(getActivity(),
                mFloatingActionButtonContainer, mFloatingActionButton);

        mSecondaryCallInfo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getPresenter().secondaryInfoClicked();
                updateFabPositionForSecondaryCallInfo();
            }
        });

        mCallStateButton = view.findViewById(R.id.callStateButton);
        mCallStateButton.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                getPresenter().onCallStateButtonTouched();
                return false;
            }
        });

        mManageConferenceCallButton = view.findViewById(R.id.manage_conference_call_button);
        mManageConferenceCallButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                InCallActivity activity = (InCallActivity) getActivity();
                activity.showConferenceFragment(true);
            }
        });

        mPrimaryName.setElegantTextHeight(false);
        mCallStateLabel.setElegantTextHeight(false);
        mCallSubject = (TextView) view.findViewById(R.id.callSubject);
    }
2)、CallCardPresenter.java的endCallClicked()方法。

    /**
     * endCallClicked.
     */
    public void endCallClicked() {
        if (mPrimary == null) {
            Log.i(TAG, "(mPrimary == null)");
            return;
        }
        Log.i(TAG, "Disconnecting call: " + mPrimary);
        Log.i(TAG, "mPrimary.getDisconnectCause().getCode(): " 
            + mPrimary.getDisconnectCause().getCode());
        if (mPrimary.getDisconnectCause().getCode() != DisconnectCause.BUSY) {
            final String callId = mPrimary.getId();
            mPrimary.setState(Call.State.DISCONNECTING);
            CallList.getInstance().onUpdate(mPrimary);
            TelecomAdapter.getInstance().disconnectCall(callId);
        } else {
            mPrimary.setState(Call.State.DISCONNECTED);
            CallList.getInstance().onDisconnect(mPrimary);
            //if has held call, unhold call.
            /* if (mSecondary != null && mSecondary.getState() == Call.State.ONHOLD) {
                final String callId = mSecondary.getId();
                TelecomAdapter.getInstance().unholdCall(callId);
            }*/
        }
    }

这里先把call的状态设置成DISCONNECTING,然后通过CallList更新UI界面,最后继续挂断流程。

3)、TelecomAdapter.java文件的disconnectCall()方法

    void disconnectCall(String callId) {
        android.telecom.Call call = getTelecomCallById(callId);
        if (call != null) {
        //执行这一句
            call.disconnect();
        } else {
            Log.e(TAG, "error disconnectCall, call not in call list " + callId);
        }
    }

通过Callid找到对应的Call对象(android.telephone.Call)

4)、调用到framework里call.java的disconnect()方法
    /**
     * Instructs this {@code Call} to disconnect.
     */
    public void disconnect() {
        mInCallAdapter.disconnectCall(mTelecomCallId);
    }
5)、调用到framework里InCallAdapter.java的disconnectCall()方法
    /**
     * Instructs Telecom to disconnect the specified call.
     *
     * @param callId The identifier of the call to disconnect.
     */
    public void disconnectCall(String callId) {
        try {
            mAdapter.disconnectCall(callId);
        } catch (RemoteException e) {
        }
    }

mAdapter就是incallui与telecom通信的AIDL接口
跨进程调用,进入telecom进程,该AIDL接口具体实现类是InCallAdapter,相同的类名不一样的包名。

6)、调用到packages里InCallAdapter.java的disconnectCall()方法
    @Override
    public void disconnectCall(String callId) {
        try {
            Log.startSession("ICA.dC", mOwnerComponentName);
            long token = Binder.clearCallingIdentity();
            try {
                synchronized (mLock) {
                    Log.v(this, "disconnectCall: %s", callId);
                    Call call = mCallIdMapper.getCall(callId);
                    if (call != null) {
      //调用到这句话                  mCallsManager.disconnectCall(call);
                    } else {
                        Log.w(this, "disconnectCall, unknown call id: %s", callId);
                    }
                }
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        } finally {
            Log.endSession();
        }
    }

同样是根据callid取出对应的 Call(com.android.server.telecom.Call),最后调用CallsManager的disconnectCall方法传入call

7)、CallsManager.java文件的diaconnectCall()方法
    /**
     * Instructs Telecom to disconnect the specified call. Intended to be invoked by the
     * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
     * the user hitting the end-call button.
     */
    @VisibleForTesting
    public void disconnectCall(Call call) {
        Log.v(this, "disconnectCall %s", call);

        if (!mCalls.contains(call)) {
            Log.w(this, "Unknown call (%s) asked to disconnect", call);
        } else {
            mLocallyDisconnectingCalls.add(call);
            call.disconnect();
        }
    }

将该call对象加入本地断开call列表,之后进入Call的disconnect方法。

8)、call.java的disconnect()方法
    @VisibleForTesting
    public void disconnect() {
        disconnect(false);
    }

继续调用

    /**
     * Attempts to disconnect the call through the connection service.
     */
    @VisibleForTesting
    public void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
        Log.event(this, Log.Events.REQUEST_DISCONNECT);

        // Track that the call is now locally disconnecting.
        setLocallyDisconnecting(true);

        if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
                mState == CallState.CONNECTING) {
            Log.v(this, "Aborting call %s", this);
            abort(wasViaNewOutgoingCallBroadcaster);
        } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
            if (mConnectionService == null) {
                Log.e(this, new Exception(), "disconnect() request on a call without a"
                        + " connection service.");
            } else {
                Log.i(this, "Send disconnect to connection service for call: %s", this);
                // The call isn't officially disconnected until the connection service
                // confirms that the call was actually disconnected. Only then is the
                // association between call and connection service severed, see
                // {@link CallsManager#markCallAsDisconnected}.
                //调用这个函数
                mConnectionService.disconnect(this);
            }
        }
    }

setLocallyDisconnecting(true); 先设置本地挂断标志为true
由于mState这个时候是CallState.ACTIVE状态,进入mConnectionService的disconnect方法。
这里的mConnectionService是ConnectionServiceWrapper类,是telecom与telephony通信的代理类

9)、ConnectionServiceWrapper.java的disconnect()方法
    /** @see IConnectionService#disconnect(String) */
    void disconnect(Call call) {
        final String callId = mCallIdMapper.getCallId(call);
        if (callId != null && isServiceValid("disconnect")) {
            try {
                logOutgoing("disconnect %s", callId);
                mServiceInterface.disconnect(callId);
            } catch (RemoteException e) {
            }
        }
    }

这里的mServiceInterface就是telephony提供给telecom调用的AIDL接口
跨进程进入telephony,AIDL接口具体实现是其父类ConnectionService的mBinder成员变量

10)、ConnectionService.java文件

/**
 * An abstract service that should be implemented by any apps which can make phone calls (VoIP or
 * otherwise) and want those calls to be integrated into the built-in phone app.
 * Once implemented, the {@code ConnectionService} needs two additional steps before it will be
 * integrated into the phone app:
 * <p>
 * 1. <i>Registration in AndroidManifest.xml</i>
 * <br/>
 * <pre>
 * &lt;service android:name="com.example.package.MyConnectionService"
 *    android:label="@string/some_label_for_my_connection_service"
 *    android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"&gt;
 *  &lt;intent-filter&gt;
 *   &lt;action android:name="android.telecom.ConnectionService" /&gt;
 *  &lt;/intent-filter&gt;
 * &lt;/service&gt;
 * </pre>
 * <p>
 * 2. <i> Registration of {@link PhoneAccount} with {@link TelecomManager}.</i>
 * <br/>
 * See {@link PhoneAccount} and {@link TelecomManager#registerPhoneAccount} for more information.
 * <p>
 * Once registered and enabled by the user in the phone app settings, telecom will bind to a
 * {@code ConnectionService} implementation when it wants that {@code ConnectionService} to place
 * a call or the service has indicated that is has an incoming call through
 * {@link TelecomManager#addNewIncomingCall}. The {@code ConnectionService} can then expect a call
 * to {@link #onCreateIncomingConnection} or {@link #onCreateOutgoingConnection} wherein it
 * should provide a new instance of a {@link Connection} object.  It is through this
 * {@link Connection} object that telecom receives state updates and the {@code ConnectionService}
 * receives call-commands such as answer, reject, hold and disconnect.
 * <p>
 * When there are no more live calls, telecom will unbind from the {@code ConnectionService}.
 */
public abstract class ConnectionService extends Service {
    /**
     * The {@link Intent} that must be declared as handled by the service.
     */
    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
    public static final String SERVICE_INTERFACE = "android.telecom.ConnectionService";

    // Flag controlling whether PII is emitted into the logs
    private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);

    private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
    private static final int MSG_CREATE_CONNECTION = 2;
    private static final int MSG_ABORT = 3;
    private static final int MSG_ANSWER = 4;
    private static final int MSG_REJECT = 5;
    private static final int MSG_DISCONNECT = 6;
    private static final int MSG_HOLD = 7;
    private static final int MSG_UNHOLD = 8;
    private static final int MSG_ON_CALL_AUDIO_STATE_CHANGED = 9;
    private static final int MSG_PLAY_DTMF_TONE = 10;
    private static final int MSG_STOP_DTMF_TONE = 11;
    private static final int MSG_CONFERENCE = 12;
    private static final int MSG_SPLIT_FROM_CONFERENCE = 13;
    private static final int MSG_ON_POST_DIAL_CONTINUE = 14;
    private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16;
    private static final int MSG_ANSWER_VIDEO = 17;
    private static final int MSG_MERGE_CONFERENCE = 18;
    private static final int MSG_SWAP_CONFERENCE = 19;
    private static final int MSG_REJECT_WITH_MESSAGE = 20;
    private static final int MSG_SILENCE = 21;
    private static final int MSG_PULL_EXTERNAL_CALL = 22;
    private static final int MSG_SEND_CALL_EVENT = 23;
    private static final int MSG_ON_EXTRAS_CHANGED = 24;

    private static Connection sNullConnection;

    private final Map<String, Connection> mConnectionById = new ConcurrentHashMap<>();
    private final Map<Connection, String> mIdByConnection = new ConcurrentHashMap<>();
    private final Map<String, Conference> mConferenceById = new ConcurrentHashMap<>();
    private final Map<Conference, String> mIdByConference = new ConcurrentHashMap<>();
    private final RemoteConnectionManager mRemoteConnectionManager =
            new RemoteConnectionManager(this);
    private final List<Runnable> mPreInitializationConnectionRequests = new ArrayList<>();
    private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter();

    private boolean mAreAccountsInitialized = false;
    private Conference sNullConference;
    private Object mIdSyncRoot = new Object();
    private int mId = 0;

    private final IBinder mBinder = new IConnectionService.Stub() {
        @Override
        public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
            mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
        }

        public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
            mHandler.obtainMessage(MSG_REMOVE_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
        }

        @Override
        public void createConnection(
                PhoneAccountHandle connectionManagerPhoneAccount,
                String id,
                ConnectionRequest request,
                boolean isIncoming,
                boolean isUnknown) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = connectionManagerPhoneAccount;
            args.arg2 = id;
            args.arg3 = request;
            args.argi1 = isIncoming ? 1 : 0;
            args.argi2 = isUnknown ? 1 : 0;
            mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget();
        }

        @Override
        public void abort(String callId) {
            mHandler.obtainMessage(MSG_ABORT, callId).sendToTarget();
        }

        @Override
        public void answerVideo(String callId, int videoState) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = callId;
            args.argi1 = videoState;
            mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget();
        }

        @Override
        public void answer(String callId) {
            mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget();
        }

        @Override
        public void reject(String callId) {
            mHandler.obtainMessage(MSG_REJECT, callId).sendToTarget();
        }

        @Override
        public void rejectWithMessage(String callId, String message) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = callId;
            args.arg2 = message;
            mHandler.obtainMessage(MSG_REJECT_WITH_MESSAGE, args).sendToTarget();
        }

        @Override
        public void silence(String callId) {
            mHandler.obtainMessage(MSG_SILENCE, callId).sendToTarget();
        }
        //调用这个函数
        @Override
        public void disconnect(String callId) {
            mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget();
        }

        @Override
        public void hold(String callId) {
            mHandler.obtainMessage(MSG_HOLD, callId).sendToTarget();
        }

        @Override
        public void unhold(String callId) {
            mHandler.obtainMessage(MSG_UNHOLD, callId).sendToTarget();
        }

        @Override
        public void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = callId;
            args.arg2 = callAudioState;
            mHandler.obtainMessage(MSG_ON_CALL_AUDIO_STATE_CHANGED, args).sendToTarget();
        }

        @Override
        public void playDtmfTone(String callId, char digit) {
            mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, digit, 0, callId).sendToTarget();
        }

        @Override
        public void stopDtmfTone(String callId) {
            mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget();
        }

        @Override
        public void conference(String callId1, String callId2) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = callId1;
            args.arg2 = callId2;
            mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
        }

        @Override
        public void splitFromConference(String callId) {
            mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget();
        }

        @Override
        public void mergeConference(String callId) {
            mHandler.obtainMessage(MSG_MERGE_CONFERENCE, callId).sendToTarget();
        }

        @Override
        public void swapConference(String callId) {
            mHandler.obtainMessage(MSG_SWAP_CONFERENCE, callId).sendToTarget();
        }

        @Override
        public void onPostDialContinue(String callId, boolean proceed) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = callId;
            args.argi1 = proceed ? 1 : 0;
            mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget();
        }

        @Override
        public void pullExternalCall(String callId) {
            mHandler.obtainMessage(MSG_PULL_EXTERNAL_CALL, callId).sendToTarget();
        }

        @Override
        public void sendCallEvent(String callId, String event, Bundle extras) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = callId;
            args.arg2 = event;
            args.arg3 = extras;
            mHandler.obtainMessage(MSG_SEND_CALL_EVENT, args).sendToTarget();
        }

        @Override
        public void onExtrasChanged(String callId, Bundle extras) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = callId;
            args.arg2 = extras;
            mHandler.obtainMessage(MSG_ON_EXTRAS_CHANGED, args).sendToTarget();
        }
    };

    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_ADD_CONNECTION_SERVICE_ADAPTER:
                    mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj);
                    onAdapterAttached();
                    break;
                case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER:
                    mAdapter.removeAdapter((IConnectionServiceAdapter) msg.obj);
                    break;
                case MSG_CREATE_CONNECTION: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        final PhoneAccountHandle connectionManagerPhoneAccount =
                                (PhoneAccountHandle) args.arg1;
                        final String id = (String) args.arg2;
                        final ConnectionRequest request = (ConnectionRequest) args.arg3;
                        final boolean isIncoming = args.argi1 == 1;
                        final boolean isUnknown = args.argi2 == 1;
                        if (!mAreAccountsInitialized) {
                            Log.d(this, "Enqueueing pre-init request %s", id);
                            mPreInitializationConnectionRequests.add(new Runnable() {
                                @Override
                                public void run() {
                                    createConnection(
                                            connectionManagerPhoneAccount,
                                            id,
                                            request,
                                            isIncoming,
                                            isUnknown);
                                }
                            });
                        } else {
                            createConnection(
                                    connectionManagerPhoneAccount,
                                    id,
                                    request,
                                    isIncoming,
                                    isUnknown);
                        }
                    } finally {
                        args.recycle();
                    }
                    break;
                }
                case MSG_ABORT:
                    abort((String) msg.obj);
                    break;
                case MSG_ANSWER:
                    answer((String) msg.obj);
                    break;
                case MSG_ANSWER_VIDEO: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        String callId = (String) args.arg1;
                        int videoState = args.argi1;
                        answerVideo(callId, videoState);
                    } finally {
                        args.recycle();
                    }
                    break;
                }
                case MSG_REJECT:
                    reject((String) msg.obj);
                    break;
                case MSG_REJECT_WITH_MESSAGE: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        reject((String) args.arg1, (String) args.arg2);
                    } finally {
                        args.recycle();
                    }
                    break;
                }
                case MSG_DISCONNECT:
                    disconnect((String) msg.obj);
                    break;
                case MSG_SILENCE:
                    silence((String) msg.obj);
                    break;
                case MSG_HOLD:
                    hold((String) msg.obj);
                    break;
                case MSG_UNHOLD:
                    unhold((String) msg.obj);
                    break;
                case MSG_ON_CALL_AUDIO_STATE_CHANGED: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        String callId = (String) args.arg1;
                        CallAudioState audioState = (CallAudioState) args.arg2;
                        onCallAudioStateChanged(callId, new CallAudioState(audioState));
                    } finally {
                        args.recycle();
                    }
                    break;
                }
                case MSG_PLAY_DTMF_TONE:
                    playDtmfTone((String) msg.obj, (char) msg.arg1);
                    break;
                case MSG_STOP_DTMF_TONE:
                    stopDtmfTone((String) msg.obj);
                    break;
                case MSG_CONFERENCE: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        String callId1 = (String) args.arg1;
                        String callId2 = (String) args.arg2;
                        conference(callId1, callId2);
                    } finally {
                        args.recycle();
                    }
                    break;
                }
                case MSG_SPLIT_FROM_CONFERENCE:
                    splitFromConference((String) msg.obj);
                    break;
                case MSG_MERGE_CONFERENCE:
                    mergeConference((String) msg.obj);
                    break;
                case MSG_SWAP_CONFERENCE:
                    swapConference((String) msg.obj);
                    break;
                case MSG_ON_POST_DIAL_CONTINUE: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        String callId = (String) args.arg1;
                        boolean proceed = (args.argi1 == 1);
                        onPostDialContinue(callId, proceed);
                    } finally {
                        args.recycle();
                    }
                    break;
                }
                case MSG_PULL_EXTERNAL_CALL: {
                    pullExternalCall((String) msg.obj);
                    break;
                }
                case MSG_SEND_CALL_EVENT: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        String callId = (String) args.arg1;
                        String event = (String) args.arg2;
                        Bundle extras = (Bundle) args.arg3;
                        sendCallEvent(callId, event, extras);
                    } finally {
                        args.recycle();
                    }
                    break;
                }
                case MSG_ON_EXTRAS_CHANGED: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        String callId = (String) args.arg1;
                        Bundle extras = (Bundle) args.arg2;
                        handleExtrasChanged(callId, extras);
                    } finally {
                        args.recycle();
                    }
                    break;
                }
                default:
                    break;
            }
        }
    };

    private final Conference.Listener mConferenceListener = new Conference.Listener() {
        @Override
        public void onStateChanged(Conference conference, int oldState, int newState) {
            String id = mIdByConference.get(conference);
            switch (newState) {
                case Connection.STATE_ACTIVE:
                    mAdapter.setActive(id);
                    break;
                case Connection.STATE_HOLDING:
                    mAdapter.setOnHold(id);
                    break;
                case Connection.STATE_DISCONNECTED:
                    // handled by onDisconnected
                    break;
            }
        }

        @Override
        public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {
            String id = mIdByConference.get(conference);
            mAdapter.setDisconnected(id, disconnectCause);
        }

        @Override
        public void onConnectionAdded(Conference conference, Connection connection) {
        }

        @Override
        public void onConnectionRemoved(Conference conference, Connection connection) {
        }

        @Override
        public void onConferenceableConnectionsChanged(
                Conference conference, List<Connection> conferenceableConnections) {
            mAdapter.setConferenceableConnections(
                    mIdByConference.get(conference),
                    createConnectionIdList(conferenceableConnections));
        }

        @Override
        public void onDestroyed(Conference conference) {
            removeConference(conference);
        }

        @Override
        public void onConnectionCapabilitiesChanged(
                Conference conference,
                int connectionCapabilities) {
            String id = mIdByConference.get(conference);
            Log.d(this, "call capabilities: conference: %s",
                    Connection.capabilitiesToString(connectionCapabilities));
            mAdapter.setConnectionCapabilities(id, connectionCapabilities);
        }

        @Override
        public void onConnectionPropertiesChanged(
                Conference conference,
                int connectionProperties) {
            String id = mIdByConference.get(conference);
            Log.d(this, "call capabilities: conference: %s",
                    Connection.propertiesToString(connectionProperties));
            mAdapter.setConnectionProperties(id, connectionProperties);
        }

        @Override
        public void onVideoStateChanged(Conference c, int videoState) {
            String id = mIdByConference.get(c);
            Log.d(this, "onVideoStateChanged set video state %d", videoState);
            mAdapter.setVideoState(id, videoState);
        }

        @Override
        public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {
            String id = mIdByConference.get(c);
            Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
                    videoProvider);
            mAdapter.setVideoProvider(id, videoProvider);
        }

        @Override
        public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {
            String id = mIdByConference.get(conference);
            if (id != null) {
                mAdapter.setStatusHints(id, statusHints);
            }
        }

        @Override
        public void onExtrasChanged(Conference c, Bundle extras) {
            String id = mIdByConference.get(c);
            if (id != null) {
                mAdapter.putExtras(id, extras);
            }
        }

        @Override
        public void onExtrasRemoved(Conference c, List<String> keys) {
            String id = mIdByConference.get(c);
            if (id != null) {
                mAdapter.removeExtras(id, keys);
            }
        }
    };

    private final Connection.Listener mConnectionListener = new Connection.Listener() {
        @Override
        public void onStateChanged(Connection c, int state) {
            String id = mIdByConnection.get(c);
            Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state));
            switch (state) {
                case Connection.STATE_ACTIVE:
                    mAdapter.setActive(id);
                    break;
                case Connection.STATE_DIALING:
                    mAdapter.setDialing(id);
                    break;
                case Connection.STATE_DISCONNECTED:
                    // Handled in onDisconnected()
                    break;
                case Connection.STATE_HOLDING:
                    mAdapter.setOnHold(id);
                    break;
                case Connection.STATE_NEW:
                    // Nothing to tell Telecom
                    break;
                case Connection.STATE_RINGING:
                    mAdapter.setRinging(id);
                    break;
            }
        }

        @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);
        }

        @Override
        public void onVideoStateChanged(Connection c, int videoState) {
            String id = mIdByConnection.get(c);
            Log.d(this, "Adapter set video state %d", videoState);
            mAdapter.setVideoState(id, videoState);
        }

        @Override
        public void onAddressChanged(Connection c, Uri address, int presentation) {
            String id = mIdByConnection.get(c);
            mAdapter.setAddress(id, address, presentation);
        }

        @Override
        public void onConnectedAddressChanged(Connection c, Uri connectedAddress, int connectedAddressPresentation) {
            String id = mIdByConnection.get(c);
            mAdapter.setConnectedAddress(id, connectedAddress, connectedAddressPresentation);
        }

        @Override
        public void onCallerDisplayNameChanged(
                Connection c, String callerDisplayName, int presentation) {
            String id = mIdByConnection.get(c);
            mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
        }

        @Override
        public void onDestroyed(Connection c) {
            removeConnection(c);
        }

        @Override
        public void onPostDialWait(Connection c, String remaining) {
            String id = mIdByConnection.get(c);
            Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining);
            mAdapter.onPostDialWait(id, remaining);
        }

        @Override
        public void onPostDialChar(Connection c, char nextChar) {
            String id = mIdByConnection.get(c);
            Log.d(this, "Adapter onPostDialChar %s, %s", c, nextChar);
            mAdapter.onPostDialChar(id, nextChar);
        }

        @Override
        public void onRingbackRequested(Connection c, boolean ringback) {
            String id = mIdByConnection.get(c);
            Log.d(this, "Adapter onRingback %b", ringback);
            mAdapter.setRingbackRequested(id, ringback);
        }

        @Override
        public void onConnectionCapabilitiesChanged(Connection c, int capabilities) {
            String id = mIdByConnection.get(c);
            Log.d(this, "capabilities: parcelableconnection: %s",
                    Connection.capabilitiesToString(capabilities));
            mAdapter.setConnectionCapabilities(id, capabilities);
        }

        @Override
        public void onConnectionPropertiesChanged(Connection c, int properties) {
            String id = mIdByConnection.get(c);
            Log.d(this, "properties: parcelableconnection: %s",
                    Connection.propertiesToString(properties));
            mAdapter.setConnectionProperties(id, properties);
        }

        @Override
        public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) {
            String id = mIdByConnection.get(c);
            Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
                    videoProvider);
            mAdapter.setVideoProvider(id, videoProvider);
        }

        @Override
        public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
            String id = mIdByConnection.get(c);
            mAdapter.setIsVoipAudioMode(id, isVoip);
        }

        @Override
        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
            String id = mIdByConnection.get(c);
            mAdapter.setStatusHints(id, statusHints);
        }

        @Override
        public void onConferenceablesChanged(
                Connection connection, List<Conferenceable> conferenceables) {
            mAdapter.setConferenceableConnections(
                    mIdByConnection.get(connection),
                    createIdList(conferenceables));
        }

        @Override
        public void onConferenceChanged(Connection connection, Conference conference) {
            String id = mIdByConnection.get(connection);
            if (id != null) {
                String conferenceId = null;
                if (conference != null) {
                    conferenceId = mIdByConference.get(conference);
                }
                mAdapter.setIsConferenced(id, conferenceId);
            }
        }

        @Override
        public void onConferenceMergeFailed(Connection connection) {
            String id = mIdByConnection.get(connection);
            if (id != null) {
                mAdapter.onConferenceMergeFailed(id);
            }
        }

        @Override
        public void onExtrasChanged(Connection c, Bundle extras) {
            String id = mIdByConnection.get(c);
            if (id != null) {
                mAdapter.putExtras(id, extras);
            }
        }

        public void onExtrasRemoved(Connection c, List<String> keys) {
            String id = mIdByConnection.get(c);
            if (id != null) {
                mAdapter.removeExtras(id, keys);
            }
        }


        @Override
        public void onConnectionEvent(Connection connection, String event, Bundle extras) {
            String id = mIdByConnection.get(connection);
            if (id != null) {
                mAdapter.onConnectionEvent(id, event, extras);
            }
        }
    };

    /** {@inheritDoc} */
    @Override
    public final IBinder onBind(Intent intent) {
        return mBinder;
    }

    /** {@inheritDoc} */
    @Override
    public boolean onUnbind(Intent intent) {
        endAllConnections();
        return super.onUnbind(intent);
    }

    /**
     * This can be used by telecom to either create a new outgoing call or attach to an existing
     * incoming call. In either case, telecom will cycle through a set of services and call
     * createConnection util a connection service cancels the process or completes it successfully.
     */
    private void createConnection(
            final PhoneAccountHandle callManagerAccount,
            final String callId,
            final ConnectionRequest request,
            boolean isIncoming,
            boolean isUnknown) {
        Log.d(this, "lum_1 createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
                        "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request,
                isIncoming,
                isUnknown);

        Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
                : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
                : onCreateOutgoingConnection(callManagerAccount, request);
        Log.d(this, "lum_2 createConnection, connection: %s", connection);
        if (connection == null) {
            Log.d(this, "lum_3");
            connection = Connection.createFailedConnection(
                    new DisconnectCause(DisconnectCause.ERROR));
        }

        connection.setTelecomCallId(callId);
        if (connection.getState() != Connection.STATE_DISCONNECTED) {
            addConnection(callId, connection);
        }

        Uri address = connection.getAddress();
        String number = address == null ? "null" : address.getSchemeSpecificPart();
        Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, properties: %s",
                Connection.toLogSafePhoneNumber(number),
                Connection.stateToString(connection.getState()),
                Connection.capabilitiesToString(connection.getConnectionCapabilities()),
                Connection.propertiesToString(connection.getConnectionProperties()));

        Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
        mAdapter.handleCreateConnectionComplete(
                callId,
                request,
                new ParcelableConnection(
                        request.getAccountHandle(),
                        connection.getState(),
                        connection.getConnectionCapabilities(),
                        connection.getConnectionProperties(),
                        connection.getAddress(),
                        connection.getAddressPresentation(),
                        connection.getCallerDisplayName(),
                        connection.getCallerDisplayNamePresentation(),
                        connection.getVideoProvider() == null ?
                                null : connection.getVideoProvider().getInterface(),
                        connection.getVideoState(),
                        connection.isRingbackRequested(),
                        connection.getAudioModeIsVoip(),
                        connection.getConnectTimeMillis(),
                        connection.getStatusHints(),
                        connection.getDisconnectCause(),
                        createIdList(connection.getConferenceables()),
                        connection.getExtras()));
        if (isUnknown) {
            triggerConferenceRecalculate();
        }
    }

    private void abort(String callId) {
        Log.d(this, "abort %s", callId);
        findConnectionForAction(callId, "abort").onAbort();
    }

    private void answerVideo(String callId, int videoState) {
        Log.d(this, "answerVideo %s", callId);
        findConnectionForAction(callId, "answer").onAnswer(videoState);
    }

    private void answer(String callId) {
        Log.d(this, "answer %s", callId);
        findConnectionForAction(callId, "answer").onAnswer();
    }

    private void reject(String callId) {
        Log.d(this, "reject %s", callId);
        findConnectionForAction(callId, "reject").onReject();
    }

    private void reject(String callId, String rejectWithMessage) {
        Log.d(this, "reject %s with message", callId);
        findConnectionForAction(callId, "reject").onReject(rejectWithMessage);
    }

    private void silence(String callId) {
        Log.d(this, "silence %s", callId);
        findConnectionForAction(callId, "silence").onSilence();
    }

    private void disconnect(String callId) {
        Log.d(this, "disconnect %s", callId);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "disconnect").onDisconnect();
        } else {
            findConferenceForAction(callId, "disconnect").onDisconnect();
        }
    }

    private void hold(String callId) {
        Log.d(this, "hold %s", callId);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "hold").onHold();
        } else {
            findConferenceForAction(callId, "hold").onHold();
        }
    }

    private void unhold(String callId) {
        Log.d(this, "unhold %s", callId);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "unhold").onUnhold();
        } else {
            findConferenceForAction(callId, "unhold").onUnhold();
        }
    }

    private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
        Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState(
                    callAudioState);
        } else {
            findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState(
                    callAudioState);
        }
    }

    private void playDtmfTone(String callId, char digit) {
        Log.d(this, "playDtmfTone %s %c", callId, digit);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
        } else {
            findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
        }
    }

    private void stopDtmfTone(String callId) {
        Log.d(this, "stopDtmfTone %s", callId);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
        } else {
            findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone();
        }
    }

    private void conference(String callId1, String callId2) {
        Log.d(this, "conference %s, %s", callId1, callId2);

        // Attempt to get second connection or conference.
        Connection connection2 = findConnectionForAction(callId2, "conference");
        Conference conference2 = getNullConference();
        if (connection2 == getNullConnection()) {
            conference2 = findConferenceForAction(callId2, "conference");
            if (conference2 == getNullConference()) {
                Log.w(this, "Connection2 or Conference2 missing in conference request %s.",
                        callId2);
                return;
            }
        }

        // Attempt to get first connection or conference and perform merge.
        Connection connection1 = findConnectionForAction(callId1, "conference");
        if (connection1 == getNullConnection()) {
            Conference conference1 = findConferenceForAction(callId1, "addConnection");
            if (conference1 == getNullConference()) {
                Log.w(this,
                        "Connection1 or Conference1 missing in conference request %s.",
                        callId1);
            } else {
                // Call 1 is a conference.
                if (connection2 != getNullConnection()) {
                    // Call 2 is a connection so merge via call 1 (conference).
                    conference1.onMerge(connection2);
                } else {
                    // Call 2 is ALSO a conference; this should never happen.
                    Log.wtf(this, "There can only be one conference and an attempt was made to " +
                            "merge two conferences.");
                    return;
                }
            }
        } else {
            // Call 1 is a connection.
            if (conference2 != getNullConference()) {
                // Call 2 is a conference, so merge via call 2.
                conference2.onMerge(connection1);
            } else {
                // Call 2 is a connection, so merge together.
                onConference(connection1, connection2);
            }
        }
    }

    private void splitFromConference(String callId) {
        Log.d(this, "splitFromConference(%s)", callId);

        Connection connection = findConnectionForAction(callId, "splitFromConference");
        if (connection == getNullConnection()) {
            Log.w(this, "Connection missing in conference request %s.", callId);
            return;
        }

        Conference conference = connection.getConference();
        if (conference != null) {
            conference.onSeparate(connection);
        }
    }

    private void mergeConference(String callId) {
        Log.d(this, "mergeConference(%s)", callId);
        Conference conference = findConferenceForAction(callId, "mergeConference");
        if (conference != null) {
            conference.onMerge();
        }
    }

    private void swapConference(String callId) {
        Log.d(this, "swapConference(%s)", callId);
        Conference conference = findConferenceForAction(callId, "swapConference");
        if (conference != null) {
            conference.onSwap();
        }
    }

    /**
     * Notifies a {@link Connection} of a request to pull an external call.
     *
     * See {@link Call#pullExternalCall()}.
     *
     * @param callId The ID of the call to pull.
     */
    private void pullExternalCall(String callId) {
        Log.d(this, "pullExternalCall(%s)", callId);
        Connection connection = findConnectionForAction(callId, "pullExternalCall");
        if (connection != null) {
            connection.onPullExternalCall();
        }
    }

    /**
     * Notifies a {@link Connection} of a call event.
     *
     * See {@link Call#sendCallEvent(String, Bundle)}.
     *
     * @param callId The ID of the call receiving the event.
     * @param event The event.
     * @param extras Extras associated with the event.
     */
    private void sendCallEvent(String callId, String event, Bundle extras) {
        Log.d(this, "sendCallEvent(%s, %s)", callId, event);
        Connection connection = findConnectionForAction(callId, "sendCallEvent");
        if (connection != null) {
            connection.onCallEvent(event, extras);
        }

    }

    /**
     * Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom.
     * <p>
     * These extra changes can originate from Telecom itself, or from an {@link InCallService} via
     * the {@link android.telecom.Call#putExtra(String, boolean)},
     * {@link android.telecom.Call#putExtra(String, int)},
     * {@link android.telecom.Call#putExtra(String, String)},
     * {@link Call#removeExtras(List)}.
     *
     * @param callId The ID of the call receiving the event.
     * @param extras The new extras bundle.
     */
    private void handleExtrasChanged(String callId, Bundle extras) {
        Log.d(this, "handleExtrasChanged(%s, %s)", callId, extras);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
        } else if (mConferenceById.containsKey(callId)) {
            findConferenceForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
        }
    }

    private void onPostDialContinue(String callId, boolean proceed) {
        Log.d(this, "onPostDialContinue(%s)", callId);
        findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
    }

    private void onAdapterAttached() {
        if (mAreAccountsInitialized) {
            // No need to query again if we already did it.
            return;
        }

        mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
            @Override
            public void onResult(
                    final List<ComponentName> componentNames,
                    final List<IBinder> services) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
                            mRemoteConnectionManager.addConnectionService(
                                    componentNames.get(i),
                                    IConnectionService.Stub.asInterface(services.get(i)));
                        }
                        onAccountsInitialized();
                        Log.d(this, "remote connection services found: " + services);
                    }
                });
            }

            @Override
            public void onError() {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mAreAccountsInitialized = true;
                    }
                });
            }
        });
    }

    /**
     * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
     * incoming request. This is used by {@code ConnectionService}s that are registered with
     * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to manage
     * SIM-based incoming calls.
     *
     * @param connectionManagerPhoneAccount See description at
     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
     * @param request Details about the incoming call.
     * @return The {@code Connection} object to satisfy this call, or {@code null} to
     *         not handle the call.
     */
    public final RemoteConnection createRemoteIncomingConnection(
            PhoneAccountHandle connectionManagerPhoneAccount,
            ConnectionRequest request) {
        return mRemoteConnectionManager.createRemoteConnection(
                connectionManagerPhoneAccount, request, true);
    }

    /**
     * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
     * outgoing request. This is used by {@code ConnectionService}s that are registered with
     * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the
     * SIM-based {@code ConnectionService} to place its outgoing calls.
     *
     * @param connectionManagerPhoneAccount See description at
     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
     * @param request Details about the incoming call.
     * @return The {@code Connection} object to satisfy this call, or {@code null} to
     *         not handle the call.
     */
    public final RemoteConnection createRemoteOutgoingConnection(
            PhoneAccountHandle connectionManagerPhoneAccount,
            ConnectionRequest request) {
        return mRemoteConnectionManager.createRemoteConnection(
                connectionManagerPhoneAccount, request, false);
    }

    /**
     * Indicates to the relevant {@code RemoteConnectionService} that the specified
     * {@link RemoteConnection}s should be merged into a conference call.
     * <p>
     * If the conference request is successful, the method {@link #onRemoteConferenceAdded} will
     * be invoked.
     *
     * @param remoteConnection1 The first of the remote connections to conference.
     * @param remoteConnection2 The second of the remote connections to conference.
     */
    public final void conferenceRemoteConnections(
            RemoteConnection remoteConnection1,
            RemoteConnection remoteConnection2) {
        mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2);
    }

    /**
     * Adds a new conference call. When a conference call is created either as a result of an
     * explicit request via {@link #onConference} or otherwise, the connection service should supply
     * an instance of {@link Conference} by invoking this method. A conference call provided by this
     * method will persist until {@link Conference#destroy} is invoked on the conference instance.
     *
     * @param conference The new conference object.
     */
    public final void addConference(Conference conference) {
        Log.d(this, "addConference: conference=%s", conference);

        String id = addConferenceInternal(conference);
        if (id != null) {
            List<String> connectionIds = new ArrayList<>(2);
            for (Connection connection : conference.getConnections()) {
                if (mIdByConnection.containsKey(connection)) {
                    connectionIds.add(mIdByConnection.get(connection));
                }
            }
            conference.setTelecomCallId(id);
            ParcelableConference parcelableConference = new ParcelableConference(
                    conference.getPhoneAccountHandle(),
                    conference.getState(),
                    conference.getConnectionCapabilities(),
                    conference.getConnectionProperties(),
                    connectionIds,
                    conference.getVideoProvider() == null ?
                            null : conference.getVideoProvider().getInterface(),
                    conference.getVideoState(),
                    conference.getConnectTimeMillis(),
                    conference.getStatusHints(),
                    conference.getExtras());

            mAdapter.addConferenceCall(id, parcelableConference);
            mAdapter.setVideoProvider(id, conference.getVideoProvider());
            mAdapter.setVideoState(id, conference.getVideoState());

            // Go through any child calls and set the parent.
            for (Connection connection : conference.getConnections()) {
                String connectionId = mIdByConnection.get(connection);
                if (connectionId != null) {
                    mAdapter.setIsConferenced(connectionId, id);
                }
            }
        }
    }

    /**
     * Adds a connection created by the {@link ConnectionService} and informs telecom of the new
     * connection.
     *
     * @param phoneAccountHandle The phone account handle for the connection.
     * @param connection The connection to add.
     */
    public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
            Connection connection) {

        String id = addExistingConnectionInternal(phoneAccountHandle, connection);
        if (id != null) {
            List<String> emptyList = new ArrayList<>(0);

            ParcelableConnection parcelableConnection = new ParcelableConnection(
                    phoneAccountHandle,
                    connection.getState(),
                    connection.getConnectionCapabilities(),
                    connection.getConnectionProperties(),
                    connection.getAddress(),
                    connection.getAddressPresentation(),
                    connection.getCallerDisplayName(),
                    connection.getCallerDisplayNamePresentation(),
                    connection.getVideoProvider() == null ?
                            null : connection.getVideoProvider().getInterface(),
                    connection.getVideoState(),
                    connection.isRingbackRequested(),
                    connection.getAudioModeIsVoip(),
                    connection.getConnectTimeMillis(),
                    connection.getStatusHints(),
                    connection.getDisconnectCause(),
                    emptyList,
                    connection.getExtras());
            mAdapter.addExistingConnection(id, parcelableConnection);
        }
    }

    /**
     * Returns all the active {@code Connection}s for which this {@code ConnectionService}
     * has taken responsibility.
     *
     * @return A collection of {@code Connection}s created by this {@code ConnectionService}.
     */
    public final Collection<Connection> getAllConnections() {
        return mConnectionById.values();
    }

    /**
     * Returns all the active {@code Conference}s for which this {@code ConnectionService}
     * has taken responsibility.
     *
     * @return A collection of {@code Conference}s created by this {@code ConnectionService}.
     */
    public final Collection<Conference> getAllConferences() {
        return mConferenceById.values();
    }

    /**
     * Create a {@code Connection} given an incoming request. This is used to attach to existing
     * incoming calls.
     *
     * @param connectionManagerPhoneAccount See description at
     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
     * @param request Details about the incoming call.
     * @return The {@code Connection} object to satisfy this call, or {@code null} to
     *         not handle the call.
     */
    public Connection onCreateIncomingConnection(
            PhoneAccountHandle connectionManagerPhoneAccount,
            ConnectionRequest request) {
        return null;
    }

    /**
     * Trigger recalculate functinality for conference calls. This is used when a Telephony
     * Connection is part of a conference controller but is not yet added to Connection
     * Service and hence cannot be added to the conference call.
     *
     * @hide
     */
    public void triggerConferenceRecalculate() {
    }

    /**
     * Create a {@code Connection} given an outgoing request. This is used to initiate new
     * outgoing calls.
     *
     * @param connectionManagerPhoneAccount The connection manager account to use for managing
     *         this call.
     *         <p>
     *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
     *         has registered one or more {@code PhoneAccount}s having
     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
     *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
     *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
     *         making the connection.
     *         <p>
     *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
     *         being asked to make a direct connection. The
     *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
     *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
     *         making the connection.
     * @param request Details about the outgoing call.
     * @return The {@code Connection} object to satisfy this call, or the result of an invocation
     *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
     */
    public Connection onCreateOutgoingConnection(
            PhoneAccountHandle connectionManagerPhoneAccount,
            ConnectionRequest request) {
        return null;
    }

    /**
     * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
     * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
     * call created using
     * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
     *
     * @param connectionManagerPhoneAccount
     * @param request
     * @return
     *
     * @hide
     */
    public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
            ConnectionRequest request) {
       return null;
    }

    /**
     * Conference two specified connections. Invoked when the user has made a request to merge the
     * specified connections into a conference call. In response, the connection service should
     * create an instance of {@link Conference} and pass it into {@link #addConference}.
     *
     * @param connection1 A connection to merge into a conference call.
     * @param connection2 A connection to merge into a conference call.
     */
    public void onConference(Connection connection1, Connection connection2) {}

    /**
     * Indicates that a remote conference has been created for existing {@link RemoteConnection}s.
     * When this method is invoked, this {@link ConnectionService} should create its own
     * representation of the conference call and send it to telecom using {@link #addConference}.
     * <p>
     * This is only relevant to {@link ConnectionService}s which are registered with
     * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.
     *
     * @param conference The remote conference call.
     */
    public void onRemoteConferenceAdded(RemoteConference conference) {}

    /**
     * Called when an existing connection is added remotely.
     * @param connection The existing connection which was added.
     */
    public void onRemoteExistingConnectionAdded(RemoteConnection connection) {}

    /**
     * @hide
     */
    public boolean containsConference(Conference conference) {
        return mIdByConference.containsKey(conference);
    }

    /** {@hide} */
    void addRemoteConference(RemoteConference remoteConference) {
        onRemoteConferenceAdded(remoteConference);
    }

    /** {@hide} */
    void addRemoteExistingConnection(RemoteConnection remoteConnection) {
        onRemoteExistingConnectionAdded(remoteConnection);
    }

    private void onAccountsInitialized() {
        mAreAccountsInitialized = true;
        for (Runnable r : mPreInitializationConnectionRequests) {
            r.run();
        }
        mPreInitializationConnectionRequests.clear();
    }

    /**
     * Adds an existing connection to the list of connections, identified by a new call ID unique
     * to this connection service.
     *
     * @param connection The connection.
     * @return The ID of the connection (e.g. the call-id).
     */
    private String addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection) {
        String id;
        if (handle == null) {
            // If no phone account handle was provided, we cannot be sure the call ID is unique,
            // so just use a random UUID.
            id = UUID.randomUUID().toString();
        } else {
            // Phone account handle was provided, so use the ConnectionService class name as a
            // prefix for a unique incremental call ID.
            id = handle.getComponentName().getClassName() + "@" + getNextCallId();
        }
        addConnection(id, connection);
        return id;
    }

    private void addConnection(String callId, Connection connection) {
        connection.setTelecomCallId(callId);
        mConnectionById.put(callId, connection);
        mIdByConnection.put(connection, callId);
        connection.addConnectionListener(mConnectionListener);
        connection.setConnectionService(this);
    }

    /** {@hide} */
    protected void removeConnection(Connection connection) {
        connection.unsetConnectionService(this);
        connection.removeConnectionListener(mConnectionListener);
        String id = mIdByConnection.get(connection);
        if (id != null) {
            mConnectionById.remove(id);
            mIdByConnection.remove(connection);
            mAdapter.removeCall(id);
        }
    }

    private String addConferenceInternal(Conference conference) {
        if (mIdByConference.containsKey(conference)) {
            Log.w(this, "Re-adding an existing conference: %s.", conference);
        } else if (conference != null) {
            // Conferences do not (yet) have a PhoneAccountHandle associated with them, so we
            // cannot determine a ConnectionService class name to associate with the ID, so use
            // a unique UUID (for now).
            String id = UUID.randomUUID().toString();
            mConferenceById.put(id, conference);
            mIdByConference.put(conference, id);
            conference.addListener(mConferenceListener);
            return id;
        }

        return null;
    }

    private void removeConference(Conference conference) {
        if (mIdByConference.containsKey(conference)) {
            conference.removeListener(mConferenceListener);

            String id = mIdByConference.get(conference);
            mConferenceById.remove(id);
            mIdByConference.remove(conference);
            mAdapter.removeCall(id);
        }
    }

    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();
    }

    static synchronized Connection getNullConnection() {
        if (sNullConnection == null) {
            sNullConnection = new Connection() {};
        }
        return sNullConnection;
    }

    private Conference findConferenceForAction(String conferenceId, String action) {
        if (mConferenceById.containsKey(conferenceId)) {
            return mConferenceById.get(conferenceId);
        }
        Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
        return getNullConference();
    }

    private List<String> createConnectionIdList(List<Connection> connections) {
        List<String> ids = new ArrayList<>();
        for (Connection c : connections) {
            if (mIdByConnection.containsKey(c)) {
                ids.add(mIdByConnection.get(c));
            }
        }
        Collections.sort(ids);
        return ids;
    }

    /**
     * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of
     * {@link Conferenceable}s passed in.
     *
     * @param conferenceables The {@link Conferenceable} connections and conferences.
     * @return List of string conference and call Ids.
     */
    private List<String> createIdList(List<Conferenceable> conferenceables) {
        List<String> ids = new ArrayList<>();
        for (Conferenceable c : conferenceables) {
            // Only allow Connection and Conference conferenceables.
            if (c instanceof Connection) {
                Connection connection = (Connection) c;
                if (mIdByConnection.containsKey(connection)) {
                    ids.add(mIdByConnection.get(connection));
                }
            } else if (c instanceof Conference) {
                Conference conference = (Conference) c;
                if (mIdByConference.containsKey(conference)) {
                    ids.add(mIdByConference.get(conference));
                }
            }
        }
        Collections.sort(ids);
        return ids;
    }

    private Conference getNullConference() {
        if (sNullConference == null) {
            sNullConference = new Conference(null) {};
        }
        return sNullConference;
    }

    private void endAllConnections() {
        // Unbound from telecomm.  We should end all connections and conferences.
        for (Connection connection : mIdByConnection.keySet()) {
            // only operate on top-level calls. Conference calls will be removed on their own.
            if (connection.getConference() == null) {
                connection.onDisconnect();
            }
        }
        for (Conference conference : mIdByConference.keySet()) {
            conference.onDisconnect();
        }
    }

    /**
     * Retrieves the next call ID as maintainted by the connection service.
     *
     * @return The call ID.
     */
    private int getNextCallId() {
        synchronized(mIdSyncRoot) {
            return ++mId;
        }
    }
}

发送MES_DISCONNECT消息到队列了处理

    private void disconnect(String callId) {
        Log.d(this, "disconnect %s", callId);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "disconnect").onDisconnect();
        } else {
            findConferenceForAction(callId, "disconnect").onDisconnect();
        }
    }

接着调用这个函数

    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();
    }

根据callid找到对应的connection对象(android.telecom.Connection),调用onDisconnect()方法

11)、TelephonyConnection.java文件的onDisconnect()方法
    @Override
    public void onDisconnect() {
        Log.v(this, "onDisconnect");
        hangup(android.telephony.DisconnectCause.LOCAL);
    }

接着调用


    protected void hangup(int telephonyDisconnectCode) {
        if (mOriginalConnection != null) {
            int connectionState = getState();
            Log.w(this, "hangup connection state: " + connectionState);
            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()) {
                    Log.v(this, "The call is Valid Ringing call");
                    Call call = getCall();
                    if (call != null) {
                        call.hangup();
                    } else {
                        Log.w(this, "Attempting to hangup a connection without backing call.");
                    }
                } else if (isValidWaitingCall()) {
                    Log.w(this, "isValidWaitingCall");
                    if (connectionState == STATE_HOLDING) {
                        mNeedAccept = true;
                        mOriginalConnection.hangup();
                    } else if (connectionState == STATE_ACTIVE) {
                        Call call = getCall(); // this call is foreground call
                        if (call != null) {
                        //调用到这个函数
                            call.hangup();
                        } else {
                            Log.w(this, "Foreground call does not exist.");
                        }
                    }
                } else if (isSrvccCompleted()) {
                    Log.d(this, "hangup: After handover, trigger pending request");
                    mPendingRequest = PENDING_REQUEST_TERMINATE;
                } 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");
            }
        }
    }

设置断开链接类型DisconnectCause.LOCAL,并调用mOriginalConnection的hangup方法,实际Connection类是GsmCdmaConnection

12)、GsmCdmaConnection.java文件的hangup()方法
    @Override
    public void hangup() throws CallStateException {
        if (!mDisconnected) {
            mOwner.hangup(this);
        } else {
            throw new CallStateException ("disconnected");
        }
    }
13)、调用GsmCdmaCallTracker.java的hangup()方法

    //***** Called from GsmCdmaConnection

    public void hangup(GsmCdmaConnection conn) throws CallStateException {
        if (conn.mOwner != this) {
            throw new CallStateException ("GsmCdmaConnection " + conn
                                    + "does not belong to GsmCdmaCallTracker " + this);
        }

        if (conn == mPendingMO) {
            // We're hanging up an outgoing call that doesn't have it's
            // GsmCdma index assigned yet

            if (Phone.DEBUG_PHONE) log("hangup: set hangupPendingMO to true");
            mHangupPendingMO = true;
        } else if (!isPhoneTypeGsm()
                && conn.getCall() == mRingingCall
                && mRingingCall.getState() == GsmCdmaCall.State.WAITING) {
            // Handle call waiting hang up case.
            //
            // The ringingCall state will change to IDLE in GsmCdmaCall.detach
            // if the ringing call connection size is 0. We don't specifically
            // set the ringing call state to IDLE here to avoid a race condition
            // where a new call waiting could get a hang up from an old call
            // waiting ringingCall.
            //
            // PhoneApp does the call log itself since only PhoneApp knows
            // the hangup reason is user ignoring or timing out. So conn.onDisconnect()
            // is not called here. Instead, conn.onLocalDisconnect() is called.
            conn.onLocalDisconnect();

            updatePhoneState();
            mPhone.notifyPreciseCallStateChanged();
            return;
        } else {
            try {
            //调用这个函数(注意参数)
                mCi.hangupConnection (conn.getGsmCdmaIndex(), obtainCompleteMessage());
            } catch (CallStateException ex) {
                // Ignore "connection not found"
                // Call may have hung up already
                Rlog.w(LOG_TAG,"GsmCdmaCallTracker WARN: hangup() on absent connection "
                                + conn);
            }
        }

        conn.onHangupLocal();
    }

由于是通话挂断,这里调用 mCi.hangupConnection(conn.getGsmCdmaIndex(),obtainCompleteMessage());
这里调用conn的getGsmCdmaIndex方法先索取索引

14)、GsmCdmaConnection.java中getGsmCdmaIndex()函数

    /*package*/ int
    getGsmCdmaIndex() throws CallStateException {
        if (mIndex >= 0) {
            return mIndex + 1;
        } else {
            throw new CallStateException ("GsmCdma index not yet assigned");
        }
    }

这个索引对应的就是DriveCall里面的index的值,匹配的modem里CallList里对于Call对象

mCi是CommandsInterface即RILJ接口,包装了一个EVENT_OPERATION_COMPLETE回调的消息,发送给RIL

15)、RIL.java 文件的hangupConnection()方法


    @Override
    public void
    hangupConnection (int gsmIndex, Message result) {
        if (RILJ_LOGD) riljLog("hangupConnection: gsmIndex=" + gsmIndex);

        RILRequest rr = RILRequest.obtain(RIL_REQUEST_HANGUP, result);

        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " " +
                gsmIndex);

        mEventLog.writeRilHangup(rr.mSerial, RIL_REQUEST_HANGUP, gsmIndex);

        rr.mParcel.writeInt(1);
        rr.mParcel.writeInt(gsmIndex);

          Rlog.d(RILJ_LOG_TAG, "lum_52");
        send(rr);
    }

给RIL层发送RIL_REQUEST_HANGUP消息并附带index参数

收到RIL层的回应消息并处理,最后发送回调消息 EVENT_OPERATION_COMPLETE给GsmCdmaCallTracker
GsmCdmaCallTracker处理回调消息EVENT_OPERATION_COMPLETE

16)、GsmCdmaCallTracker.java文件operationComplete()方法
    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;
        }
    }

这里再次向RIL发送消息主动获取当前call状态,包装的回调消息为EVENT_POLL_CALLS_RESULT

RIL 返回消息,GsmCdmaCallTracker接受EVENT_POLL_CALLS_RESULT消息并处理


    // ***** Overwritten from CallTracker

    @Override
    protected synchronized void handlePollCalls(AsyncResult ar) {
        List polledCalls;

        if (VDBG) log("handlePollCalls");
        if (ar.exception == null) {
            polledCalls = (List)ar.result;
        } else if (isCommandExceptionRadioNotAvailable(ar.exception)) {
            // just a dummy empty ArrayList to cause the loop
            // to hang up all the calls
            polledCalls = new ArrayList();
        } else {
            // Radio probably wasn't ready--try again in a bit
            // But don't keep polling if the channel is closed
            pollCallsAfterDelay();
            return;
        }

        Connection newRinging = null; //or waiting
        ArrayList<Connection> newUnknownConnectionsGsm = new ArrayList<Connection>();
        Connection newUnknownConnectionCdma = null;
        boolean hasNonHangupStateChanged = false;   // Any change besides
                                                    // a dropped connection
        boolean hasAnyCallDisconnected = false;
        boolean needsPollDelay = false;
        boolean unknownConnectionAppeared = false;

        int ignoredUpdateRequest = ImsCall.UPDATE_NONE;
       // countOfIgnoredRequest can be 0 ~ 3.
        // 0 means there's no ignored request.
        // 1 means there's hangup or hold or resume or accept.
        // 2 means there's hangup active or hold and accept waiting.
        // 3 means there's hangup active and hangup hold and accept waiting.
        int countOfIgnoredRequest = 0;
        GsmCdmaConnection pendingConnection = null;
        // pendingHoldConnection uses to distinguish whether to hangup active or hold ignored request,
        // when countOfIgnoredRequest is 2.
        GsmCdmaConnection pendingHoldConnection = null;

        //CDMA
        boolean noConnectionExists = true;

        for (int i = 0, curDC = 0, dcSize = polledCalls.size()
                ; i < mConnections.length; i++) {
            GsmCdmaConnection conn = mConnections[i];
            DriverCall dc = null;

            // polledCall list is sparse
            if (curDC < dcSize) {
                dc = (DriverCall) polledCalls.get(curDC);

                if (dc.index == i+1) {
                    curDC++;
                } else {
                    dc = null;
                }
            }

            //CDMA
            if (conn != null || dc != null) {
                noConnectionExists = false;
            }

            if (DBG_POLL) log("poll: conn[i=" + i + "]=" +
                    conn+", dc=" + dc);

            if (conn == null && dc != null) {
                // Connection appeared in CLCC response that we don't know about
                if (mPendingMO != null && mPendingMO.compareTo(dc)) {

                    if (DBG_POLL) log("poll: pendingMO=" + mPendingMO);

                    // It's our pending mobile originating call
                    mConnections[i] = mPendingMO;
                    mPendingMO.mIndex = i;
                    mPendingMO.update(dc);
                    mPendingMO = null;

                    // Someone has already asked to hangup this call
                    if (mHangupPendingMO) {
                        mHangupPendingMO = false;

                        // Re-start Ecm timer when an uncompleted emergency call ends
                        if (!isPhoneTypeGsm() && mIsEcmTimerCanceled) {
                            handleEcmTimer(GsmCdmaPhone.RESTART_ECM_TIMER);
                        }

                        try {
                            if (Phone.DEBUG_PHONE) log(
                                    "poll: hangupPendingMO, hangup conn " + i);
                            hangup(mConnections[i]);
                        } catch (CallStateException ex) {
                            Rlog.e(LOG_TAG, "unexpected error on hangup");
                        }

                        // Do not continue processing this poll
                        // Wait for hangup and repoll
                        return;
                    }

                    // Check update state of foreground ImsCall.
                    // If it is UPDATE_TERMINATE, it means user wanted hangup the call.
                    // But there was no response. it may be ignored by silent redial or SRVCC.
                    // In here, handing silent redial case.
                    ImsPhone imsPhone = (ImsPhone)mPhone.getImsPhone();
                    if (imsPhone != null) {
                        ImsCall imsCall = imsPhone.getForegroundCall().getImsCall();
                        if (imsCall != null && imsCall.getUpdateRequest() == ImsCall.UPDATE_TERMINATE) {
                            try {
                                Rlog.e(LOG_TAG, "hangup after silent redial");
                                hangup(mConnections[i]);
                            } catch (CallStateException ex) {
                                Rlog.e(LOG_TAG, "unexpected error on hangup");
                            }
                            // clear ImsCall update state.
                            // set ImsReasonInfo as CODE_UNSPECIFIED.
                            imsCall.clear(new ImsReasonInfo());

                            // Do not continue processing this poll
                            // Wait for hangup and repoll
                            return;
                        }
                    }
                } else {
                    if (Phone.DEBUG_PHONE) {
                        log("pendingMo=" + mPendingMO + ", dc=" + dc);
                    }

                    mConnections[i] = new GsmCdmaConnection(mPhone, dc, this, i);

                    Connection hoConnection = getHoConnection(dc);
                    if (hoConnection != null) {
                        // Single Radio Voice Call Continuity (SRVCC) completed
                        mConnections[i].migrateFrom(hoConnection);
                        // Updating connect time for silent redial cases (ex: Calls are transferred
                        // from DIALING/ALERTING/INCOMING/WAITING to ACTIVE)
                        if (hoConnection.mPreHandoverState != GsmCdmaCall.State.ACTIVE &&
                                hoConnection.mPreHandoverState != GsmCdmaCall.State.HOLDING &&
                                dc.state == DriverCall.State.ACTIVE) {
                            mConnections[i].onConnectedInOrOut();
                        }

                        if (hoConnection instanceof ImsPhoneConnection) {
                            ImsCall imsCall = ((ImsPhoneConnection)hoConnection).getImsCall();
                            // Find ignored request. (HOLD, RESUME, TERMINATED, ACCEPT)
                            if (imsCall != null &&
                                findIgnoredRequest(imsCall.getUpdateRequest(), mConnections[i].getState())) {
                                    ignoredUpdateRequest = imsCall.getUpdateRequest();
                                    pendingConnection = mConnections[i];
                                    if (mConnections[i].getState() == Call.State.HOLDING) {
                                        pendingHoldConnection = mConnections[i];
                                    }
                                    countOfIgnoredRequest++;
                            }
                        }

                        mHandoverConnections.remove(hoConnection);

                        if (isPhoneTypeGsm()) {
                            for (Iterator<Connection> it = mHandoverConnections.iterator();
                                 it.hasNext(); ) {
                                Connection c = it.next();
                                Rlog.i(LOG_TAG, "HO Conn state is " + c.mPreHandoverState);
                                if (c.mPreHandoverState == mConnections[i].getState()) {
                                    Rlog.i(LOG_TAG, "Removing HO conn "
                                            + hoConnection + c.mPreHandoverState);
                                    it.remove();
                                }
                            }
                        }

                        mPhone.notifyHandoverStateChanged(mConnections[i]);
                    } else {
                        // find if the MT call is a new ring or unknown connection
                        newRinging = checkMtFindNewRinging(dc,i);
                        if (newRinging == null) {
                            unknownConnectionAppeared = true;
                            if (isPhoneTypeGsm()) {
                                newUnknownConnectionsGsm.add(mConnections[i]);
                            } else {
                                newUnknownConnectionCdma = mConnections[i];
                            }
                        }
                    }
                }
                hasNonHangupStateChanged = true;
            } else if (conn != null && dc == null) {
                if (isPhoneTypeGsm()) {
                    // Connection missing in CLCC response that we were
                    // tracking.
                    mDroppedDuringPoll.add(conn);
                    // Dropped connections are removed from the CallTracker
                    // list but kept in the GsmCdmaCall list
                    mConnections[i] = null;
                } else {
                    // This case means the RIL has no more active call anymore and
                    // we need to clean up the foregroundCall and ringingCall.
                    // Loop through foreground call connections as
                    // it contains the known logical connections.
                    int count = mForegroundCall.mConnections.size();
                    for (int n = 0; n < count; n++) {
                        if (Phone.DEBUG_PHONE) log("adding fgCall cn " + n + " to droppedDuringPoll");
                        GsmCdmaConnection cn = (GsmCdmaConnection)mForegroundCall.mConnections.get(n);
                        mDroppedDuringPoll.add(cn);
                    }
                    count = mRingingCall.mConnections.size();
                    // Loop through ringing call connections as
                    // it may contain the known logical connections.
                    for (int n = 0; n < count; n++) {
                        if (Phone.DEBUG_PHONE) log("adding rgCall cn " + n + " to droppedDuringPoll");
                        GsmCdmaConnection cn = (GsmCdmaConnection)mRingingCall.mConnections.get(n);
                        mDroppedDuringPoll.add(cn);
                    }

                    // Re-start Ecm timer when the connected emergency call ends
                    if (mIsEcmTimerCanceled) {
                        handleEcmTimer(GsmCdmaPhone.RESTART_ECM_TIMER);
                    }
                    // If emergency call is not going through while dialing
                    checkAndEnableDataCallAfterEmergencyCallDropped();

                    // Dropped connections are removed from the CallTracker
                    // list but kept in the Call list
                    mConnections[i] = null;

                }
            } else if (conn != null && dc != null && !conn.compareTo(dc) && isPhoneTypeGsm()) {
                // Connection in CLCC response does not match what
                // we were tracking. Assume dropped call and new call

                mDroppedDuringPoll.add(conn);
                mConnections[i] = new GsmCdmaConnection (mPhone, dc, this, i);

                if (mConnections[i].getCall() == mRingingCall) {
                    newRinging = mConnections[i];
                } // else something strange happened
                hasNonHangupStateChanged = true;
            } else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */
                // Call collision case
                if (!isPhoneTypeGsm() && conn.isIncoming() != dc.isMT) {
                    if (dc.isMT == true) {
                        // Mt call takes precedence than Mo,drops Mo
                        mDroppedDuringPoll.add(conn);
                        // find if the MT call is a new ring or unknown connection
                        newRinging = checkMtFindNewRinging(dc,i);
                        if (newRinging == null) {
                            unknownConnectionAppeared = true;
                            newUnknownConnectionCdma = conn;
                        }
                        checkAndEnableDataCallAfterEmergencyCallDropped();
                    } else {
                        // Call info stored in conn is not consistent with the call info from dc.
                        // We should follow the rule of MT calls taking precedence over MO calls
                        // when there is conflict, so here we drop the call info from dc and
                        // continue to use the call info from conn, and only take a log.
                        Rlog.e(LOG_TAG,"Error in RIL, Phantom call appeared " + dc);
                    }
                } else {
                    boolean changed;
                    changed = conn.update(dc);
                    hasNonHangupStateChanged = hasNonHangupStateChanged || changed;
                }
            }

            if (REPEAT_POLLING) {
                if (dc != null) {
                    // FIXME with RIL, we should not need this anymore
                    if ((dc.state == DriverCall.State.DIALING
                            /*&& cm.getOption(cm.OPTION_POLL_DIALING)*/)
                        || (dc.state == DriverCall.State.ALERTING
                            /*&& cm.getOption(cm.OPTION_POLL_ALERTING)*/)
                        || (dc.state == DriverCall.State.INCOMING
                            /*&& cm.getOption(cm.OPTION_POLL_INCOMING)*/)
                        || (dc.state == DriverCall.State.WAITING
                            /*&& cm.getOption(cm.OPTION_POLL_WAITING)*/)) {
                        // Sometimes there's no unsolicited notification
                        // for state transitions
                        needsPollDelay = true;
                    }
                }
            }
        }

        // SRVCC Solution
        Rlog.d(LOG_TAG, "countOfIgnoredRequest is " + countOfIgnoredRequest);
        // this means hangup active and hangup hold and accept waiting.
        if (countOfIgnoredRequest == 3) {
            if (pendingHoldConnection != null) {
                try {
                    Rlog.d(LOG_TAG, "Trigger ignored hangup active, accept waiting call request");
                    hangup(pendingHoldConnection);
                    hangupForegroundResumeBackground();
                    // Do not continue processing this poll
                    // Wait for hangup and repoll
                    return;
                } catch (CallStateException ex) {
                    Rlog.d(LOG_TAG, "hangup failed with exception: " + ex);
                }
            } else {
                Rlog.d(LOG_TAG, "pendingHoldConnection is null.");
            }
        // this means hangup active or hold and accept waiting.
        } else if (countOfIgnoredRequest == 2) {
            if (pendingHoldConnection != null) {
                try {
                    Rlog.d(LOG_TAG, "Trigger ignored hangup hold, accept waiting call request");
                    hangup(pendingHoldConnection);
                    switchWaitingOrHoldingAndActive();
                    // Do not continue processing this poll
                    // Wait for hangup and repoll
                    return;
                } catch (CallStateException ex) {
                    Rlog.d(LOG_TAG, "hangup or switching failed with exception: " + ex);
                }
            } else {
                Rlog.d(LOG_TAG, "Trigger ignored hangup active, accept waiting call request");
                hangupForegroundResumeBackground();
                // Do not continue processing this poll
                // Wait for hangup and repoll
                return;
            }
        } else if (countOfIgnoredRequest == 1) { // other cases.
            if (ignoredUpdateRequest == ImsCall.UPDATE_TERMINATE) {
                if (pendingConnection != null) {
                    try {
                        Rlog.d(LOG_TAG, "Trigger ignored terminate request.");
                        pendingConnection.hangup();
                        // Do not continue processing this poll
                        // Wait for hangup and repoll
                        return;
                    } catch (CallStateException ex) {
                        Rlog.d(LOG_TAG, "hangup failed with exception: " + ex);
                    }
                }
            } else if (ignoredUpdateRequest == ImsCall.UPDATE_HOLD ||
                       ignoredUpdateRequest == ImsCall.UPDATE_RESUME) {
                try {
                    Rlog.d(LOG_TAG, "Trigger ignored hold/resume request.");
                    switchWaitingOrHoldingAndActive();
                    // Do not continue processing this poll
                    // Wait for switching and repoll
                    return;
                } catch (CallStateException ex) {
                    Rlog.d(LOG_TAG, "switching failed with exception: " + ex);
                }
            } else if (ignoredUpdateRequest == ImsCall.UPDATE_ACCEPT) {
                try {
                    Rlog.d(LOG_TAG, "Trigger ignored accept request.");
                    acceptCall();
                    // Do not continue processing this poll
                    // Wait for accept and repoll
                    return;
                } catch (CallStateException ex) {
                    Rlog.d(LOG_TAG, "accept failed with exception: " + ex);
                }
            }
        }

        // Safety check so that obj is not stuck with mIsInEmergencyCall set to true (and data
        // disabled). This should never happen though.
        if (!isPhoneTypeGsm() && noConnectionExists) {
            checkAndEnableDataCallAfterEmergencyCallDropped();
        }

        // This is the first poll after an ATD.
        // We expect the pending call to appear in the list
        // If it does not, we land here
        if (mPendingMO != null) {
            Rlog.d(LOG_TAG, "Pending MO dropped before poll fg state:"
                    + mForegroundCall.getState());

            mDroppedDuringPoll.add(mPendingMO);
            mPendingMO = null;
            mHangupPendingMO = false;

            if (!isPhoneTypeGsm()) {
                if( mPendingCallInEcm) {
                    mPendingCallInEcm = false;
                }
                checkAndEnableDataCallAfterEmergencyCallDropped();
            }
        }

        if (newRinging != null) {
            mPhone.notifyNewRingingConnection(newRinging);
        }

        // clear the "local hangup" and "missed/rejected call"
        // cases from the "dropped during poll" list
        // These cases need no "last call fail" reason
        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;
            }
        }

        /* Disconnect any pending Handover connections */
        for (Iterator<Connection> it = mHandoverConnections.iterator();
                it.hasNext();) {
            Connection hoConnection = it.next();
            log("handlePollCalls - disconnect hoConn= " + hoConnection +
                    " hoConn.State= " + hoConnection.getState());
            if (hoConnection.getState().isRinging()) {
                hoConnection.onDisconnect(DisconnectCause.INCOMING_MISSED);
            } else {
                hoConnection.onDisconnect(DisconnectCause.NOT_VALID);
            }
            it.remove();
        }

       // for STK function SET_UP_CALL
        Intent intent = new Intent("com.android.call.CALL_STATE_CHANGED");

        // Any non-local disconnects: determine cause
        if (mDroppedDuringPoll.size() > 0) {
            mCi.getLastCallFailCause(
                obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE));
                intent.putExtra("CALL_SUCCESSFUL", false);
           } else {
                intent.putExtra("CALL_SUCCESSFUL", true);
        }
        mPhone.getContext().sendBroadcast(intent);
        if (needsPollDelay) {
            pollCallsAfterDelay();
        }

        if (mDroppedDuringPoll.size() > 0 || hasAnyCallDisconnected) {
            processEmcStatusAfterCsEnd(EmcStatus.CALL_END);
        }

        // Cases when we can no longer keep disconnected Connection's
        // with their previous calls
        // 1) the phone has started to ring
        // 2) A Call/Connection object has changed state...
        //    we may have switched or held or answered (but not hung up)
        if (newRinging != null || hasNonHangupStateChanged || hasAnyCallDisconnected) {
            internalClearDisconnected();
        }

        if (VDBG) log("handlePollCalls calling updatePhoneState()");
        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();
        }

        //dumpState();
    }

这里是通话断开事件,将connection放入mDroppedDurungPoll列表,由于断开类型DisconnectCause.LOCAL,直接调用GsmCdmaConnection的onDisconnect方法传入cause参数

16)、GsmCdmaConnection.java的onDisconnect()方法

    /**
     * Called when the radio indicates the connection has been disconnected.
     * @param cause call disconnect cause; values are defined in {@link DisconnectCause}
     */
    @Override
    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()方法 设置断开时间以及通话时长

    private void
    doDisconnect() {
        mIndex = -1;
        mDisconnectTime = System.currentTimeMillis();
        mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal;
        mDisconnected = true;
        clearPostDialListeners();
    }

最后通知注册者断开事件mOwner.getPhone().notifyDisconnect(this);

之后的流程就跟上篇讲解phone拒接流程一样,这里就不重复描述了

详见http://www.cnblogs.com/lance2016/p/6391096.html

文章参考:
Android7.0 Phone应用源码分析(三) phone拒接流程分析

http://www.cnblogs.com/lance2016/p/6582858.html

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值