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