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