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