TelephonyConnection.java revision 2093a451b17c26f4341e9565b65dcaa0e20bbd7d
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 */
32abstract class 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 abstract int buildCallCapabilities();
139
140    final void updateCallCapabilities() {
141        int newCallCapabilities = buildCallCapabilities();
142        if (getCallCapabilities() != newCallCapabilities) {
143            setCallCapabilities(newCallCapabilities);
144        }
145    }
146
147    final void onAddedToCallService() {
148        updateCallCapabilities();
149        if (mOriginalConnection != null) {
150            setCallerDisplayName(
151                    mOriginalConnection.getCnapName(),
152                    mOriginalConnection.getCnapNamePresentation());
153        }
154    }
155
156    protected void hangup(int disconnectCause) {
157        if (mOriginalConnection != null) {
158            try {
159                Call call = mOriginalConnection.getCall();
160                if (call != null && !call.isMultiparty()) {
161                    call.hangup();
162                } else {
163                    mOriginalConnection.hangup();
164                }
165                // Set state deliberately since we are going to close() and will no longer be
166                // listening to state updates from mOriginalConnection
167                setDisconnected(disconnectCause, null);
168            } catch (CallStateException e) {
169                Log.e(this, e, "Call to Connection.hangup failed with exception");
170            }
171        }
172        close();
173    }
174
175    private void updateState() {
176        if (mOriginalConnection == null) {
177            return;
178        }
179
180        Call.State newState = mOriginalConnection.getState();
181        Log.v(this, "Update state from %s to %s for %s", mState, newState, this);
182        if (mState != newState) {
183            Log.d(this, "mOriginalConnection new state = %s", newState);
184
185            mState = newState;
186            switch (newState) {
187                case IDLE:
188                    break;
189                case ACTIVE:
190                    setActive();
191                    break;
192                case HOLDING:
193                    setOnHold();
194                    break;
195                case DIALING:
196                case ALERTING:
197                    setDialing();
198                    break;
199                case INCOMING:
200                case WAITING:
201                    setRinging();
202                    break;
203                case DISCONNECTED:
204                    setDisconnected(mOriginalConnection.getDisconnectCause(), null);
205                    break;
206                case DISCONNECTING:
207                    break;
208            }
209            updateCallCapabilities();
210        }
211    }
212
213    private void close() {
214        if (mOriginalConnection != null) {
215            Call call = mOriginalConnection.getCall();
216            if (call != null) {
217                call.getPhone().unregisterForPreciseCallStateChanged(mHandler);
218            }
219            mOriginalConnection = null;
220            setDestroyed();
221        }
222    }
223
224    private class StateHandler extends Handler {
225        @Override
226        public void handleMessage(Message msg) {
227            switch (msg.what) {
228                case EVENT_PRECISE_CALL_STATE_CHANGED:
229                    updateState();
230                    break;
231            }
232        }
233    }
234}
235