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