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