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