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