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