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