TelephonyConnection.java revision 8330dac12f312495a2ae2d525917bf4f898edf9a
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.ComponentName;
20import android.content.Context;
21import android.graphics.drawable.Icon;
22import android.net.Uri;
23import android.os.AsyncResult;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.Message;
27import android.telecom.CallAudioState;
28import android.telecom.ConferenceParticipant;
29import android.telecom.Connection;
30import android.telecom.PhoneAccount;
31import android.telecom.StatusHints;
32
33import com.android.internal.telephony.Call;
34import com.android.internal.telephony.CallStateException;
35import com.android.internal.telephony.Connection.PostDialListener;
36import com.android.internal.telephony.gsm.SuppServiceNotification;
37
38import com.android.internal.telephony.Phone;
39import com.android.internal.telephony.imsphone.ImsPhoneConnection;
40import com.android.phone.R;
41
42import java.lang.Override;
43import java.util.Arrays;
44import java.util.ArrayList;
45import java.util.Collections;
46import java.util.List;
47import java.util.Objects;
48import java.util.Set;
49import java.util.concurrent.ConcurrentHashMap;
50
51/**
52 * Base class for CDMA and GSM connections.
53 */
54abstract class TelephonyConnection extends Connection {
55    private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
56    private static final int MSG_RINGBACK_TONE = 2;
57    private static final int MSG_HANDOVER_STATE_CHANGED = 3;
58    private static final int MSG_DISCONNECT = 4;
59    private static final int MSG_MULTIPARTY_STATE_CHANGED = 5;
60    private static final int MSG_CONFERENCE_MERGE_FAILED = 6;
61    private static final int MSG_SUPP_SERVICE_NOTIFY = 7;
62    private SuppServiceNotification mSsNotification = null;
63
64    private final Handler mHandler = new Handler() {
65        @Override
66        public void handleMessage(Message msg) {
67            switch (msg.what) {
68                case MSG_PRECISE_CALL_STATE_CHANGED:
69                    Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED");
70                    updateState();
71                    break;
72                case MSG_HANDOVER_STATE_CHANGED:
73                    Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED");
74                    AsyncResult ar = (AsyncResult) msg.obj;
75                    com.android.internal.telephony.Connection connection =
76                         (com.android.internal.telephony.Connection) ar.result;
77                    if (mOriginalConnection != null) {
78                        if (connection != null &&
79                            ((connection.getAddress() != null &&
80                            mOriginalConnection.getAddress() != null &&
81                            mOriginalConnection.getAddress().contains(connection.getAddress())) ||
82                            connection.getStateBeforeHandover() == mOriginalConnection.getState())) {
83                            Log.d(TelephonyConnection.this,
84                                    "SettingOriginalConnection " + mOriginalConnection.toString()
85                                            + " with " + connection.toString());
86                            setOriginalConnection(connection);
87                            mWasImsConnection = false;
88                        }
89                    } else {
90                        Log.w(TelephonyConnection.this,
91                                "MSG_HANDOVER_STATE_CHANGED: mOriginalConnection==null - invalid state (not cleaned up)");
92                    }
93                    break;
94                case MSG_RINGBACK_TONE:
95                    Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE");
96                    // TODO: This code assumes that there is only one connection in the foreground
97                    // call, in other words, it punts on network-mediated conference calling.
98                    if (getOriginalConnection() != getForegroundConnection()) {
99                        Log.v(TelephonyConnection.this, "handleMessage, original connection is " +
100                                "not foreground connection, skipping");
101                        return;
102                    }
103                    setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result);
104                    break;
105                case MSG_DISCONNECT:
106                    updateState();
107                    break;
108                case MSG_MULTIPARTY_STATE_CHANGED:
109                    boolean isMultiParty = (Boolean) msg.obj;
110                    Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
111                    mIsMultiParty = isMultiParty;
112                    if (isMultiParty) {
113                        notifyConferenceStarted();
114                    }
115                case MSG_CONFERENCE_MERGE_FAILED:
116                    notifyConferenceMergeFailed();
117                    break;
118                case MSG_SUPP_SERVICE_NOTIFY:
119                    Log.v(TelephonyConnection.this, "MSG_SUPP_SERVICE_NOTIFY on phoneId : "
120                            +getPhone().getPhoneId());
121                    if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {
122                        mSsNotification =
123                                (SuppServiceNotification)((AsyncResult) msg.obj).result;
124                        if (mOriginalConnection != null && mSsNotification.history != null) {
125                            Bundle extras = mOriginalConnection.getExtras();
126                            if (extras != null) {
127                                Log.v(TelephonyConnection.this,
128                                        "Updating call history info in extras.");
129                                extras.putStringArrayList(EXTRA_CALL_HISTORY_INFO,
130                                        new ArrayList(Arrays.asList(mSsNotification.history)));
131                                setExtras(extras);
132                            }
133                        }
134                    }
135                    break;
136            }
137        }
138    };
139
140    /**
141     * A listener/callback mechanism that is specific communication from TelephonyConnections
142     * to TelephonyConnectionService (for now). It is more specific that Connection.Listener
143     * because it is only exposed in Telephony.
144     */
145    public abstract static class TelephonyConnectionListener {
146        public void onOriginalConnectionConfigured(TelephonyConnection c) {}
147    }
148
149    private final PostDialListener mPostDialListener = new PostDialListener() {
150        @Override
151        public void onPostDialWait() {
152            Log.v(TelephonyConnection.this, "onPostDialWait");
153            if (mOriginalConnection != null) {
154                setPostDialWait(mOriginalConnection.getRemainingPostDialString());
155            }
156        }
157
158        @Override
159        public void onPostDialChar(char c) {
160            Log.v(TelephonyConnection.this, "onPostDialChar: %s", c);
161            if (mOriginalConnection != null) {
162                setNextPostDialChar(c);
163            }
164        }
165    };
166
167    /**
168     * Listener for listening to events in the {@link com.android.internal.telephony.Connection}.
169     */
170    private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener =
171            new com.android.internal.telephony.Connection.ListenerBase() {
172        @Override
173        public void onVideoStateChanged(int videoState) {
174            setVideoState(videoState);
175        }
176
177        /**
178         * The {@link com.android.internal.telephony.Connection} has reported a change in local
179         * video capability.
180         *
181         * @param capable True if capable.
182         */
183        @Override
184        public void onLocalVideoCapabilityChanged(boolean capable) {
185            setLocalVideoCapable(capable);
186        }
187
188        /**
189         * The {@link com.android.internal.telephony.Connection} has reported a change in remote
190         * video capability.
191         *
192         * @param capable True if capable.
193         */
194        @Override
195        public void onRemoteVideoCapabilityChanged(boolean capable) {
196            setRemoteVideoCapable(capable);
197        }
198
199        /**
200         * The {@link com.android.internal.telephony.Connection} has reported a change in the
201         * video call provider.
202         *
203         * @param videoProvider The video call provider.
204         */
205        @Override
206        public void onVideoProviderChanged(VideoProvider videoProvider) {
207            setVideoProvider(videoProvider);
208        }
209
210        /**
211         * Used by {@link com.android.internal.telephony.Connection} to report a change in whether
212         * the call is being made over a wifi network.
213         *
214         * @param isWifi True if call is made over wifi.
215         */
216        @Override
217        public void onWifiChanged(boolean isWifi) {
218            setWifi(isWifi);
219        }
220
221        /**
222         * Used by the {@link com.android.internal.telephony.Connection} to report a change in the
223         * audio quality for the current call.
224         *
225         * @param audioQuality The audio quality.
226         */
227        @Override
228        public void onAudioQualityChanged(int audioQuality) {
229            setAudioQuality(audioQuality);
230        }
231        /**
232         * Handles a change in the state of conference participant(s), as reported by the
233         * {@link com.android.internal.telephony.Connection}.
234         *
235         * @param participants The participant(s) which changed.
236         */
237        @Override
238        public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) {
239            updateConferenceParticipants(participants);
240        }
241
242        /*
243         * Handles a change to the multiparty state for this connection.
244         *
245         * @param isMultiParty {@code true} if the call became multiparty, {@code false}
246         *      otherwise.
247         */
248        @Override
249        public void onMultipartyStateChanged(boolean isMultiParty) {
250            handleMultipartyStateChange(isMultiParty);
251        }
252
253        /**
254         * Handles the event that the request to merge calls failed.
255         */
256        @Override
257        public void onConferenceMergedFailed() {
258            handleConferenceMergeFailed();
259        }
260    };
261
262    private com.android.internal.telephony.Connection mOriginalConnection;
263    private Call.State mOriginalConnectionState = Call.State.IDLE;
264    private Bundle mOriginalConnectionExtras = new Bundle();
265
266    private boolean mWasImsConnection;
267
268    /**
269     * Tracks the multiparty state of the ImsCall so that changes in the bit state can be detected.
270     */
271    private boolean mIsMultiParty = false;
272
273    /**
274     * Determines if the {@link TelephonyConnection} has local video capabilities.
275     * This is used when {@link TelephonyConnection#updateConnectionCapabilities()}} is called,
276     * ensuring the appropriate capabilities are set.  Since capabilities
277     * can be rebuilt at any time it is necessary to track the video capabilities between rebuild.
278     * The capabilities (including video capabilities) are communicated to the telecom
279     * layer.
280     */
281    private boolean mLocalVideoCapable;
282
283    /**
284     * Determines if the {@link TelephonyConnection} has remote video capabilities.
285     * This is used when {@link TelephonyConnection#updateConnectionCapabilities()}} is called,
286     * ensuring the appropriate capabilities are set.  Since capabilities can be rebuilt at any time
287     * it is necessary to track the video capabilities between rebuild. The capabilities (including
288     * video capabilities) are communicated to the telecom layer.
289     */
290    private boolean mRemoteVideoCapable;
291
292    /**
293     * Determines if the {@link TelephonyConnection} is using wifi.
294     * This is used when {@link TelephonyConnection#updateConnectionCapabilities} is called to
295     * indicate wheter a call has the {@link Connection#CAPABILITY_WIFI} capability.
296     */
297    private boolean mIsWifi;
298
299    /**
300     * Determines the audio quality is high for the {@link TelephonyConnection}.
301     * This is used when {@link TelephonyConnection#updateConnectionCapabilities}} is called to
302     * indicate whether a call has the {@link Connection#CAPABILITY_HIGH_DEF_AUDIO} capability.
303     */
304    private boolean mHasHighDefAudio;
305
306    /**
307     * For video calls, indicates whether the outgoing video for the call can be paused using
308     * the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState.
309     */
310    private boolean mIsVideoPauseSupported;
311
312    /**
313     * Listeners to our TelephonyConnection specific callbacks
314     */
315    private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
316            new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1));
317
318    protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) {
319        if (originalConnection != null) {
320            setOriginalConnection(originalConnection);
321        }
322    }
323
324    /**
325     * Creates a clone of the current {@link TelephonyConnection}.
326     *
327     * @return The clone.
328     */
329    public abstract TelephonyConnection cloneConnection();
330
331    @Override
332    public void onCallAudioStateChanged(CallAudioState audioState) {
333        // TODO: update TTY mode.
334        if (getPhone() != null) {
335            getPhone().setEchoSuppressionEnabled();
336        }
337    }
338
339    @Override
340    public void onStateChanged(int state) {
341        Log.v(this, "onStateChanged, state: " + Connection.stateToString(state));
342        updateStatusHints();
343    }
344
345    @Override
346    public void onDisconnect() {
347        Log.v(this, "onDisconnect");
348        hangup(android.telephony.DisconnectCause.LOCAL);
349    }
350
351    /**
352     * Notifies this Connection of a request to disconnect a participant of the conference managed
353     * by the connection.
354     *
355     * @param endpoint the {@link Uri} of the participant to disconnect.
356     */
357    @Override
358    public void onDisconnectConferenceParticipant(Uri endpoint) {
359        Log.v(this, "onDisconnectConferenceParticipant %s", endpoint);
360
361        if (mOriginalConnection == null) {
362            return;
363        }
364
365        mOriginalConnection.onDisconnectConferenceParticipant(endpoint);
366    }
367
368    @Override
369    public void onSeparate() {
370        Log.v(this, "onSeparate");
371        if (mOriginalConnection != null) {
372            try {
373                mOriginalConnection.separate();
374            } catch (CallStateException e) {
375                Log.e(this, e, "Call to Connection.separate failed with exception");
376            }
377        }
378    }
379
380    @Override
381    public void onAbort() {
382        Log.v(this, "onAbort");
383        hangup(android.telephony.DisconnectCause.LOCAL);
384    }
385
386    @Override
387    public void onHold() {
388        performHold();
389    }
390
391    @Override
392    public void onUnhold() {
393        performUnhold();
394    }
395
396    @Override
397    public void onAnswer(int videoState) {
398        Log.v(this, "onAnswer");
399        if (isValidRingingCall() && getPhone() != null) {
400            try {
401                getPhone().acceptCall(videoState);
402            } catch (CallStateException e) {
403                Log.e(this, e, "Failed to accept call.");
404            }
405        }
406    }
407
408    @Override
409    public void onReject() {
410        Log.v(this, "onReject");
411        if (isValidRingingCall()) {
412            hangup(android.telephony.DisconnectCause.INCOMING_REJECTED);
413        }
414        super.onReject();
415    }
416
417    @Override
418    public void onPostDialContinue(boolean proceed) {
419        Log.v(this, "onPostDialContinue, proceed: " + proceed);
420        if (mOriginalConnection != null) {
421            if (proceed) {
422                mOriginalConnection.proceedAfterWaitChar();
423            } else {
424                mOriginalConnection.cancelPostDial();
425            }
426        }
427    }
428
429    public void performHold() {
430        Log.v(this, "performHold");
431        // TODO: Can dialing calls be put on hold as well since they take up the
432        // foreground call slot?
433        if (Call.State.ACTIVE == mOriginalConnectionState) {
434            Log.v(this, "Holding active call");
435            try {
436                Phone phone = mOriginalConnection.getCall().getPhone();
437                Call ringingCall = phone.getRingingCall();
438
439                // Although the method says switchHoldingAndActive, it eventually calls a RIL method
440                // called switchWaitingOrHoldingAndActive. What this means is that if we try to put
441                // a call on hold while a call-waiting call exists, it'll end up accepting the
442                // call-waiting call, which is bad if that was not the user's intention. We are
443                // cheating here and simply skipping it because we know any attempt to hold a call
444                // while a call-waiting call is happening is likely a request from Telecom prior to
445                // accepting the call-waiting call.
446                // TODO: Investigate a better solution. It would be great here if we
447                // could "fake" hold by silencing the audio and microphone streams for this call
448                // instead of actually putting it on hold.
449                if (ringingCall.getState() != Call.State.WAITING) {
450                    phone.switchHoldingAndActive();
451                }
452
453                // TODO: Cdma calls are slightly different.
454            } catch (CallStateException e) {
455                Log.e(this, e, "Exception occurred while trying to put call on hold.");
456            }
457        } else {
458            Log.w(this, "Cannot put a call that is not currently active on hold.");
459        }
460    }
461
462    public void performUnhold() {
463        Log.v(this, "performUnhold");
464        if (Call.State.HOLDING == mOriginalConnectionState) {
465            try {
466                // Here's the deal--Telephony hold/unhold is weird because whenever there exists
467                // more than one call, one of them must always be active. In other words, if you
468                // have an active call and holding call, and you put the active call on hold, it
469                // will automatically activate the holding call. This is weird with how Telecom
470                // sends its commands. When a user opts to "unhold" a background call, telecom
471                // issues hold commands to all active calls, and then the unhold command to the
472                // background call. This means that we get two commands...each of which reduces to
473                // switchHoldingAndActive(). The result is that they simply cancel each other out.
474                // To fix this so that it works well with telecom we add a minor hack. If we
475                // have one telephony call, everything works as normally expected. But if we have
476                // two or more calls, we will ignore all requests to "unhold" knowing that the hold
477                // requests already do what we want. If you've read up to this point, I'm very sorry
478                // that we are doing this. I didn't think of a better solution that wouldn't also
479                // make the Telecom APIs very ugly.
480
481                if (!hasMultipleTopLevelCalls()) {
482                    mOriginalConnection.getCall().getPhone().switchHoldingAndActive();
483                } else {
484                    Log.i(this, "Skipping unhold command for %s", this);
485                }
486            } catch (CallStateException e) {
487                Log.e(this, e, "Exception occurred while trying to release call from hold.");
488            }
489        } else {
490            Log.w(this, "Cannot release a call that is not already on hold from hold.");
491        }
492    }
493
494    public void performConference(TelephonyConnection otherConnection) {
495        Log.d(this, "performConference - %s", this);
496        if (getPhone() != null) {
497            try {
498                // We dont use the "other" connection because there is no concept of that in the
499                // implementation of calls inside telephony. Basically, you can "conference" and it
500                // will conference with the background call.  We know that otherConnection is the
501                // background call because it would never have called setConferenceableConnections()
502                // otherwise.
503                getPhone().conference();
504            } catch (CallStateException e) {
505                Log.e(this, e, "Failed to conference call.");
506            }
507        }
508    }
509
510    /**
511     * Builds call capabilities common to all TelephonyConnections. Namely, apply IMS-based
512     * capabilities.
513     */
514    protected int buildConnectionCapabilities() {
515        int callCapabilities = 0;
516        if (isImsConnection()) {
517            if (mOriginalConnection.isIncoming()) {
518                callCapabilities |= CAPABILITY_SPEED_UP_MT_AUDIO;
519            }
520            callCapabilities |= CAPABILITY_SUPPORT_HOLD;
521            if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) {
522                callCapabilities |= CAPABILITY_HOLD;
523            }
524        }
525
526        // If the phone is in ECM mode, mark the call to indicate that the callback number should be
527        // shown.
528        Phone phone = getPhone();
529        if (phone != null && phone.isInEcm()) {
530            callCapabilities |= CAPABILITY_SHOW_CALLBACK_NUMBER;
531        }
532        return callCapabilities;
533    }
534
535    protected final void updateConnectionCapabilities() {
536        int newCapabilities = buildConnectionCapabilities();
537
538        newCapabilities = changeCapability(newCapabilities,
539                CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, mRemoteVideoCapable);
540        newCapabilities = changeCapability(newCapabilities,
541                CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, mLocalVideoCapable);
542        newCapabilities = changeCapability(newCapabilities,
543                CAPABILITY_HIGH_DEF_AUDIO, mHasHighDefAudio);
544        newCapabilities = changeCapability(newCapabilities, CAPABILITY_WIFI, mIsWifi);
545        newCapabilities = changeCapability(newCapabilities, CAPABILITY_CAN_PAUSE_VIDEO,
546                mIsVideoPauseSupported && mRemoteVideoCapable && mLocalVideoCapable);
547
548        newCapabilities = applyConferenceTerminationCapabilities(newCapabilities);
549
550        if (getConnectionCapabilities() != newCapabilities) {
551            setConnectionCapabilities(newCapabilities);
552        }
553    }
554
555    protected final void updateAddress() {
556        updateConnectionCapabilities();
557        if (mOriginalConnection != null) {
558            Uri address = getAddressFromNumber(mOriginalConnection.getAddress());
559            int presentation = mOriginalConnection.getNumberPresentation();
560            if (!Objects.equals(address, getAddress()) ||
561                    presentation != getAddressPresentation()) {
562                Log.v(this, "updateAddress, address changed");
563                setAddress(address, presentation);
564            }
565
566            String name = mOriginalConnection.getCnapName();
567            int namePresentation = mOriginalConnection.getCnapNamePresentation();
568            if (!Objects.equals(name, getCallerDisplayName()) ||
569                    namePresentation != getCallerDisplayNamePresentation()) {
570                Log.v(this, "updateAddress, caller display name changed");
571                setCallerDisplayName(name, namePresentation);
572            }
573        }
574    }
575
576    void onRemovedFromCallService() {
577        // Subclass can override this to do cleanup.
578    }
579
580    void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
581        Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
582        clearOriginalConnection();
583
584        mOriginalConnection = originalConnection;
585        getPhone().registerForPreciseCallStateChanged(
586                mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
587        getPhone().registerForHandoverStateChanged(
588                mHandler, MSG_HANDOVER_STATE_CHANGED, null);
589        getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
590        getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null);
591        getPhone().registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null);
592        mOriginalConnection.addPostDialListener(mPostDialListener);
593        mOriginalConnection.addListener(mOriginalConnectionListener);
594
595        // Set video state and capabilities
596        setVideoState(mOriginalConnection.getVideoState());
597        updateState();
598        setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable());
599        setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable());
600        setWifi(mOriginalConnection.isWifi());
601        setVideoProvider(mOriginalConnection.getVideoProvider());
602        setAudioQuality(mOriginalConnection.getAudioQuality());
603
604        if (isImsConnection()) {
605            mWasImsConnection = true;
606        }
607        mIsMultiParty = mOriginalConnection.isMultiparty();
608
609        fireOnOriginalConnectionConfigured();
610        updateAddress();
611    }
612
613    /**
614     * Un-sets the underlying radio connection.
615     */
616    void clearOriginalConnection() {
617        if (mOriginalConnection != null) {
618            if (getPhone() != null) {
619                getPhone().unregisterForPreciseCallStateChanged(mHandler);
620                getPhone().unregisterForRingbackTone(mHandler);
621                getPhone().unregisterForHandoverStateChanged(mHandler);
622                getPhone().unregisterForDisconnect(mHandler);
623                getPhone().unregisterForSuppServiceNotification(mHandler);
624            }
625            mOriginalConnection.removePostDialListener(mPostDialListener);
626            mOriginalConnection.removeListener(mOriginalConnectionListener);
627            mOriginalConnection = null;
628        }
629    }
630
631    protected void hangup(int telephonyDisconnectCode) {
632        if (mOriginalConnection != null) {
633            try {
634                // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
635                // connection.hangup(). Without this change, the party originating the call will not
636                // get sent to voicemail if the user opts to reject the call.
637                if (isValidRingingCall()) {
638                    Call call = getCall();
639                    if (call != null) {
640                        call.hangup();
641                    } else {
642                        Log.w(this, "Attempting to hangup a connection without backing call.");
643                    }
644                } else {
645                    // We still prefer to call connection.hangup() for non-ringing calls in order
646                    // to support hanging-up specific calls within a conference call. If we invoked
647                    // call.hangup() while in a conference, we would end up hanging up the entire
648                    // conference call instead of the specific connection.
649                    mOriginalConnection.hangup();
650                }
651            } catch (CallStateException e) {
652                Log.e(this, e, "Call to Connection.hangup failed with exception");
653            }
654        }
655    }
656
657    com.android.internal.telephony.Connection getOriginalConnection() {
658        return mOriginalConnection;
659    }
660
661    protected Call getCall() {
662        if (mOriginalConnection != null) {
663            return mOriginalConnection.getCall();
664        }
665        return null;
666    }
667
668    Phone getPhone() {
669        Call call = getCall();
670        if (call != null) {
671            return call.getPhone();
672        }
673        return null;
674    }
675
676    private boolean hasMultipleTopLevelCalls() {
677        int numCalls = 0;
678        Phone phone = getPhone();
679        if (phone != null) {
680            if (!phone.getRingingCall().isIdle()) {
681                numCalls++;
682            }
683            if (!phone.getForegroundCall().isIdle()) {
684                numCalls++;
685            }
686            if (!phone.getBackgroundCall().isIdle()) {
687                numCalls++;
688            }
689        }
690        return numCalls > 1;
691    }
692
693    private com.android.internal.telephony.Connection getForegroundConnection() {
694        if (getPhone() != null) {
695            return getPhone().getForegroundCall().getEarliestConnection();
696        }
697        return null;
698    }
699
700     /**
701     * Checks for and returns the list of conference participants
702     * associated with this connection.
703     */
704    public List<ConferenceParticipant> getConferenceParticipants() {
705        if (mOriginalConnection == null) {
706            Log.v(this, "Null mOriginalConnection, cannot get conf participants.");
707            return null;
708        }
709        return mOriginalConnection.getConferenceParticipants();
710    }
711
712    /**
713     * Checks to see the original connection corresponds to an active incoming call. Returns false
714     * if there is no such actual call, or if the associated call is not incoming (See
715     * {@link Call.State#isRinging}).
716     */
717    private boolean isValidRingingCall() {
718        if (getPhone() == null) {
719            Log.v(this, "isValidRingingCall, phone is null");
720            return false;
721        }
722
723        Call ringingCall = getPhone().getRingingCall();
724        if (!ringingCall.getState().isRinging()) {
725            Log.v(this, "isValidRingingCall, ringing call is not in ringing state");
726            return false;
727        }
728
729        if (ringingCall.getEarliestConnection() != mOriginalConnection) {
730            Log.v(this, "isValidRingingCall, ringing call connection does not match");
731            return false;
732        }
733
734        Log.v(this, "isValidRingingCall, returning true");
735        return true;
736    }
737
738    protected void updateExtras() {
739        Bundle extras = null;
740        if (mOriginalConnection != null) {
741            extras = mOriginalConnection.getExtras();
742            if (extras != null) {
743                // Check if extras have changed and need updating.
744                if (!areBundlesEqual(mOriginalConnectionExtras, extras)) {
745                    if (Log.DEBUG) {
746                        Log.d(TelephonyConnection.this, "Updating extras:");
747                        for (String key : extras.keySet()) {
748                            Object value = extras.get(key);
749                            if (value instanceof String) {
750                                Log.d(this, "updateExtras Key=" + Log.pii(key) +
751                                             " value=" + Log.pii((String)value));
752                            }
753                        }
754                    }
755                    mOriginalConnectionExtras.clear();
756                    mOriginalConnectionExtras.putAll(extras);
757                } else {
758                    Log.d(this, "Extras update not required");
759                }
760            } else {
761                Log.d(this, "updateExtras extras: " + Log.pii(extras));
762            }
763        }
764    }
765
766    private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
767        if (extras == null || newExtras == null) {
768            return extras == newExtras;
769        }
770
771        if (extras.size() != newExtras.size()) {
772            return false;
773        }
774
775        for(String key : extras.keySet()) {
776            if (key != null) {
777                final Object value = extras.get(key);
778                final Object newValue = newExtras.get(key);
779                if (!Objects.equals(value, newValue)) {
780                    return false;
781                }
782            }
783        }
784        return true;
785    }
786
787    void updateState() {
788        if (mOriginalConnection == null) {
789            return;
790        }
791
792        Call.State newState = mOriginalConnection.getState();
793        Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this);
794        if (mOriginalConnectionState != newState) {
795            mOriginalConnectionState = newState;
796            switch (newState) {
797                case IDLE:
798                    break;
799                case ACTIVE:
800                    setActiveInternal();
801                    break;
802                case HOLDING:
803                    setOnHold();
804                    break;
805                case DIALING:
806                case ALERTING:
807                    setDialing();
808                    break;
809                case INCOMING:
810                case WAITING:
811                    setRinging();
812                    break;
813                case DISCONNECTED:
814                    setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
815                            mOriginalConnection.getDisconnectCause(),
816                            mOriginalConnection.getVendorDisconnectCause()));
817                    close();
818                    break;
819                case DISCONNECTING:
820                    break;
821            }
822        }
823        updateStatusHints();
824        updateConnectionCapabilities();
825        updateAddress();
826        updateMultiparty();
827        updateExtras();
828    }
829
830    /**
831     * Checks for changes to the multiparty bit.  If a conference has started, informs listeners.
832     */
833    private void updateMultiparty() {
834        if (mOriginalConnection == null) {
835            return;
836        }
837
838        if (mIsMultiParty != mOriginalConnection.isMultiparty()) {
839            mIsMultiParty = mOriginalConnection.isMultiparty();
840
841            if (mIsMultiParty) {
842                notifyConferenceStarted();
843            }
844        }
845    }
846
847    /**
848     * Handles a failure when merging calls into a conference.
849     * {@link com.android.internal.telephony.Connection.Listener#onConferenceMergedFailed()}
850     * listener.
851     */
852    private void handleConferenceMergeFailed(){
853        mHandler.obtainMessage(MSG_CONFERENCE_MERGE_FAILED).sendToTarget();
854    }
855
856    /**
857     * Handles requests to update the multiparty state received via the
858     * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)}
859     * listener.
860     * <p>
861     * Note: We post this to the mHandler to ensure that if a conference must be created as a
862     * result of the multiparty state change, the conference creation happens on the correct
863     * thread.  This ensures that the thread check in
864     * {@link com.android.internal.telephony.PhoneBase#checkCorrectThread(android.os.Handler)}
865     * does not fire.
866     *
867     * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise.
868     */
869    private void handleMultipartyStateChange(boolean isMultiParty) {
870        Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
871        mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget();
872    }
873
874    private void setActiveInternal() {
875        if (getState() == STATE_ACTIVE) {
876            Log.w(this, "Should not be called if this is already ACTIVE");
877            return;
878        }
879
880        // When we set a call to active, we need to make sure that there are no other active
881        // calls. However, the ordering of state updates to connections can be non-deterministic
882        // since all connections register for state changes on the phone independently.
883        // To "optimize", we check here to see if there already exists any active calls.  If so,
884        // we issue an update for those calls first to make sure we only have one top-level
885        // active call.
886        if (getConnectionService() != null) {
887            for (Connection current : getConnectionService().getAllConnections()) {
888                if (current != this && current instanceof TelephonyConnection) {
889                    TelephonyConnection other = (TelephonyConnection) current;
890                    if (other.getState() == STATE_ACTIVE) {
891                        other.updateState();
892                    }
893                }
894            }
895        }
896        setActive();
897    }
898
899    private void close() {
900        Log.v(this, "close");
901        clearOriginalConnection();
902        destroy();
903    }
904
905    /**
906     * Applies capabilities specific to conferences termination to the
907     * {@code CallCapabilities} bit-mask.
908     *
909     * @param capabilities The {@code CallCapabilities} bit-mask.
910     * @return The capabilities with the IMS conference capabilities applied.
911     */
912    private int applyConferenceTerminationCapabilities(int capabilities) {
913        int currentCapabilities = capabilities;
914
915        // An IMS call cannot be individually disconnected or separated from its parent conference.
916        // If the call was IMS, even if it hands over to GMS, these capabilities are not supported.
917        if (!mWasImsConnection) {
918            currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE;
919            currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE;
920        }
921
922        return currentCapabilities;
923    }
924
925    /**
926     * Returns the local video capability state for the connection.
927     *
928     * @return {@code True} if the connection has local video capabilities.
929     */
930    public boolean isLocalVideoCapable() {
931        return mLocalVideoCapable;
932    }
933
934    /**
935     * Returns the remote video capability state for the connection.
936     *
937     * @return {@code True} if the connection has remote video capabilities.
938     */
939    public boolean isRemoteVideoCapable() {
940        return mRemoteVideoCapable;
941    }
942
943    /**
944     * Sets whether video capability is present locally.  Used during rebuild of the
945     * capabilities to set the video call capabilities.
946     *
947     * @param capable {@code True} if video capable.
948     */
949    public void setLocalVideoCapable(boolean capable) {
950        mLocalVideoCapable = capable;
951        updateConnectionCapabilities();
952    }
953
954    /**
955     * Sets whether video capability is present remotely.  Used during rebuild of the
956     * capabilities to set the video call capabilities.
957     *
958     * @param capable {@code True} if video capable.
959     */
960    public void setRemoteVideoCapable(boolean capable) {
961        mRemoteVideoCapable = capable;
962        updateConnectionCapabilities();
963    }
964
965    /**
966     * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset
967     * the {@link Connection#CAPABILITY_WIFI} capability.
968     */
969    public void setWifi(boolean isWifi) {
970        mIsWifi = isWifi;
971        updateConnectionCapabilities();
972        updateStatusHints();
973    }
974
975    /**
976     * Whether the call is using wifi.
977     */
978    boolean isWifi() {
979        return mIsWifi;
980    }
981
982    /**
983     * Sets the current call audio quality. Used during rebuild of the capabilities
984     * to set or unset the {@link Connection#CAPABILITY_HIGH_DEF_AUDIO} capability.
985     *
986     * @param audioQuality The audio quality.
987     */
988    public void setAudioQuality(int audioQuality) {
989        mHasHighDefAudio = audioQuality ==
990                com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION;
991        updateConnectionCapabilities();
992    }
993
994    void resetStateForConference() {
995        if (getState() == Connection.STATE_HOLDING) {
996            if (mOriginalConnection.getState() == Call.State.ACTIVE) {
997                setActive();
998            }
999        }
1000    }
1001
1002    boolean setHoldingForConference() {
1003        if (getState() == Connection.STATE_ACTIVE) {
1004            setOnHold();
1005            return true;
1006        }
1007        return false;
1008    }
1009
1010    /**
1011     * For video calls, sets whether this connection supports pausing the outgoing video for the
1012     * call using the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState.
1013     *
1014     * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise.
1015     */
1016    public void setVideoPauseSupported(boolean isVideoPauseSupported) {
1017        mIsVideoPauseSupported = isVideoPauseSupported;
1018    }
1019
1020    /**
1021     * Whether the original connection is an IMS connection.
1022     * @return {@code True} if the original connection is an IMS connection, {@code false}
1023     *     otherwise.
1024     */
1025    protected boolean isImsConnection() {
1026        return getOriginalConnection() instanceof ImsPhoneConnection;
1027    }
1028
1029    /**
1030     * Whether the original connection was ever an IMS connection, either before or now.
1031     * @return {@code True} if the original connection was ever an IMS connection, {@code false}
1032     *     otherwise.
1033     */
1034    public boolean wasImsConnection() {
1035        return mWasImsConnection;
1036    }
1037
1038    private static Uri getAddressFromNumber(String number) {
1039        // Address can be null for blocked calls.
1040        if (number == null) {
1041            number = "";
1042        }
1043        return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
1044    }
1045
1046    /**
1047     * Changes a capabilities bit-mask to add or remove a capability.
1048     *
1049     * @param capabilities The capabilities bit-mask.
1050     * @param capability The capability to change.
1051     * @param enabled Whether the capability should be set or removed.
1052     * @return The capabilities bit-mask with the capability changed.
1053     */
1054    private int changeCapability(int capabilities, int capability, boolean enabled) {
1055        if (enabled) {
1056            return capabilities | capability;
1057        } else {
1058            return capabilities & ~capability;
1059        }
1060    }
1061
1062    private void updateStatusHints() {
1063        boolean isIncoming = isValidRingingCall();
1064        if (mIsWifi && (isIncoming || getState() == STATE_ACTIVE)) {
1065            int labelId = isIncoming
1066                    ? R.string.status_hint_label_incoming_wifi_call
1067                    : R.string.status_hint_label_wifi_call;
1068
1069            Context context = getPhone().getContext();
1070            setStatusHints(new StatusHints(
1071                    context.getString(labelId),
1072                    Icon.createWithResource(
1073                            context.getResources(),
1074                            R.drawable.ic_signal_wifi_4_bar_24dp),
1075                    null /* extras */));
1076        } else {
1077            setStatusHints(null);
1078        }
1079    }
1080
1081    /**
1082     * Register a listener for {@link TelephonyConnection} specific triggers.
1083     * @param l The instance of the listener to add
1084     * @return The connection being listened to
1085     */
1086    public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) {
1087        mTelephonyListeners.add(l);
1088        // If we already have an original connection, let's call back immediately.
1089        // This would be the case for incoming calls.
1090        if (mOriginalConnection != null) {
1091            fireOnOriginalConnectionConfigured();
1092        }
1093        return this;
1094    }
1095
1096    /**
1097     * Remove a listener for {@link TelephonyConnection} specific triggers.
1098     * @param l The instance of the listener to remove
1099     * @return The connection being listened to
1100     */
1101    public final TelephonyConnection removeTelephonyConnectionListener(
1102            TelephonyConnectionListener l) {
1103        if (l != null) {
1104            mTelephonyListeners.remove(l);
1105        }
1106        return this;
1107    }
1108
1109    /**
1110     * Fire a callback to the various listeners for when the original connection is
1111     * set in this {@link TelephonyConnection}
1112     */
1113    private final void fireOnOriginalConnectionConfigured() {
1114        for (TelephonyConnectionListener l : mTelephonyListeners) {
1115            l.onOriginalConnectionConfigured(this);
1116        }
1117    }
1118
1119    /**
1120     * Creates a string representation of this {@link TelephonyConnection}.  Primarily intended for
1121     * use in log statements.
1122     *
1123     * @return String representation of the connection.
1124     */
1125    @Override
1126    public String toString() {
1127        StringBuilder sb = new StringBuilder();
1128        sb.append("[TelephonyConnection objId:");
1129        sb.append(System.identityHashCode(this));
1130        sb.append(" type:");
1131        if (isImsConnection()) {
1132            sb.append("ims");
1133        } else if (this instanceof com.android.services.telephony.GsmConnection) {
1134            sb.append("gsm");
1135        } else if (this instanceof CdmaConnection) {
1136            sb.append("cdma");
1137        }
1138        sb.append(" state:");
1139        sb.append(Connection.stateToString(getState()));
1140        sb.append(" capabilities:");
1141        sb.append(capabilitiesToString(getConnectionCapabilities()));
1142        sb.append(" address:");
1143        sb.append(Log.pii(getAddress()));
1144        sb.append(" originalConnection:");
1145        sb.append(mOriginalConnection);
1146        sb.append(" partOfConf:");
1147        if (getConference() == null) {
1148            sb.append("N");
1149        } else {
1150            sb.append("Y");
1151        }
1152        sb.append("]");
1153        return sb.toString();
1154    }
1155}
1156