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