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