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