TelephonyConnection.java revision 5046bc73a9bb72c6f6f9f913def0c71d551da62d
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 (isImsConnection()) {
597            if (mOriginalConnection.isIncoming()) {
598                callCapabilities |= CAPABILITY_SPEED_UP_MT_AUDIO;
599            }
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            if (mOriginalConnection.getConnectionExtras() == null) {
741                Bundle b = new Bundle();
742                b.putInt(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, getPhone().getPhoneType());
743                mOriginalConnection.setConnectionExtras(b);
744            } else {
745                mOriginalConnection.getConnectionExtras().putInt(
746                        TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, getPhone().getPhoneType());
747            }
748        }
749    }
750
751    /**
752     * Whether the connection should be treated as an emergency.
753     * @return {@code true} if the connection should be treated as an emergency call based
754     * on the number dialed, {@code false} otherwise.
755     */
756    protected boolean shouldTreatAsEmergencyCall() {
757        return mTreatAsEmergencyCall;
758    }
759
760    /**
761     * Un-sets the underlying radio connection.
762     */
763    void clearOriginalConnection() {
764        if (mOriginalConnection != null) {
765            if (getPhone() != null) {
766                getPhone().unregisterForPreciseCallStateChanged(mHandler);
767                getPhone().unregisterForRingbackTone(mHandler);
768                getPhone().unregisterForHandoverStateChanged(mHandler);
769                getPhone().unregisterForDisconnect(mHandler);
770                getPhone().unregisterForSuppServiceNotification(mHandler);
771                getPhone().unregisterForOnHoldTone(mHandler);
772            }
773            mOriginalConnection.removePostDialListener(mPostDialListener);
774            mOriginalConnection.removeListener(mOriginalConnectionListener);
775            mOriginalConnection = null;
776        }
777    }
778
779    protected void hangup(int telephonyDisconnectCode) {
780        if (mOriginalConnection != null) {
781            try {
782                // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
783                // connection.hangup(). Without this change, the party originating the call will not
784                // get sent to voicemail if the user opts to reject the call.
785                if (isValidRingingCall()) {
786                    Call call = getCall();
787                    if (call != null) {
788                        call.hangup();
789                    } else {
790                        Log.w(this, "Attempting to hangup a connection without backing call.");
791                    }
792                } else {
793                    // We still prefer to call connection.hangup() for non-ringing calls in order
794                    // to support hanging-up specific calls within a conference call. If we invoked
795                    // call.hangup() while in a conference, we would end up hanging up the entire
796                    // conference call instead of the specific connection.
797                    mOriginalConnection.hangup();
798                }
799            } catch (CallStateException e) {
800                Log.e(this, e, "Call to Connection.hangup failed with exception");
801            }
802        }
803    }
804
805    com.android.internal.telephony.Connection getOriginalConnection() {
806        return mOriginalConnection;
807    }
808
809    protected Call getCall() {
810        if (mOriginalConnection != null) {
811            return mOriginalConnection.getCall();
812        }
813        return null;
814    }
815
816    Phone getPhone() {
817        Call call = getCall();
818        if (call != null) {
819            return call.getPhone();
820        }
821        return null;
822    }
823
824    private boolean hasMultipleTopLevelCalls() {
825        int numCalls = 0;
826        Phone phone = getPhone();
827        if (phone != null) {
828            if (!phone.getRingingCall().isIdle()) {
829                numCalls++;
830            }
831            if (!phone.getForegroundCall().isIdle()) {
832                numCalls++;
833            }
834            if (!phone.getBackgroundCall().isIdle()) {
835                numCalls++;
836            }
837        }
838        return numCalls > 1;
839    }
840
841    private com.android.internal.telephony.Connection getForegroundConnection() {
842        if (getPhone() != null) {
843            return getPhone().getForegroundCall().getEarliestConnection();
844        }
845        return null;
846    }
847
848     /**
849     * Checks for and returns the list of conference participants
850     * associated with this connection.
851     */
852    public List<ConferenceParticipant> getConferenceParticipants() {
853        if (mOriginalConnection == null) {
854            Log.v(this, "Null mOriginalConnection, cannot get conf participants.");
855            return null;
856        }
857        return mOriginalConnection.getConferenceParticipants();
858    }
859
860    /**
861     * Checks to see the original connection corresponds to an active incoming call. Returns false
862     * if there is no such actual call, or if the associated call is not incoming (See
863     * {@link Call.State#isRinging}).
864     */
865    private boolean isValidRingingCall() {
866        if (getPhone() == null) {
867            Log.v(this, "isValidRingingCall, phone is null");
868            return false;
869        }
870
871        Call ringingCall = getPhone().getRingingCall();
872        if (!ringingCall.getState().isRinging()) {
873            Log.v(this, "isValidRingingCall, ringing call is not in ringing state");
874            return false;
875        }
876
877        if (ringingCall.getEarliestConnection() != mOriginalConnection) {
878            Log.v(this, "isValidRingingCall, ringing call connection does not match");
879            return false;
880        }
881
882        Log.v(this, "isValidRingingCall, returning true");
883        return true;
884    }
885
886    protected void updateExtras(Bundle extras) {
887        if (mOriginalConnection != null) {
888            if (extras != null) {
889                // Check if extras have changed and need updating.
890                if (!areBundlesEqual(mOriginalConnectionExtras, extras)) {
891                    if (Log.DEBUG) {
892                        Log.d(TelephonyConnection.this, "Updating extras:");
893                        for (String key : extras.keySet()) {
894                            Object value = extras.get(key);
895                            if (value instanceof String) {
896                                Log.d(this, "updateExtras Key=" + Log.pii(key) +
897                                             " value=" + Log.pii((String)value));
898                            }
899                        }
900                    }
901                    mOriginalConnectionExtras.clear();
902
903                    mOriginalConnectionExtras.putAll(extras);
904
905                    // Remap any string extras that have a remapping defined.
906                    for (String key : mOriginalConnectionExtras.keySet()) {
907                        if (sExtrasMap.containsKey(key)) {
908                            String newKey = sExtrasMap.get(key);
909                            mOriginalConnectionExtras.putString(newKey, extras.getString(key));
910                            mOriginalConnectionExtras.remove(key);
911                        }
912                    }
913
914                    // Ensure extras are propagated to Telecom.
915                    putExtras(mOriginalConnectionExtras);
916                } else {
917                    Log.d(this, "Extras update not required");
918                }
919            } else {
920                Log.d(this, "updateExtras extras: " + Log.pii(extras));
921            }
922        }
923    }
924
925    private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
926        if (extras == null || newExtras == null) {
927            return extras == newExtras;
928        }
929
930        if (extras.size() != newExtras.size()) {
931            return false;
932        }
933
934        for(String key : extras.keySet()) {
935            if (key != null) {
936                final Object value = extras.get(key);
937                final Object newValue = newExtras.get(key);
938                if (!Objects.equals(value, newValue)) {
939                    return false;
940                }
941            }
942        }
943        return true;
944    }
945
946    void setStateOverride(Call.State state) {
947        mIsStateOverridden = true;
948        mConnectionOverriddenState = state;
949        // Need to keep track of the original connection's state before override.
950        mOriginalConnectionState = mOriginalConnection.getState();
951        updateStateInternal();
952    }
953
954    void resetStateOverride() {
955        mIsStateOverridden = false;
956        updateStateInternal();
957    }
958
959    void updateStateInternal() {
960        if (mOriginalConnection == null) {
961            return;
962        }
963        Call.State newState;
964        // If the state is overridden and the state of the original connection hasn't changed since,
965        // then we continue in the overridden state, else we go to the original connection's state.
966        if (mIsStateOverridden && mOriginalConnectionState == mOriginalConnection.getState()) {
967            newState = mConnectionOverriddenState;
968        } else {
969            newState = mOriginalConnection.getState();
970        }
971        Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState, this);
972
973        if (mConnectionState != newState) {
974            mConnectionState = newState;
975            switch (newState) {
976                case IDLE:
977                    break;
978                case ACTIVE:
979                    setActiveInternal();
980                    break;
981                case HOLDING:
982                    setOnHold();
983                    break;
984                case DIALING:
985                case ALERTING:
986                    setDialing();
987                    break;
988                case INCOMING:
989                case WAITING:
990                    setRinging();
991                    break;
992                case DISCONNECTED:
993                    setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
994                            mOriginalConnection.getDisconnectCause(),
995                            mOriginalConnection.getVendorDisconnectCause()));
996                    close();
997                    break;
998                case DISCONNECTING:
999                    break;
1000            }
1001        }
1002    }
1003
1004    void updateState() {
1005        if (mOriginalConnection == null) {
1006            return;
1007        }
1008
1009        updateStateInternal();
1010        updateStatusHints();
1011        updateConnectionCapabilities();
1012        updateConnectionProperties();
1013        updateAddress();
1014        updateMultiparty();
1015    }
1016
1017    /**
1018     * Checks for changes to the multiparty bit.  If a conference has started, informs listeners.
1019     */
1020    private void updateMultiparty() {
1021        if (mOriginalConnection == null) {
1022            return;
1023        }
1024
1025        if (mIsMultiParty != mOriginalConnection.isMultiparty()) {
1026            mIsMultiParty = mOriginalConnection.isMultiparty();
1027
1028            if (mIsMultiParty) {
1029                notifyConferenceStarted();
1030            }
1031        }
1032    }
1033
1034    /**
1035     * Handles a failure when merging calls into a conference.
1036     * {@link com.android.internal.telephony.Connection.Listener#onConferenceMergedFailed()}
1037     * listener.
1038     */
1039    private void handleConferenceMergeFailed(){
1040        mHandler.obtainMessage(MSG_CONFERENCE_MERGE_FAILED).sendToTarget();
1041    }
1042
1043    /**
1044     * Handles requests to update the multiparty state received via the
1045     * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)}
1046     * listener.
1047     * <p>
1048     * Note: We post this to the mHandler to ensure that if a conference must be created as a
1049     * result of the multiparty state change, the conference creation happens on the correct
1050     * thread.  This ensures that the thread check in
1051     * {@link com.android.internal.telephony.Phone#checkCorrectThread(android.os.Handler)}
1052     * does not fire.
1053     *
1054     * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise.
1055     */
1056    private void handleMultipartyStateChange(boolean isMultiParty) {
1057        Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
1058        mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget();
1059    }
1060
1061    private void setActiveInternal() {
1062        if (getState() == STATE_ACTIVE) {
1063            Log.w(this, "Should not be called if this is already ACTIVE");
1064            return;
1065        }
1066
1067        // When we set a call to active, we need to make sure that there are no other active
1068        // calls. However, the ordering of state updates to connections can be non-deterministic
1069        // since all connections register for state changes on the phone independently.
1070        // To "optimize", we check here to see if there already exists any active calls.  If so,
1071        // we issue an update for those calls first to make sure we only have one top-level
1072        // active call.
1073        if (getConnectionService() != null) {
1074            for (Connection current : getConnectionService().getAllConnections()) {
1075                if (current != this && current instanceof TelephonyConnection) {
1076                    TelephonyConnection other = (TelephonyConnection) current;
1077                    if (other.getState() == STATE_ACTIVE) {
1078                        other.updateState();
1079                    }
1080                }
1081            }
1082        }
1083        setActive();
1084    }
1085
1086    private void close() {
1087        Log.v(this, "close");
1088        clearOriginalConnection();
1089        destroy();
1090    }
1091
1092    /**
1093     * Determines if the current connection is video capable.
1094     *
1095     * A connection is deemed to be video capable if the original connection capabilities state that
1096     * both local and remote video is supported.
1097     *
1098     * @return {@code true} if the connection is video capable, {@code false} otherwise.
1099     */
1100    private boolean isVideoCapable() {
1101        return can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL)
1102                && can(mOriginalConnectionCapabilities,
1103                Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
1104    }
1105
1106    /**
1107     * Determines if the current connection is an external connection.
1108     *
1109     * A connection is deemed to be external if the original connection capabilities state that it
1110     * is.
1111     *
1112     * @return {@code true} if the connection is external, {@code false} otherwise.
1113     */
1114    private boolean isExternalConnection() {
1115        return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION)
1116                && can(mOriginalConnectionCapabilities,
1117                Capability.IS_EXTERNAL_CONNECTION);
1118    }
1119
1120    /**
1121     * Determines if the current connection is pullable.
1122     *
1123     * A connection is deemed to be pullable if the original connection capabilities state that it
1124     * is.
1125     *
1126     * @return {@code true} if the connection is pullable, {@code false} otherwise.
1127     */
1128    private boolean isPullable() {
1129        return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION)
1130                && can(mOriginalConnectionCapabilities, Capability.IS_PULLABLE);
1131    }
1132
1133    /**
1134     * Applies capabilities specific to conferences termination to the
1135     * {@code ConnectionCapabilities} bit-mask.
1136     *
1137     * @param capabilities The {@code ConnectionCapabilities} bit-mask.
1138     * @return The capabilities with the IMS conference capabilities applied.
1139     */
1140    private int applyConferenceTerminationCapabilities(int capabilities) {
1141        int currentCapabilities = capabilities;
1142
1143        // An IMS call cannot be individually disconnected or separated from its parent conference.
1144        // If the call was IMS, even if it hands over to GMS, these capabilities are not supported.
1145        if (!mWasImsConnection) {
1146            currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE;
1147            currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE;
1148        }
1149
1150        return currentCapabilities;
1151    }
1152
1153    /**
1154     * Stores the new original connection capabilities, and applies them to the current connection,
1155     * notifying any listeners as necessary.
1156     *
1157     * @param connectionCapabilities The original connection capabilties.
1158     */
1159    public void setOriginalConnectionCapabilities(int connectionCapabilities) {
1160        mOriginalConnectionCapabilities = connectionCapabilities;
1161        updateConnectionCapabilities();
1162        updateConnectionProperties();
1163    }
1164
1165    /**
1166     * Called to apply the capabilities present in the {@link #mOriginalConnection} to this
1167     * {@link Connection}.  Provides a mapping between the capabilities present in the original
1168     * connection (see {@link com.android.internal.telephony.Connection.Capability}) and those in
1169     * this {@link Connection}.
1170     *
1171     * @param capabilities The capabilities bitmask from the {@link Connection}.
1172     * @return the capabilities bitmask with the original connection capabilities remapped and
1173     *      applied.
1174     */
1175    public int applyOriginalConnectionCapabilities(int capabilities) {
1176        // We only support downgrading to audio if both the remote and local side support
1177        // downgrading to audio.
1178        boolean supportsDowngradeToAudio = can(mOriginalConnectionCapabilities,
1179                Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL |
1180                        Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE);
1181        capabilities = changeBitmask(capabilities,
1182                CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO, !supportsDowngradeToAudio);
1183
1184        capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
1185                can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL));
1186
1187        capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
1188                can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
1189
1190        return capabilities;
1191    }
1192
1193    /**
1194     * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset
1195     * the {@link Connection#PROPERTY_WIFI} property.
1196     */
1197    public void setWifi(boolean isWifi) {
1198        mIsWifi = isWifi;
1199        updateConnectionProperties();
1200        updateStatusHints();
1201    }
1202
1203    /**
1204     * Whether the call is using wifi.
1205     */
1206    boolean isWifi() {
1207        return mIsWifi;
1208    }
1209
1210    /**
1211     * Sets the current call audio quality. Used during rebuild of the properties
1212     * to set or unset the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property.
1213     *
1214     * @param audioQuality The audio quality.
1215     */
1216    public void setAudioQuality(int audioQuality) {
1217        mHasHighDefAudio = audioQuality ==
1218                com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION;
1219        updateConnectionProperties();
1220    }
1221
1222    void resetStateForConference() {
1223        if (getState() == Connection.STATE_HOLDING) {
1224            resetStateOverride();
1225        }
1226    }
1227
1228    boolean setHoldingForConference() {
1229        if (getState() == Connection.STATE_ACTIVE) {
1230            setStateOverride(Call.State.HOLDING);
1231            return true;
1232        }
1233        return false;
1234    }
1235
1236    /**
1237     * For video calls, sets whether this connection supports pausing the outgoing video for the
1238     * call using the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState.
1239     *
1240     * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise.
1241     */
1242    public void setVideoPauseSupported(boolean isVideoPauseSupported) {
1243        mIsVideoPauseSupported = isVideoPauseSupported;
1244    }
1245
1246    /**
1247     * Sets whether this connection supports conference calling.
1248     * @param isConferenceSupported {@code true} if conference calling is supported by this
1249     *                                         connection, {@code false} otherwise.
1250     */
1251    public void setConferenceSupported(boolean isConferenceSupported) {
1252        mIsConferenceSupported = isConferenceSupported;
1253    }
1254
1255    /**
1256     * @return {@code true} if this connection supports merging calls into a conference.
1257     */
1258    public boolean isConferenceSupported() {
1259        return mIsConferenceSupported;
1260    }
1261
1262    /**
1263     * Whether the original connection is an IMS connection.
1264     * @return {@code True} if the original connection is an IMS connection, {@code false}
1265     *     otherwise.
1266     */
1267    protected boolean isImsConnection() {
1268        com.android.internal.telephony.Connection originalConnection = getOriginalConnection();
1269        return originalConnection != null &&
1270                originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
1271    }
1272
1273    /**
1274     * Whether the original connection was ever an IMS connection, either before or now.
1275     * @return {@code True} if the original connection was ever an IMS connection, {@code false}
1276     *     otherwise.
1277     */
1278    public boolean wasImsConnection() {
1279        return mWasImsConnection;
1280    }
1281
1282    private static Uri getAddressFromNumber(String number) {
1283        // Address can be null for blocked calls.
1284        if (number == null) {
1285            number = "";
1286        }
1287        return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
1288    }
1289
1290    /**
1291     * Changes a capabilities bit-mask to add or remove a capability.
1292     *
1293     * @param bitmask The bit-mask.
1294     * @param bitfield The bit-field to change.
1295     * @param enabled Whether the bit-field should be set or removed.
1296     * @return The bit-mask with the bit-field changed.
1297     */
1298    private int changeBitmask(int bitmask, int bitfield, boolean enabled) {
1299        if (enabled) {
1300            return bitmask | bitfield;
1301        } else {
1302            return bitmask & ~bitfield;
1303        }
1304    }
1305
1306    private void updateStatusHints() {
1307        boolean isIncoming = isValidRingingCall();
1308        if (mIsWifi && (isIncoming || getState() == STATE_ACTIVE)) {
1309            int labelId = isIncoming
1310                    ? R.string.status_hint_label_incoming_wifi_call
1311                    : R.string.status_hint_label_wifi_call;
1312
1313            Context context = getPhone().getContext();
1314            setStatusHints(new StatusHints(
1315                    context.getString(labelId),
1316                    Icon.createWithResource(
1317                            context.getResources(),
1318                            R.drawable.ic_signal_wifi_4_bar_24dp),
1319                    null /* extras */));
1320        } else {
1321            setStatusHints(null);
1322        }
1323    }
1324
1325    /**
1326     * Register a listener for {@link TelephonyConnection} specific triggers.
1327     * @param l The instance of the listener to add
1328     * @return The connection being listened to
1329     */
1330    public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) {
1331        mTelephonyListeners.add(l);
1332        // If we already have an original connection, let's call back immediately.
1333        // This would be the case for incoming calls.
1334        if (mOriginalConnection != null) {
1335            fireOnOriginalConnectionConfigured();
1336        }
1337        return this;
1338    }
1339
1340    /**
1341     * Remove a listener for {@link TelephonyConnection} specific triggers.
1342     * @param l The instance of the listener to remove
1343     * @return The connection being listened to
1344     */
1345    public final TelephonyConnection removeTelephonyConnectionListener(
1346            TelephonyConnectionListener l) {
1347        if (l != null) {
1348            mTelephonyListeners.remove(l);
1349        }
1350        return this;
1351    }
1352
1353    /**
1354     * Fire a callback to the various listeners for when the original connection is
1355     * set in this {@link TelephonyConnection}
1356     */
1357    private final void fireOnOriginalConnectionConfigured() {
1358        for (TelephonyConnectionListener l : mTelephonyListeners) {
1359            l.onOriginalConnectionConfigured(this);
1360        }
1361    }
1362
1363    /**
1364     * Handles exiting ECM mode.
1365     */
1366    protected void handleExitedEcmMode() {
1367        updateConnectionProperties();
1368    }
1369
1370    /**
1371     * Provides a mapping from extras keys which may be found in the
1372     * {@link com.android.internal.telephony.Connection} to their equivalents defined in
1373     * {@link android.telecom.Connection}.
1374     *
1375     * @return Map containing key mappings.
1376     */
1377    private static Map<String, String> createExtrasMap() {
1378        Map<String, String> result = new HashMap<String, String>();
1379        result.put(ImsCallProfile.EXTRA_CHILD_NUMBER,
1380                android.telecom.Connection.EXTRA_CHILD_ADDRESS);
1381        result.put(ImsCallProfile.EXTRA_DISPLAY_TEXT,
1382                android.telecom.Connection.EXTRA_CALL_SUBJECT);
1383        return Collections.unmodifiableMap(result);
1384    }
1385
1386    /**
1387     * Creates a string representation of this {@link TelephonyConnection}.  Primarily intended for
1388     * use in log statements.
1389     *
1390     * @return String representation of the connection.
1391     */
1392    @Override
1393    public String toString() {
1394        StringBuilder sb = new StringBuilder();
1395        sb.append("[TelephonyConnection objId:");
1396        sb.append(System.identityHashCode(this));
1397        sb.append(" telecomCallID:");
1398        sb.append(getTelecomCallId());
1399        sb.append(" type:");
1400        if (isImsConnection()) {
1401            sb.append("ims");
1402        } else if (this instanceof com.android.services.telephony.GsmConnection) {
1403            sb.append("gsm");
1404        } else if (this instanceof CdmaConnection) {
1405            sb.append("cdma");
1406        }
1407        sb.append(" state:");
1408        sb.append(Connection.stateToString(getState()));
1409        sb.append(" capabilities:");
1410        sb.append(capabilitiesToString(getConnectionCapabilities()));
1411        sb.append(" properties:");
1412        sb.append(propertiesToString(getConnectionProperties()));
1413        sb.append(" address:");
1414        sb.append(Log.pii(getAddress()));
1415        sb.append(" originalConnection:");
1416        sb.append(mOriginalConnection);
1417        sb.append(" partOfConf:");
1418        if (getConference() == null) {
1419            sb.append("N");
1420        } else {
1421            sb.append("Y");
1422        }
1423        sb.append("]");
1424        return sb.toString();
1425    }
1426}
1427