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