TelephonyConnection.java revision 536cff018ecf3a329ccf44f723bae071c840ab71
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.os.Handler;
20import android.os.Message;
21import android.telecomm.CallAudioState;
22import android.telephony.DisconnectCause;
23
24import com.android.internal.telephony.Call;
25import com.android.internal.telephony.CallStateException;
26import com.android.internal.telephony.Phone;
27import android.telecomm.Connection;
28
29/**
30 * Manages a single phone call in Telephony.
31 */
32class TelephonyConnection extends Connection {
33    private static final int EVENT_PRECISE_CALL_STATE_CHANGED = 1;
34
35    private final StateHandler mHandler = new StateHandler();
36
37    private com.android.internal.telephony.Connection mOriginalConnection;
38    private Call.State mState = Call.State.IDLE;
39
40    protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) {
41        mOriginalConnection = originalConnection;
42        mOriginalConnection.getCall().getPhone().registerForPreciseCallStateChanged(mHandler,
43                EVENT_PRECISE_CALL_STATE_CHANGED, null);
44        updateState();
45    }
46
47    com.android.internal.telephony.Connection getOriginalConnection() {
48        return mOriginalConnection;
49    }
50
51    @Override
52    protected void onAbort() {
53        hangup(DisconnectCause.LOCAL);
54        super.onAbort();
55    }
56
57    @Override
58    protected void onDisconnect() {
59        hangup(DisconnectCause.LOCAL);
60        super.onDisconnect();
61    }
62
63    @Override
64    protected void onSeparate() {
65        if (mOriginalConnection != null) {
66            try {
67                mOriginalConnection.separate();
68            } catch (CallStateException e) {
69                Log.e(this, e, "Call to Connection.separate failed with exception");
70            }
71        }
72        super.onSeparate();
73    }
74
75    @Override
76    public void onHold() {
77        Log.d(this, "Attempting to put call on hold");
78        // TODO(santoscordon): Can dialing calls be put on hold as well since they take up the
79        // foreground call slot?
80        if (Call.State.ACTIVE == mState) {
81            Log.v(this, "Holding active call");
82            try {
83                Phone phone = mOriginalConnection.getCall().getPhone();
84                Call ringingCall = phone.getRingingCall();
85
86                // Although the method says switchHoldingAndActive, it eventually calls a RIL method
87                // called switchWaitingOrHoldingAndActive. What this means is that if we try to put
88                // a call on hold while a call-waiting call exists, it'll end up accepting the
89                // call-waiting call, which is bad if that was not the user's intention. We are
90                // cheating here and simply skipping it because we know any attempt to hold a call
91                // while a call-waiting call is happening is likely a request from Telecomm prior to
92                // accepting the call-waiting call.
93                // TODO(santoscordon): Investigate a better solution. It would be great here if we
94                // could "fake" hold by silencing the audio and microphone streams for this call
95                // instead of actually putting it on hold.
96                if (ringingCall.getState() != Call.State.WAITING) {
97                    phone.switchHoldingAndActive();
98                }
99
100                // TODO(santoscordon): Cdma calls are slightly different.
101            } catch (CallStateException e) {
102                Log.e(this, e, "Exception occurred while trying to put call on hold.");
103            }
104        } else {
105            Log.w(this, "Cannot put a call that is not currently active on hold.");
106        }
107        super.onHold();
108    }
109
110    @Override
111    protected void onUnhold() {
112        Log.d(this, "Attempting to release call from hold");
113        if (Call.State.HOLDING == mState) {
114            try {
115                // TODO: This doesn't handle multiple calls across connection services yet
116                mOriginalConnection.getCall().getPhone().switchHoldingAndActive();
117            } catch (CallStateException e) {
118                Log.e(this, e, "Exception occurred while trying to release call from hold.");
119            }
120        } else {
121            Log.w(this, "Cannot release a call that is not already on hold from hold.");
122        }
123        super.onUnhold();
124    }
125
126    @Override
127    public void onSetAudioState(CallAudioState audioState) {
128        // TODO: update TTY mode.
129        if (mOriginalConnection != null) {
130            Call call = mOriginalConnection.getCall();
131            if (call != null) {
132                call.getPhone().setEchoSuppressionEnabled();
133            }
134        }
135        super.onSetAudioState(audioState);
136    }
137
138    protected void hangup(int disconnectCause) {
139        if (mOriginalConnection != null) {
140            try {
141                Call call = mOriginalConnection.getCall();
142                if (call != null && !call.isMultiparty()) {
143                    call.hangup();
144                } else {
145                    mOriginalConnection.hangup();
146                }
147                // Set state deliberately since we are going to close() and will no longer be
148                // listening to state updates from mOriginalConnection
149                setDisconnected(disconnectCause, null);
150            } catch (CallStateException e) {
151                Log.e(this, e, "Call to Connection.hangup failed with exception");
152            }
153        }
154        close();
155    }
156
157    private void updateState() {
158        if (mOriginalConnection == null) {
159            return;
160        }
161
162        Call.State newState = mOriginalConnection.getState();
163        Log.v(this, "Update state from %s to %s for %s", mState, newState, this);
164        if (mState != newState) {
165            Log.d(this, "mOriginalConnection new state = %s", newState);
166
167            mState = newState;
168            switch (newState) {
169                case IDLE:
170                    break;
171                case ACTIVE:
172                    setActive();
173                    break;
174                case HOLDING:
175                    setOnHold();
176                    break;
177                case DIALING:
178                case ALERTING:
179                    setDialing();
180                    break;
181                case INCOMING:
182                case WAITING:
183                    setRinging();
184                    break;
185                case DISCONNECTED:
186                    setDisconnected(mOriginalConnection.getDisconnectCause(), null);
187                    break;
188                case DISCONNECTING:
189                    break;
190            }
191        }
192    }
193
194    private void close() {
195        if (mOriginalConnection != null) {
196            Call call = mOriginalConnection.getCall();
197            if (call != null) {
198                call.getPhone().unregisterForPreciseCallStateChanged(mHandler);
199            }
200            mOriginalConnection = null;
201            setDestroyed();
202        }
203    }
204
205    private class StateHandler extends Handler {
206        @Override
207        public void handleMessage(Message msg) {
208            switch (msg.what) {
209                case EVENT_PRECISE_CALL_STATE_CHANGED:
210                    updateState();
211                    break;
212            }
213        }
214    }
215}
216