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