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