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