TelephonyConnection.java revision 1e036c365772031b8e7054a70d973b300d7552a9
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.services.telephony;
18
19import android.content.Context;
20import android.graphics.drawable.Icon;
21import android.net.Uri;
22import android.os.AsyncResult;
23import android.os.Bundle;
24import android.os.Handler;
25import android.os.Message;
26import android.telecom.CallAudioState;
27import android.telecom.ConferenceParticipant;
28import android.telecom.Connection;
29import android.telecom.PhoneAccount;
30import android.telecom.StatusHints;
31import android.telecom.TelecomManager;
32import android.telephony.PhoneNumberUtils;
33import android.util.Pair;
34
35import com.android.ims.ImsCallProfile;
36import com.android.internal.telephony.Call;
37import com.android.internal.telephony.CallStateException;
38import com.android.internal.telephony.Connection.Capability;
39import com.android.internal.telephony.Connection.PostDialListener;
40import com.android.internal.telephony.PhoneConstants;
41import com.android.internal.telephony.gsm.SuppServiceNotification;
42
43import com.android.internal.telephony.Phone;
44import com.android.phone.R;
45
46import java.lang.Override;
47import java.util.Arrays;
48import java.util.ArrayList;
49import java.util.Collections;
50import java.util.HashMap;
51import java.util.List;
52import java.util.Map;
53import java.util.Objects;
54import java.util.Set;
55import java.util.concurrent.ConcurrentHashMap;
56
57/**
58 * Base class for CDMA and GSM connections.
59 */
60abstract class TelephonyConnection extends Connection {
61    private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
62    private static final int MSG_RINGBACK_TONE = 2;
63    private static final int MSG_HANDOVER_STATE_CHANGED = 3;
64    private static final int MSG_DISCONNECT = 4;
65    private static final int MSG_MULTIPARTY_STATE_CHANGED = 5;
66    private static final int MSG_CONFERENCE_MERGE_FAILED = 6;
67    private static final int MSG_SUPP_SERVICE_NOTIFY = 7;
68
69    /**
70     * Mappings from {@link com.android.internal.telephony.Connection} extras keys to their
71     * equivalents defined in {@link android.telecom.Connection}.
72     */
73    private static final Map<String, String> sExtrasMap = createExtrasMap();
74
75    private static final int MSG_SET_VIDEO_STATE = 8;
76    private static final int MSG_SET_VIDEO_PROVIDER = 9;
77    private static final int MSG_SET_AUDIO_QUALITY = 10;
78    private static final int MSG_SET_CONFERENCE_PARTICIPANTS = 11;
79    private static final int MSG_CONNECTION_EXTRAS_CHANGED = 12;
80    private static final int MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES = 13;
81    private static final int MSG_ON_HOLD_TONE = 14;
82
83    private final Handler mHandler = new Handler() {
84        @Override
85        public void handleMessage(Message msg) {
86            switch (msg.what) {
87                case MSG_PRECISE_CALL_STATE_CHANGED:
88                    Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED");
89                    updateState();
90                    break;
91                case MSG_HANDOVER_STATE_CHANGED:
92                    Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED");
93                    AsyncResult ar = (AsyncResult) msg.obj;
94                    com.android.internal.telephony.Connection connection =
95                         (com.android.internal.telephony.Connection) ar.result;
96                    if (mOriginalConnection != null) {
97                        if (connection != null &&
98                            ((connection.getAddress() != null &&
99                            mOriginalConnection.getAddress() != null &&
100                            mOriginalConnection.getAddress().contains(connection.getAddress())) ||
101                            connection.getState() == mOriginalConnection.getStateBeforeHandover())) {
102                            Log.d(TelephonyConnection.this,
103                                    "SettingOriginalConnection " + mOriginalConnection.toString()
104                                            + " with " + connection.toString());
105                            setOriginalConnection(connection);
106                            mWasImsConnection = false;
107                        }
108                    } else {
109                        Log.w(TelephonyConnection.this,
110                                "MSG_HANDOVER_STATE_CHANGED: mOriginalConnection==null - invalid state (not cleaned up)");
111                    }
112                    break;
113                case MSG_RINGBACK_TONE:
114                    Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE");
115                    // TODO: This code assumes that there is only one connection in the foreground
116                    // call, in other words, it punts on network-mediated conference calling.
117                    if (getOriginalConnection() != getForegroundConnection()) {
118                        Log.v(TelephonyConnection.this, "handleMessage, original connection is " +
119                                "not foreground connection, skipping");
120                        return;
121                    }
122                    setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result);
123                    break;
124                case MSG_DISCONNECT:
125                    updateState();
126                    break;
127                case MSG_MULTIPARTY_STATE_CHANGED:
128                    boolean isMultiParty = (Boolean) msg.obj;
129                    Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
130                    mIsMultiParty = isMultiParty;
131                    if (isMultiParty) {
132                        notifyConferenceStarted();
133                    }
134                case MSG_CONFERENCE_MERGE_FAILED:
135                    notifyConferenceMergeFailed();
136                    break;
137                case MSG_SUPP_SERVICE_NOTIFY:
138                    Log.v(TelephonyConnection.this, "MSG_SUPP_SERVICE_NOTIFY on phoneId : "
139                            +getPhone().getPhoneId());
140                    SuppServiceNotification mSsNotification = null;
141                    if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {
142                        mSsNotification =
143                                (SuppServiceNotification)((AsyncResult) msg.obj).result;
144                        if (mOriginalConnection != null && mSsNotification.history != null) {
145                            Bundle lastForwardedNumber = new Bundle();
146                            Log.v(TelephonyConnection.this,
147                                    "Updating call history info in extras.");
148                            lastForwardedNumber.putStringArrayList(
149                                Connection.EXTRA_LAST_FORWARDED_NUMBER,
150                                new ArrayList(Arrays.asList(mSsNotification.history)));
151                            putExtras(lastForwardedNumber);
152                        }
153                    }
154                    break;
155
156                case MSG_SET_VIDEO_STATE:
157                    int videoState = (int) msg.obj;
158                    setVideoState(videoState);
159                    break;
160
161                case MSG_SET_VIDEO_PROVIDER:
162                    VideoProvider videoProvider = (VideoProvider) msg.obj;
163                    setVideoProvider(videoProvider);
164                    break;
165
166                case MSG_SET_AUDIO_QUALITY:
167                    int audioQuality = (int) msg.obj;
168                    setAudioQuality(audioQuality);
169                    break;
170
171                case MSG_SET_CONFERENCE_PARTICIPANTS:
172                    List<ConferenceParticipant> participants = (List<ConferenceParticipant>) msg.obj;
173                    updateConferenceParticipants(participants);
174                    break;
175
176                case MSG_CONNECTION_EXTRAS_CHANGED:
177                    final Bundle extras = (Bundle) msg.obj;
178                    updateExtras(extras);
179                    break;
180
181                case MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES:
182                    setOriginalConnectionCapabilities(msg.arg1);
183                    break;
184
185                case MSG_ON_HOLD_TONE:
186                    AsyncResult asyncResult = (AsyncResult) msg.obj;
187                    Pair<com.android.internal.telephony.Connection, Boolean> heldInfo =
188                            (Pair<com.android.internal.telephony.Connection, Boolean>)
189                                    asyncResult.result;
190
191                    // Determines if the hold tone is starting or stopping.
192                    boolean playTone = ((Boolean) (heldInfo.second)).booleanValue();
193
194                    // Determine which connection the hold tone is stopping or starting for
195                    com.android.internal.telephony.Connection heldConnection = heldInfo.first;
196
197                    // Only start or stop the hold tone if this is the connection which is starting
198                    // or stopping the hold tone.
199                    if (heldConnection == mOriginalConnection) {
200                        // If starting the hold tone, send a connection event to Telecom which will
201                        // cause it to play the on hold tone.
202                        if (playTone) {
203                            sendConnectionEvent(EVENT_ON_HOLD_TONE_START, null);
204                        } else {
205                            sendConnectionEvent(EVENT_ON_HOLD_TONE_END, null);
206                        }
207                    }
208                    break;
209            }
210        }
211    };
212
213    /**
214     * A listener/callback mechanism that is specific communication from TelephonyConnections
215     * to TelephonyConnectionService (for now). It is more specific that Connection.Listener
216     * because it is only exposed in Telephony.
217     */
218    public abstract static class TelephonyConnectionListener {
219        public void onOriginalConnectionConfigured(TelephonyConnection c) {}
220    }
221
222    private final PostDialListener mPostDialListener = new PostDialListener() {
223        @Override
224        public void onPostDialWait() {
225            Log.v(TelephonyConnection.this, "onPostDialWait");
226            if (mOriginalConnection != null) {
227                setPostDialWait(mOriginalConnection.getRemainingPostDialString());
228            }
229        }
230
231        @Override
232        public void onPostDialChar(char c) {
233            Log.v(TelephonyConnection.this, "onPostDialChar: %s", c);
234            if (mOriginalConnection != null) {
235                setNextPostDialChar(c);
236            }
237        }
238    };
239
240    /**
241     * Listener for listening to events in the {@link com.android.internal.telephony.Connection}.
242     */
243    private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener =
244            new com.android.internal.telephony.Connection.ListenerBase() {
245        @Override
246        public void onVideoStateChanged(int videoState) {
247            mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState).sendToTarget();
248        }
249
250        /*
251         * The {@link com.android.internal.telephony.Connection} has reported a change in
252         * connection capability.
253         * @param capabilities bit mask containing voice or video or both capabilities.
254         */
255        @Override
256        public void onConnectionCapabilitiesChanged(int capabilities) {
257            mHandler.obtainMessage(MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES,
258                    capabilities, 0).sendToTarget();
259        }
260
261        /**
262         * The {@link com.android.internal.telephony.Connection} has reported a change in the
263         * video call provider.
264         *
265         * @param videoProvider The video call provider.
266         */
267        @Override
268        public void onVideoProviderChanged(VideoProvider videoProvider) {
269            mHandler.obtainMessage(MSG_SET_VIDEO_PROVIDER, videoProvider).sendToTarget();
270        }
271
272        /**
273         * Used by {@link com.android.internal.telephony.Connection} to report a change in whether
274         * the call is being made over a wifi network.
275         *
276         * @param isWifi True if call is made over wifi.
277         */
278        @Override
279        public void onWifiChanged(boolean isWifi) {
280            setWifi(isWifi);
281        }
282
283        /**
284         * Used by the {@link com.android.internal.telephony.Connection} to report a change in the
285         * audio quality for the current call.
286         *
287         * @param audioQuality The audio quality.
288         */
289        @Override
290        public void onAudioQualityChanged(int audioQuality) {
291            mHandler.obtainMessage(MSG_SET_AUDIO_QUALITY, audioQuality).sendToTarget();
292        }
293        /**
294         * Handles a change in the state of conference participant(s), as reported by the
295         * {@link com.android.internal.telephony.Connection}.
296         *
297         * @param participants The participant(s) which changed.
298         */
299        @Override
300        public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) {
301            mHandler.obtainMessage(MSG_SET_CONFERENCE_PARTICIPANTS, participants).sendToTarget();
302        }
303
304        /*
305         * Handles a change to the multiparty state for this connection.
306         *
307         * @param isMultiParty {@code true} if the call became multiparty, {@code false}
308         *      otherwise.
309         */
310        @Override
311        public void onMultipartyStateChanged(boolean isMultiParty) {
312            handleMultipartyStateChange(isMultiParty);
313        }
314
315        /**
316         * Handles the event that the request to merge calls failed.
317         */
318        @Override
319        public void onConferenceMergedFailed() {
320            handleConferenceMergeFailed();
321        }
322
323        @Override
324        public void onExtrasChanged(Bundle extras) {
325            mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, extras).sendToTarget();
326        }
327
328        /**
329         * Handles the phone exiting ECM mode by updating the connection capabilities.  During an
330         * ongoing call, if ECM mode is exited, we will re-enable mute for CDMA calls.
331         */
332        @Override
333        public void onExitedEcmMode() {
334            handleExitedEcmMode();
335        }
336    };
337
338    protected com.android.internal.telephony.Connection mOriginalConnection;
339    private Call.State mConnectionState = Call.State.IDLE;
340    private Bundle mOriginalConnectionExtras = new Bundle();
341    private boolean mIsStateOverridden = false;
342    private Call.State mOriginalConnectionState = Call.State.IDLE;
343    private Call.State mConnectionOverriddenState = Call.State.IDLE;
344
345    private boolean mWasImsConnection;
346
347    /**
348     * Tracks the multiparty state of the ImsCall so that changes in the bit state can be detected.
349     */
350    private boolean mIsMultiParty = false;
351
352    /**
353     * The {@link com.android.internal.telephony.Connection} capabilities associated with the
354     * current {@link #mOriginalConnection}.
355     */
356    private int mOriginalConnectionCapabilities;
357
358    /**
359     * Determines if the {@link TelephonyConnection} is using wifi.
360     * This is used when {@link TelephonyConnection#updateConnectionProperties()} is called to
361     * indicate whether a call has the {@link Connection#PROPERTY_WIFI} property.
362     */
363    private boolean mIsWifi;
364
365    /**
366     * Determines the audio quality is high for the {@link TelephonyConnection}.
367     * This is used when {@link TelephonyConnection#updateConnectionProperties}} is called to
368     * indicate whether a call has the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property.
369     */
370    private boolean mHasHighDefAudio;
371
372    /**
373     * Indicates that the connection should be treated as an emergency call because the
374     * number dialed matches an internal list of emergency numbers. Does not guarantee whether
375     * the network will treat the call as an emergency call.
376     */
377    private boolean mTreatAsEmergencyCall;
378
379    /**
380     * For video calls, indicates whether the outgoing video for the call can be paused using
381     * the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState.
382     */
383    private boolean mIsVideoPauseSupported;
384
385    /**
386     * Indicates whether this connection supports being a part of a conference..
387     */
388    private boolean mIsConferenceSupported;
389
390    /**
391     * Listeners to our TelephonyConnection specific callbacks
392     */
393    private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
394            new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1));
395
396    protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection,
397            String callId) {
398        setTelecomCallId(callId);
399        if (originalConnection != null) {
400            setOriginalConnection(originalConnection);
401        }
402    }
403
404    /**
405     * Creates a clone of the current {@link TelephonyConnection}.
406     *
407     * @return The clone.
408     */
409    public abstract TelephonyConnection cloneConnection();
410
411    @Override
412    public void onCallAudioStateChanged(CallAudioState audioState) {
413        // TODO: update TTY mode.
414        if (getPhone() != null) {
415            getPhone().setEchoSuppressionEnabled();
416        }
417    }
418
419    @Override
420    public void onStateChanged(int state) {
421        Log.v(this, "onStateChanged, state: " + Connection.stateToString(state));
422        updateStatusHints();
423    }
424
425    @Override
426    public void onDisconnect() {
427        Log.v(this, "onDisconnect");
428        hangup(android.telephony.DisconnectCause.LOCAL);
429    }
430
431    /**
432     * Notifies this Connection of a request to disconnect a participant of the conference managed
433     * by the connection.
434     *
435     * @param endpoint the {@link Uri} of the participant to disconnect.
436     */
437    @Override
438    public void onDisconnectConferenceParticipant(Uri endpoint) {
439        Log.v(this, "onDisconnectConferenceParticipant %s", endpoint);
440
441        if (mOriginalConnection == null) {
442            return;
443        }
444
445        mOriginalConnection.onDisconnectConferenceParticipant(endpoint);
446    }
447
448    @Override
449    public void onSeparate() {
450        Log.v(this, "onSeparate");
451        if (mOriginalConnection != null) {
452            try {
453                mOriginalConnection.separate();
454            } catch (CallStateException e) {
455                Log.e(this, e, "Call to Connection.separate failed with exception");
456            }
457        }
458    }
459
460    @Override
461    public void onAbort() {
462        Log.v(this, "onAbort");
463        hangup(android.telephony.DisconnectCause.LOCAL);
464    }
465
466    @Override
467    public void onHold() {
468        performHold();
469    }
470
471    @Override
472    public void onUnhold() {
473        performUnhold();
474    }
475
476    @Override
477    public void onAnswer(int videoState) {
478        Log.v(this, "onAnswer");
479        if (isValidRingingCall() && getPhone() != null) {
480            try {
481                getPhone().acceptCall(videoState);
482            } catch (CallStateException e) {
483                Log.e(this, e, "Failed to accept call.");
484            }
485        }
486    }
487
488    @Override
489    public void onReject() {
490        Log.v(this, "onReject");
491        if (isValidRingingCall()) {
492            hangup(android.telephony.DisconnectCause.INCOMING_REJECTED);
493        }
494        super.onReject();
495    }
496
497    @Override
498    public void onPostDialContinue(boolean proceed) {
499        Log.v(this, "onPostDialContinue, proceed: " + proceed);
500        if (mOriginalConnection != null) {
501            if (proceed) {
502                mOriginalConnection.proceedAfterWaitChar();
503            } else {
504                mOriginalConnection.cancelPostDial();
505            }
506        }
507    }
508
509    public void performHold() {
510        Log.v(this, "performHold");
511        // TODO: Can dialing calls be put on hold as well since they take up the
512        // foreground call slot?
513        if (Call.State.ACTIVE == mConnectionState) {
514            Log.v(this, "Holding active call");
515            try {
516                Phone phone = mOriginalConnection.getCall().getPhone();
517                Call ringingCall = phone.getRingingCall();
518
519                // Although the method says switchHoldingAndActive, it eventually calls a RIL method
520                // called switchWaitingOrHoldingAndActive. What this means is that if we try to put
521                // a call on hold while a call-waiting call exists, it'll end up accepting the
522                // call-waiting call, which is bad if that was not the user's intention. We are
523                // cheating here and simply skipping it because we know any attempt to hold a call
524                // while a call-waiting call is happening is likely a request from Telecom prior to
525                // accepting the call-waiting call.
526                // TODO: Investigate a better solution. It would be great here if we
527                // could "fake" hold by silencing the audio and microphone streams for this call
528                // instead of actually putting it on hold.
529                if (ringingCall.getState() != Call.State.WAITING) {
530                    phone.switchHoldingAndActive();
531                }
532
533                // TODO: Cdma calls are slightly different.
534            } catch (CallStateException e) {
535                Log.e(this, e, "Exception occurred while trying to put call on hold.");
536            }
537        } else {
538            Log.w(this, "Cannot put a call that is not currently active on hold.");
539        }
540    }
541
542    public void performUnhold() {
543        Log.v(this, "performUnhold");
544        if (Call.State.HOLDING == mConnectionState) {
545            try {
546                // Here's the deal--Telephony hold/unhold is weird because whenever there exists
547                // more than one call, one of them must always be active. In other words, if you
548                // have an active call and holding call, and you put the active call on hold, it
549                // will automatically activate the holding call. This is weird with how Telecom
550                // sends its commands. When a user opts to "unhold" a background call, telecom
551                // issues hold commands to all active calls, and then the unhold command to the
552                // background call. This means that we get two commands...each of which reduces to
553                // switchHoldingAndActive(). The result is that they simply cancel each other out.
554                // To fix this so that it works well with telecom we add a minor hack. If we
555                // have one telephony call, everything works as normally expected. But if we have
556                // two or more calls, we will ignore all requests to "unhold" knowing that the hold
557                // requests already do what we want. If you've read up to this point, I'm very sorry
558                // that we are doing this. I didn't think of a better solution that wouldn't also
559                // make the Telecom APIs very ugly.
560
561                if (!hasMultipleTopLevelCalls()) {
562                    mOriginalConnection.getCall().getPhone().switchHoldingAndActive();
563                } else {
564                    Log.i(this, "Skipping unhold command for %s", this);
565                }
566            } catch (CallStateException e) {
567                Log.e(this, e, "Exception occurred while trying to release call from hold.");
568            }
569        } else {
570            Log.w(this, "Cannot release a call that is not already on hold from hold.");
571        }
572    }
573
574    public void performConference(TelephonyConnection otherConnection) {
575        Log.d(this, "performConference - %s", this);
576        if (getPhone() != null) {
577            try {
578                // We dont use the "other" connection because there is no concept of that in the
579                // implementation of calls inside telephony. Basically, you can "conference" and it
580                // will conference with the background call.  We know that otherConnection is the
581                // background call because it would never have called setConferenceableConnections()
582                // otherwise.
583                getPhone().conference();
584            } catch (CallStateException e) {
585                Log.e(this, e, "Failed to conference call.");
586            }
587        }
588    }
589
590    /**
591     * Builds connection capabilities common to all TelephonyConnections. Namely, apply IMS-based
592     * capabilities.
593     */
594    protected int buildConnectionCapabilities() {
595        int callCapabilities = 0;
596        if (mOriginalConnection != null && mOriginalConnection.isIncoming()) {
597            callCapabilities |= CAPABILITY_SPEED_UP_MT_AUDIO;
598        }
599        if (isImsConnection()) {
600            if (!shouldTreatAsEmergencyCall()) {
601                callCapabilities |= CAPABILITY_SUPPORT_HOLD;
602                if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) {
603                    callCapabilities |= CAPABILITY_HOLD;
604                }
605            }
606        }
607
608        return callCapabilities;
609    }
610
611    protected final void updateConnectionCapabilities() {
612        int newCapabilities = buildConnectionCapabilities();
613
614        newCapabilities = applyOriginalConnectionCapabilities(newCapabilities);
615        newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PAUSE_VIDEO,
616                mIsVideoPauseSupported && isVideoCapable());
617        newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PULL_CALL,
618                isExternalConnection() && isPullable());
619        newCapabilities = applyConferenceTerminationCapabilities(newCapabilities);
620
621        if (getConnectionCapabilities() != newCapabilities) {
622            setConnectionCapabilities(newCapabilities);
623        }
624    }
625
626    protected int buildConnectionProperties() {
627        int connectionProperties = 0;
628
629        // If the phone is in ECM mode, mark the call to indicate that the callback number should be
630        // shown.
631        Phone phone = getPhone();
632        if (phone != null && phone.isInEcm()) {
633            connectionProperties |= PROPERTY_SHOW_CALLBACK_NUMBER;
634        }
635
636        return connectionProperties;
637    }
638
639    /**
640     * Updates the properties of the connection.
641     */
642    protected final void updateConnectionProperties() {
643        int newProperties = buildConnectionProperties();
644
645        newProperties = changeBitmask(newProperties, PROPERTY_HIGH_DEF_AUDIO, mHasHighDefAudio);
646        newProperties = changeBitmask(newProperties, PROPERTY_WIFI, mIsWifi);
647        newProperties = changeBitmask(newProperties, PROPERTY_IS_EXTERNAL_CALL,
648                isExternalConnection());
649
650        if (getConnectionProperties() != newProperties) {
651            setConnectionProperties(newProperties);
652        }
653    }
654
655    protected final void updateAddress() {
656        updateConnectionCapabilities();
657        updateConnectionProperties();
658        if (mOriginalConnection != null) {
659            Uri address = getAddressFromNumber(mOriginalConnection.getAddress());
660            int presentation = mOriginalConnection.getNumberPresentation();
661            if (!Objects.equals(address, getAddress()) ||
662                    presentation != getAddressPresentation()) {
663                Log.v(this, "updateAddress, address changed");
664                setAddress(address, presentation);
665            }
666
667            String name = mOriginalConnection.getCnapName();
668            int namePresentation = mOriginalConnection.getCnapNamePresentation();
669            if (!Objects.equals(name, getCallerDisplayName()) ||
670                    namePresentation != getCallerDisplayNamePresentation()) {
671                Log.v(this, "updateAddress, caller display name changed");
672                setCallerDisplayName(name, namePresentation);
673            }
674
675            if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) {
676                mTreatAsEmergencyCall = true;
677            }
678        }
679    }
680
681    void onRemovedFromCallService() {
682        // Subclass can override this to do cleanup.
683    }
684
685    void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
686        Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
687        clearOriginalConnection();
688        mOriginalConnectionExtras.clear();
689        mOriginalConnection = originalConnection;
690        mOriginalConnection.setTelecomCallId(getTelecomCallId());
691        getPhone().registerForPreciseCallStateChanged(
692                mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
693        getPhone().registerForHandoverStateChanged(
694                mHandler, MSG_HANDOVER_STATE_CHANGED, null);
695        getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
696        getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null);
697        getPhone().registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null);
698        getPhone().registerForOnHoldTone(mHandler, MSG_ON_HOLD_TONE, null);
699        mOriginalConnection.addPostDialListener(mPostDialListener);
700        mOriginalConnection.addListener(mOriginalConnectionListener);
701
702        // Set video state and capabilities
703        setVideoState(mOriginalConnection.getVideoState());
704        setOriginalConnectionCapabilities(mOriginalConnection.getConnectionCapabilities());
705        setWifi(mOriginalConnection.isWifi());
706        setVideoProvider(mOriginalConnection.getVideoProvider());
707        setAudioQuality(mOriginalConnection.getAudioQuality());
708        setTechnologyTypeExtra();
709
710        // Post update of extras to the handler; extras are updated via the handler to ensure thread
711        // safety.
712        mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED,
713                mOriginalConnection.getConnectionExtras()).sendToTarget();
714
715        if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) {
716            mTreatAsEmergencyCall = true;
717        }
718
719        if (isImsConnection()) {
720            mWasImsConnection = true;
721        }
722        mIsMultiParty = mOriginalConnection.isMultiparty();
723
724        // updateState can set mOriginalConnection to null if its state is DISCONNECTED, so this
725        // should be executed *after* the above setters have run.
726        updateState();
727        if (mOriginalConnection == null) {
728            Log.w(this, "original Connection was nulled out as part of setOriginalConnection. " +
729                    originalConnection);
730        }
731
732        fireOnOriginalConnectionConfigured();
733    }
734
735    /**
736     * Sets the EXTRA_CALL_TECHNOLOGY_TYPE extra on the connection to report back to Telecom.
737     */
738    private void setTechnologyTypeExtra() {
739        if (getPhone() != null) {
740            putExtra(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, getPhone().getPhoneType());
741        }
742    }
743
744    /**
745     * Whether the connection should be treated as an emergency.
746     * @return {@code true} if the connection should be treated as an emergency call based
747     * on the number dialed, {@code false} otherwise.
748     */
749    protected boolean shouldTreatAsEmergencyCall() {
750        return mTreatAsEmergencyCall;
751    }
752
753    /**
754     * Un-sets the underlying radio connection.
755     */
756    void clearOriginalConnection() {
757        if (mOriginalConnection != null) {
758            if (getPhone() != null) {
759                getPhone().unregisterForPreciseCallStateChanged(mHandler);
760                getPhone().unregisterForRingbackTone(mHandler);
761                getPhone().unregisterForHandoverStateChanged(mHandler);
762                getPhone().unregisterForDisconnect(mHandler);
763                getPhone().unregisterForSuppServiceNotification(mHandler);
764                getPhone().unregisterForOnHoldTone(mHandler);
765            }
766            mOriginalConnection.removePostDialListener(mPostDialListener);
767            mOriginalConnection.removeListener(mOriginalConnectionListener);
768            mOriginalConnection = null;
769        }
770    }
771
772    protected void hangup(int telephonyDisconnectCode) {
773        if (mOriginalConnection != null) {
774            try {
775                // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
776                // connection.hangup(). Without this change, the party originating the call will not
777                // get sent to voicemail if the user opts to reject the call.
778                if (isValidRingingCall()) {
779                    Call call = getCall();
780                    if (call != null) {
781                        call.hangup();
782                    } else {
783                        Log.w(this, "Attempting to hangup a connection without backing call.");
784                    }
785                } else {
786                    // We still prefer to call connection.hangup() for non-ringing calls in order
787                    // to support hanging-up specific calls within a conference call. If we invoked
788                    // call.hangup() while in a conference, we would end up hanging up the entire
789                    // conference call instead of the specific connection.
790                    mOriginalConnection.hangup();
791                }
792            } catch (CallStateException e) {
793                Log.e(this, e, "Call to Connection.hangup failed with exception");
794            }
795        }
796    }
797
798    com.android.internal.telephony.Connection getOriginalConnection() {
799        return mOriginalConnection;
800    }
801
802    protected Call getCall() {
803        if (mOriginalConnection != null) {
804            return mOriginalConnection.getCall();
805        }
806        return null;
807    }
808
809    Phone getPhone() {
810        Call call = getCall();
811        if (call != null) {
812            return call.getPhone();
813        }
814        return null;
815    }
816
817    private boolean hasMultipleTopLevelCalls() {
818        int numCalls = 0;
819        Phone phone = getPhone();
820        if (phone != null) {
821            if (!phone.getRingingCall().isIdle()) {
822                numCalls++;
823            }
824            if (!phone.getForegroundCall().isIdle()) {
825                numCalls++;
826            }
827            if (!phone.getBackgroundCall().isIdle()) {
828                numCalls++;
829            }
830        }
831        return numCalls > 1;
832    }
833
834    private com.android.internal.telephony.Connection getForegroundConnection() {
835        if (getPhone() != null) {
836            return getPhone().getForegroundCall().getEarliestConnection();
837        }
838        return null;
839    }
840
841     /**
842     * Checks for and returns the list of conference participants
843     * associated with this connection.
844     */
845    public List<ConferenceParticipant> getConferenceParticipants() {
846        if (mOriginalConnection == null) {
847            Log.v(this, "Null mOriginalConnection, cannot get conf participants.");
848            return null;
849        }
850        return mOriginalConnection.getConferenceParticipants();
851    }
852
853    /**
854     * Checks to see the original connection corresponds to an active incoming call. Returns false
855     * if there is no such actual call, or if the associated call is not incoming (See
856     * {@link Call.State#isRinging}).
857     */
858    private boolean isValidRingingCall() {
859        if (getPhone() == null) {
860            Log.v(this, "isValidRingingCall, phone is null");
861            return false;
862        }
863
864        Call ringingCall = getPhone().getRingingCall();
865        if (!ringingCall.getState().isRinging()) {
866            Log.v(this, "isValidRingingCall, ringing call is not in ringing state");
867            return false;
868        }
869
870        if (ringingCall.getEarliestConnection() != mOriginalConnection) {
871            Log.v(this, "isValidRingingCall, ringing call connection does not match");
872            return false;
873        }
874
875        Log.v(this, "isValidRingingCall, returning true");
876        return true;
877    }
878
879    protected void updateExtras(Bundle extras) {
880        if (mOriginalConnection != null) {
881            if (extras != null) {
882                // Check if extras have changed and need updating.
883                if (!areBundlesEqual(mOriginalConnectionExtras, extras)) {
884                    if (Log.DEBUG) {
885                        Log.d(TelephonyConnection.this, "Updating extras:");
886                        for (String key : extras.keySet()) {
887                            Object value = extras.get(key);
888                            if (value instanceof String) {
889                                Log.d(this, "updateExtras Key=" + Log.pii(key) +
890                                             " value=" + Log.pii((String)value));
891                            }
892                        }
893                    }
894                    mOriginalConnectionExtras.clear();
895
896                    mOriginalConnectionExtras.putAll(extras);
897
898                    // Remap any string extras that have a remapping defined.
899                    for (String key : mOriginalConnectionExtras.keySet()) {
900                        if (sExtrasMap.containsKey(key)) {
901                            String newKey = sExtrasMap.get(key);
902                            mOriginalConnectionExtras.putString(newKey, extras.getString(key));
903                            mOriginalConnectionExtras.remove(key);
904                        }
905                    }
906
907                    // Ensure extras are propagated to Telecom.
908                    putExtras(mOriginalConnectionExtras);
909                } else {
910                    Log.d(this, "Extras update not required");
911                }
912            } else {
913                Log.d(this, "updateExtras extras: " + Log.pii(extras));
914            }
915        }
916    }
917
918    private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
919        if (extras == null || newExtras == null) {
920            return extras == newExtras;
921        }
922
923        if (extras.size() != newExtras.size()) {
924            return false;
925        }
926
927        for(String key : extras.keySet()) {
928            if (key != null) {
929                final Object value = extras.get(key);
930                final Object newValue = newExtras.get(key);
931                if (!Objects.equals(value, newValue)) {
932                    return false;
933                }
934            }
935        }
936        return true;
937    }
938
939    void setStateOverride(Call.State state) {
940        mIsStateOverridden = true;
941        mConnectionOverriddenState = state;
942        // Need to keep track of the original connection's state before override.
943        mOriginalConnectionState = mOriginalConnection.getState();
944        updateStateInternal();
945    }
946
947    void resetStateOverride() {
948        mIsStateOverridden = false;
949        updateStateInternal();
950    }
951
952    void updateStateInternal() {
953        if (mOriginalConnection == null) {
954            return;
955        }
956        Call.State newState;
957        // If the state is overridden and the state of the original connection hasn't changed since,
958        // then we continue in the overridden state, else we go to the original connection's state.
959        if (mIsStateOverridden && mOriginalConnectionState == mOriginalConnection.getState()) {
960            newState = mConnectionOverriddenState;
961        } else {
962            newState = mOriginalConnection.getState();
963        }
964        Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState, this);
965
966        if (mConnectionState != newState) {
967            mConnectionState = newState;
968            switch (newState) {
969                case IDLE:
970                    break;
971                case ACTIVE:
972                    setActiveInternal();
973                    break;
974                case HOLDING:
975                    setOnHold();
976                    break;
977                case DIALING:
978                case ALERTING:
979                    setDialing();
980                    break;
981                case INCOMING:
982                case WAITING:
983                    setRinging();
984                    break;
985                case DISCONNECTED:
986                    setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
987                            mOriginalConnection.getDisconnectCause(),
988                            mOriginalConnection.getVendorDisconnectCause()));
989                    close();
990                    break;
991                case DISCONNECTING:
992                    break;
993            }
994        }
995    }
996
997    void updateState() {
998        if (mOriginalConnection == null) {
999            return;
1000        }
1001
1002        updateStateInternal();
1003        updateStatusHints();
1004        updateConnectionCapabilities();
1005        updateConnectionProperties();
1006        updateAddress();
1007        updateMultiparty();
1008    }
1009
1010    /**
1011     * Checks for changes to the multiparty bit.  If a conference has started, informs listeners.
1012     */
1013    private void updateMultiparty() {
1014        if (mOriginalConnection == null) {
1015            return;
1016        }
1017
1018        if (mIsMultiParty != mOriginalConnection.isMultiparty()) {
1019            mIsMultiParty = mOriginalConnection.isMultiparty();
1020
1021            if (mIsMultiParty) {
1022                notifyConferenceStarted();
1023            }
1024        }
1025    }
1026
1027    /**
1028     * Handles a failure when merging calls into a conference.
1029     * {@link com.android.internal.telephony.Connection.Listener#onConferenceMergedFailed()}
1030     * listener.
1031     */
1032    private void handleConferenceMergeFailed(){
1033        mHandler.obtainMessage(MSG_CONFERENCE_MERGE_FAILED).sendToTarget();
1034    }
1035
1036    /**
1037     * Handles requests to update the multiparty state received via the
1038     * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)}
1039     * listener.
1040     * <p>
1041     * Note: We post this to the mHandler to ensure that if a conference must be created as a
1042     * result of the multiparty state change, the conference creation happens on the correct
1043     * thread.  This ensures that the thread check in
1044     * {@link com.android.internal.telephony.Phone#checkCorrectThread(android.os.Handler)}
1045     * does not fire.
1046     *
1047     * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise.
1048     */
1049    private void handleMultipartyStateChange(boolean isMultiParty) {
1050        Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
1051        mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget();
1052    }
1053
1054    private void setActiveInternal() {
1055        if (getState() == STATE_ACTIVE) {
1056            Log.w(this, "Should not be called if this is already ACTIVE");
1057            return;
1058        }
1059
1060        // When we set a call to active, we need to make sure that there are no other active
1061        // calls. However, the ordering of state updates to connections can be non-deterministic
1062        // since all connections register for state changes on the phone independently.
1063        // To "optimize", we check here to see if there already exists any active calls.  If so,
1064        // we issue an update for those calls first to make sure we only have one top-level
1065        // active call.
1066        if (getConnectionService() != null) {
1067            for (Connection current : getConnectionService().getAllConnections()) {
1068                if (current != this && current instanceof TelephonyConnection) {
1069                    TelephonyConnection other = (TelephonyConnection) current;
1070                    if (other.getState() == STATE_ACTIVE) {
1071                        other.updateState();
1072                    }
1073                }
1074            }
1075        }
1076        setActive();
1077    }
1078
1079    private void close() {
1080        Log.v(this, "close");
1081        clearOriginalConnection();
1082        destroy();
1083    }
1084
1085    /**
1086     * Determines if the current connection is video capable.
1087     *
1088     * A connection is deemed to be video capable if the original connection capabilities state that
1089     * both local and remote video is supported.
1090     *
1091     * @return {@code true} if the connection is video capable, {@code false} otherwise.
1092     */
1093    private boolean isVideoCapable() {
1094        return can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL)
1095                && can(mOriginalConnectionCapabilities,
1096                Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
1097    }
1098
1099    /**
1100     * Determines if the current connection is an external connection.
1101     *
1102     * A connection is deemed to be external if the original connection capabilities state that it
1103     * is.
1104     *
1105     * @return {@code true} if the connection is external, {@code false} otherwise.
1106     */
1107    private boolean isExternalConnection() {
1108        return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION)
1109                && can(mOriginalConnectionCapabilities,
1110                Capability.IS_EXTERNAL_CONNECTION);
1111    }
1112
1113    /**
1114     * Determines if the current connection is pullable.
1115     *
1116     * A connection is deemed to be pullable if the original connection capabilities state that it
1117     * is.
1118     *
1119     * @return {@code true} if the connection is pullable, {@code false} otherwise.
1120     */
1121    private boolean isPullable() {
1122        return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION)
1123                && can(mOriginalConnectionCapabilities, Capability.IS_PULLABLE);
1124    }
1125
1126    /**
1127     * Applies capabilities specific to conferences termination to the
1128     * {@code ConnectionCapabilities} bit-mask.
1129     *
1130     * @param capabilities The {@code ConnectionCapabilities} bit-mask.
1131     * @return The capabilities with the IMS conference capabilities applied.
1132     */
1133    private int applyConferenceTerminationCapabilities(int capabilities) {
1134        int currentCapabilities = capabilities;
1135
1136        // An IMS call cannot be individually disconnected or separated from its parent conference.
1137        // If the call was IMS, even if it hands over to GMS, these capabilities are not supported.
1138        if (!mWasImsConnection) {
1139            currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE;
1140            currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE;
1141        }
1142
1143        return currentCapabilities;
1144    }
1145
1146    /**
1147     * Stores the new original connection capabilities, and applies them to the current connection,
1148     * notifying any listeners as necessary.
1149     *
1150     * @param connectionCapabilities The original connection capabilties.
1151     */
1152    public void setOriginalConnectionCapabilities(int connectionCapabilities) {
1153        mOriginalConnectionCapabilities = connectionCapabilities;
1154        updateConnectionCapabilities();
1155        updateConnectionProperties();
1156    }
1157
1158    /**
1159     * Called to apply the capabilities present in the {@link #mOriginalConnection} to this
1160     * {@link Connection}.  Provides a mapping between the capabilities present in the original
1161     * connection (see {@link com.android.internal.telephony.Connection.Capability}) and those in
1162     * this {@link Connection}.
1163     *
1164     * @param capabilities The capabilities bitmask from the {@link Connection}.
1165     * @return the capabilities bitmask with the original connection capabilities remapped and
1166     *      applied.
1167     */
1168    public int applyOriginalConnectionCapabilities(int capabilities) {
1169        // We only support downgrading to audio if both the remote and local side support
1170        // downgrading to audio.
1171        boolean supportsDowngradeToAudio = can(mOriginalConnectionCapabilities,
1172                Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL |
1173                        Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE);
1174        capabilities = changeBitmask(capabilities,
1175                CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO, !supportsDowngradeToAudio);
1176
1177        capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
1178                can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL));
1179
1180        capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
1181                can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
1182
1183        return capabilities;
1184    }
1185
1186    /**
1187     * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset
1188     * the {@link Connection#PROPERTY_WIFI} property.
1189     */
1190    public void setWifi(boolean isWifi) {
1191        mIsWifi = isWifi;
1192        updateConnectionProperties();
1193        updateStatusHints();
1194    }
1195
1196    /**
1197     * Whether the call is using wifi.
1198     */
1199    boolean isWifi() {
1200        return mIsWifi;
1201    }
1202
1203    /**
1204     * Sets the current call audio quality. Used during rebuild of the properties
1205     * to set or unset the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property.
1206     *
1207     * @param audioQuality The audio quality.
1208     */
1209    public void setAudioQuality(int audioQuality) {
1210        mHasHighDefAudio = audioQuality ==
1211                com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION;
1212        updateConnectionProperties();
1213    }
1214
1215    void resetStateForConference() {
1216        if (getState() == Connection.STATE_HOLDING) {
1217            resetStateOverride();
1218        }
1219    }
1220
1221    boolean setHoldingForConference() {
1222        if (getState() == Connection.STATE_ACTIVE) {
1223            setStateOverride(Call.State.HOLDING);
1224            return true;
1225        }
1226        return false;
1227    }
1228
1229    /**
1230     * For video calls, sets whether this connection supports pausing the outgoing video for the
1231     * call using the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState.
1232     *
1233     * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise.
1234     */
1235    public void setVideoPauseSupported(boolean isVideoPauseSupported) {
1236        mIsVideoPauseSupported = isVideoPauseSupported;
1237    }
1238
1239    /**
1240     * Sets whether this connection supports conference calling.
1241     * @param isConferenceSupported {@code true} if conference calling is supported by this
1242     *                                         connection, {@code false} otherwise.
1243     */
1244    public void setConferenceSupported(boolean isConferenceSupported) {
1245        mIsConferenceSupported = isConferenceSupported;
1246    }
1247
1248    /**
1249     * @return {@code true} if this connection supports merging calls into a conference.
1250     */
1251    public boolean isConferenceSupported() {
1252        return mIsConferenceSupported;
1253    }
1254
1255    /**
1256     * Whether the original connection is an IMS connection.
1257     * @return {@code True} if the original connection is an IMS connection, {@code false}
1258     *     otherwise.
1259     */
1260    protected boolean isImsConnection() {
1261        com.android.internal.telephony.Connection originalConnection = getOriginalConnection();
1262        return originalConnection != null &&
1263                originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
1264    }
1265
1266    /**
1267     * Whether the original connection was ever an IMS connection, either before or now.
1268     * @return {@code True} if the original connection was ever an IMS connection, {@code false}
1269     *     otherwise.
1270     */
1271    public boolean wasImsConnection() {
1272        return mWasImsConnection;
1273    }
1274
1275    private static Uri getAddressFromNumber(String number) {
1276        // Address can be null for blocked calls.
1277        if (number == null) {
1278            number = "";
1279        }
1280        return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
1281    }
1282
1283    /**
1284     * Changes a capabilities bit-mask to add or remove a capability.
1285     *
1286     * @param bitmask The bit-mask.
1287     * @param bitfield The bit-field to change.
1288     * @param enabled Whether the bit-field should be set or removed.
1289     * @return The bit-mask with the bit-field changed.
1290     */
1291    private int changeBitmask(int bitmask, int bitfield, boolean enabled) {
1292        if (enabled) {
1293            return bitmask | bitfield;
1294        } else {
1295            return bitmask & ~bitfield;
1296        }
1297    }
1298
1299    private void updateStatusHints() {
1300        boolean isIncoming = isValidRingingCall();
1301        if (mIsWifi && (isIncoming || getState() == STATE_ACTIVE)) {
1302            int labelId = isIncoming
1303                    ? R.string.status_hint_label_incoming_wifi_call
1304                    : R.string.status_hint_label_wifi_call;
1305
1306            Context context = getPhone().getContext();
1307            setStatusHints(new StatusHints(
1308                    context.getString(labelId),
1309                    Icon.createWithResource(
1310                            context.getResources(),
1311                            R.drawable.ic_signal_wifi_4_bar_24dp),
1312                    null /* extras */));
1313        } else {
1314            setStatusHints(null);
1315        }
1316    }
1317
1318    /**
1319     * Register a listener for {@link TelephonyConnection} specific triggers.
1320     * @param l The instance of the listener to add
1321     * @return The connection being listened to
1322     */
1323    public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) {
1324        mTelephonyListeners.add(l);
1325        // If we already have an original connection, let's call back immediately.
1326        // This would be the case for incoming calls.
1327        if (mOriginalConnection != null) {
1328            fireOnOriginalConnectionConfigured();
1329        }
1330        return this;
1331    }
1332
1333    /**
1334     * Remove a listener for {@link TelephonyConnection} specific triggers.
1335     * @param l The instance of the listener to remove
1336     * @return The connection being listened to
1337     */
1338    public final TelephonyConnection removeTelephonyConnectionListener(
1339            TelephonyConnectionListener l) {
1340        if (l != null) {
1341            mTelephonyListeners.remove(l);
1342        }
1343        return this;
1344    }
1345
1346    /**
1347     * Fire a callback to the various listeners for when the original connection is
1348     * set in this {@link TelephonyConnection}
1349     */
1350    private final void fireOnOriginalConnectionConfigured() {
1351        for (TelephonyConnectionListener l : mTelephonyListeners) {
1352            l.onOriginalConnectionConfigured(this);
1353        }
1354    }
1355
1356    /**
1357     * Handles exiting ECM mode.
1358     */
1359    protected void handleExitedEcmMode() {
1360        updateConnectionProperties();
1361    }
1362
1363    /**
1364     * Provides a mapping from extras keys which may be found in the
1365     * {@link com.android.internal.telephony.Connection} to their equivalents defined in
1366     * {@link android.telecom.Connection}.
1367     *
1368     * @return Map containing key mappings.
1369     */
1370    private static Map<String, String> createExtrasMap() {
1371        Map<String, String> result = new HashMap<String, String>();
1372        result.put(ImsCallProfile.EXTRA_CHILD_NUMBER,
1373                android.telecom.Connection.EXTRA_CHILD_ADDRESS);
1374        result.put(ImsCallProfile.EXTRA_DISPLAY_TEXT,
1375                android.telecom.Connection.EXTRA_CALL_SUBJECT);
1376        return Collections.unmodifiableMap(result);
1377    }
1378
1379    /**
1380     * Creates a string representation of this {@link TelephonyConnection}.  Primarily intended for
1381     * use in log statements.
1382     *
1383     * @return String representation of the connection.
1384     */
1385    @Override
1386    public String toString() {
1387        StringBuilder sb = new StringBuilder();
1388        sb.append("[TelephonyConnection objId:");
1389        sb.append(System.identityHashCode(this));
1390        sb.append(" telecomCallID:");
1391        sb.append(getTelecomCallId());
1392        sb.append(" type:");
1393        if (isImsConnection()) {
1394            sb.append("ims");
1395        } else if (this instanceof com.android.services.telephony.GsmConnection) {
1396            sb.append("gsm");
1397        } else if (this instanceof CdmaConnection) {
1398            sb.append("cdma");
1399        }
1400        sb.append(" state:");
1401        sb.append(Connection.stateToString(getState()));
1402        sb.append(" capabilities:");
1403        sb.append(capabilitiesToString(getConnectionCapabilities()));
1404        sb.append(" properties:");
1405        sb.append(propertiesToString(getConnectionProperties()));
1406        sb.append(" address:");
1407        sb.append(Log.pii(getAddress()));
1408        sb.append(" originalConnection:");
1409        sb.append(mOriginalConnection);
1410        sb.append(" partOfConf:");
1411        if (getConference() == null) {
1412            sb.append("N");
1413        } else {
1414            sb.append("Y");
1415        }
1416        sb.append("]");
1417        return sb.toString();
1418    }
1419}
1420