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