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