• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.services.telephony;
18 
19 import android.content.Context;
20 import android.os.Handler;
21 import android.os.Message;
22 import android.provider.Settings;
23 import android.telephony.DisconnectCause;
24 import android.telephony.TelephonyManager;
25 
26 import com.android.internal.telephony.Call;
27 import com.android.internal.telephony.CallStateException;
28 import com.android.internal.telephony.Connection;
29 import com.android.internal.telephony.Phone;
30 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
31 import com.android.phone.settings.SettingsConstants;
32 
33 import java.util.LinkedList;
34 import java.util.Queue;
35 
36 /**
37  * Manages a single phone call handled by CDMA.
38  */
39 final class CdmaConnection extends TelephonyConnection {
40 
41     private static final int MSG_CALL_WAITING_MISSED = 1;
42     private static final int MSG_DTMF_SEND_CONFIRMATION = 2;
43     private static final int MSG_CDMA_LINE_CONTROL_INFO_REC = 3;
44     private static final int TIMEOUT_CALL_WAITING_MILLIS = 20 * 1000;
45 
46     private final Handler mHandler = new Handler() {
47 
48         /** ${inheritDoc} */
49         @Override
50         public void handleMessage(Message msg) {
51             switch (msg.what) {
52                 case MSG_CALL_WAITING_MISSED:
53                     hangupCallWaiting(DisconnectCause.INCOMING_MISSED);
54                     break;
55                 case MSG_DTMF_SEND_CONFIRMATION:
56                     handleBurstDtmfConfirmation();
57                     break;
58                 case MSG_CDMA_LINE_CONTROL_INFO_REC:
59                     handleCdmaConnectionTimeReset();
60                     break;
61                 default:
62                     break;
63             }
64         }
65 
66     };
67 
68     /**
69      * {@code True} if the CDMA connection should allow mute.
70      */
71     private boolean mAllowMute;
72     // Queue of pending short-DTMF characters.
73     private final Queue<Character> mDtmfQueue = new LinkedList<>();
74     private final EmergencyTonePlayer mEmergencyTonePlayer;
75 
76     // Indicates that the DTMF confirmation from telephony is pending.
77     private boolean mDtmfBurstConfirmationPending = false;
78     private boolean mIsCallWaiting;
79     private boolean mIsConnectionTimeReset = false;
80 
CdmaConnection( Connection connection, EmergencyTonePlayer emergencyTonePlayer, boolean allowMute, int callDirection, String telecomCallId)81     CdmaConnection(
82             Connection connection,
83             EmergencyTonePlayer emergencyTonePlayer,
84             boolean allowMute,
85             int callDirection,
86             String telecomCallId) {
87         super(connection, telecomCallId, callDirection);
88         mEmergencyTonePlayer = emergencyTonePlayer;
89         mAllowMute = allowMute;
90         mIsCallWaiting = connection != null && connection.getState() == Call.State.WAITING;
91         boolean isImsCall = getOriginalConnection() instanceof ImsPhoneConnection;
92         // Start call waiting timer for CDMA waiting call.
93         if (mIsCallWaiting && !isImsCall) {
94             startCallWaitingTimer();
95         }
96     }
97 
98     /** {@inheritDoc} */
99     @Override
onPlayDtmfTone(char digit)100     public void onPlayDtmfTone(char digit) {
101         if (useBurstDtmf()) {
102             Log.i(this, "sending dtmf digit as burst");
103             sendShortDtmfToNetwork(digit);
104         } else {
105             Log.i(this, "sending dtmf digit directly");
106             getPhone().startDtmf(digit);
107         }
108     }
109 
110     /** {@inheritDoc} */
111     @Override
onStopDtmfTone()112     public void onStopDtmfTone() {
113         if (!useBurstDtmf()) {
114             getPhone().stopDtmf();
115         }
116     }
117 
118     @Override
onReject()119     public void onReject() {
120         Connection connection = getOriginalConnection();
121         if (connection != null) {
122             switch (connection.getState()) {
123                 case INCOMING:
124                     // Normal ringing calls are handled the generic way.
125                     super.onReject();
126                     break;
127                 case WAITING:
128                     hangupCallWaiting(DisconnectCause.INCOMING_REJECTED);
129                     break;
130                 default:
131                     Log.e(this, new Exception(), "Rejecting a non-ringing call");
132                     // might as well hang this up, too.
133                     super.onReject();
134                     break;
135             }
136         }
137     }
138 
139     @Override
onAnswer()140     public void onAnswer() {
141         mHandler.removeMessages(MSG_CALL_WAITING_MISSED);
142         super.onAnswer();
143     }
144 
145     /**
146      * Clones the current {@link CdmaConnection}.
147      * <p>
148      * Listeners are not copied to the new instance.
149      *
150      * @return The cloned connection.
151      */
152     @Override
cloneConnection()153     public TelephonyConnection cloneConnection() {
154         CdmaConnection cdmaConnection = new CdmaConnection(getOriginalConnection(),
155                 mEmergencyTonePlayer, mAllowMute, getCallDirection(), getTelecomCallId());
156         return cdmaConnection;
157     }
158 
159     @Override
onStateChanged(int state)160     public void onStateChanged(int state) {
161         Connection originalConnection = getOriginalConnection();
162         mIsCallWaiting = originalConnection != null &&
163                 originalConnection.getState() == Call.State.WAITING;
164 
165         if (mEmergencyTonePlayer != null) {
166             if (state == android.telecom.Connection.STATE_DIALING) {
167                 if (isEmergency()) {
168                     mEmergencyTonePlayer.start();
169                 }
170             } else {
171                 // No need to check if it is an emergency call, since it is a no-op if it
172                 // isn't started.
173                 mEmergencyTonePlayer.stop();
174             }
175         }
176 
177         super.onStateChanged(state);
178     }
179 
180     @Override
buildConnectionCapabilities()181     protected int buildConnectionCapabilities() {
182         int capabilities = super.buildConnectionCapabilities();
183         if (mAllowMute) {
184             capabilities |= CAPABILITY_MUTE;
185         }
186         return capabilities;
187     }
188 
189     @Override
performConference(android.telecom.Connection otherConnection)190     public void performConference(android.telecom.Connection otherConnection) {
191         if (isImsConnection()) {
192             super.performConference(otherConnection);
193         } else {
194             Log.w(this, "Non-IMS CDMA Connection attempted to call performConference.");
195         }
196     }
197 
forceAsDialing(boolean isDialing)198     void forceAsDialing(boolean isDialing) {
199         if (isDialing) {
200             setStateOverride(Call.State.DIALING);
201         } else {
202             resetStateOverride();
203         }
204     }
205 
isCallWaiting()206     boolean isCallWaiting() {
207         return mIsCallWaiting;
208     }
209 
210     /**
211      * We do not get much in the way of confirmation for Cdma call waiting calls. There is no
212      * indication that a rejected call succeeded, a call waiting call has stopped. Instead we
213      * simulate this for the user. We allow TIMEOUT_CALL_WAITING_MILLIS milliseconds before we
214      * assume that the call was missed and reject it ourselves. reject the call automatically.
215      */
startCallWaitingTimer()216     private void startCallWaitingTimer() {
217         mHandler.sendEmptyMessageDelayed(MSG_CALL_WAITING_MISSED, TIMEOUT_CALL_WAITING_MILLIS);
218     }
219 
hangupCallWaiting(int telephonyDisconnectCause)220     private void hangupCallWaiting(int telephonyDisconnectCause) {
221         Connection originalConnection = getOriginalConnection();
222         if (originalConnection != null) {
223             try {
224                 originalConnection.hangup();
225             } catch (CallStateException e) {
226                 Log.e(this, e, "Failed to hangup call waiting call");
227             }
228             setTelephonyConnectionDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
229                     telephonyDisconnectCause, null, getPhone().getPhoneId()));
230         }
231     }
232 
233     /**
234      * Read the settings to determine which type of DTMF method this CDMA phone calls.
235      */
useBurstDtmf()236     private boolean useBurstDtmf() {
237         if (isImsConnection()) {
238             Log.d(this,"in ims call, return false");
239             return false;
240         }
241         int dtmfTypeSetting = Settings.System.getInt(
242                 getPhone().getContext().getContentResolver(),
243                 Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
244                 SettingsConstants.DTMF_TONE_TYPE_NORMAL);
245         return dtmfTypeSetting == SettingsConstants.DTMF_TONE_TYPE_NORMAL;
246     }
247 
sendShortDtmfToNetwork(char digit)248     private void sendShortDtmfToNetwork(char digit) {
249         synchronized(mDtmfQueue) {
250             if (mDtmfBurstConfirmationPending) {
251                 mDtmfQueue.add(new Character(digit));
252             } else {
253                 sendBurstDtmfStringLocked(Character.toString(digit));
254             }
255         }
256     }
257 
sendBurstDtmfStringLocked(String dtmfString)258     private void sendBurstDtmfStringLocked(String dtmfString) {
259         getPhone().sendBurstDtmf(
260                 dtmfString, 0, 0, mHandler.obtainMessage(MSG_DTMF_SEND_CONFIRMATION));
261         mDtmfBurstConfirmationPending = true;
262     }
263 
handleBurstDtmfConfirmation()264     private void handleBurstDtmfConfirmation() {
265         String dtmfDigits = null;
266         synchronized(mDtmfQueue) {
267             mDtmfBurstConfirmationPending = false;
268             if (!mDtmfQueue.isEmpty()) {
269                 StringBuilder builder = new StringBuilder(mDtmfQueue.size());
270                 while (!mDtmfQueue.isEmpty()) {
271                     builder.append(mDtmfQueue.poll());
272                 }
273                 dtmfDigits = builder.toString();
274 
275                 // It would be nice to log the digit, but since DTMF digits can be passwords
276                 // to things, or other secure account numbers, we want to keep it away from
277                 // the logs.
278                 Log.i(this, "%d dtmf character[s] removed from the queue", dtmfDigits.length());
279             }
280             if (dtmfDigits != null) {
281                 sendBurstDtmfStringLocked(dtmfDigits);
282             }
283         }
284     }
285 
isEmergency()286     private boolean isEmergency() {
287         Phone phone = getPhone();
288         if (phone != null && getAddress() != null) {
289             TelephonyManager tm = (TelephonyManager) phone.getContext()
290                     .getSystemService(Context.TELEPHONY_SERVICE);
291             if (tm != null) {
292                 return tm.isEmergencyNumber(getAddress().getSchemeSpecificPart());
293             }
294         }
295         return false;
296     }
297 
298     /**
299      * Called when ECM mode is exited; set the connection to allow mute and update the connection
300      * capabilities.
301      */
302     @Override
handleExitedEcmMode()303     protected void handleExitedEcmMode() {
304         // We allow mute upon existing ECM mode and rebuild the capabilities.
305         mAllowMute = true;
306         super.handleExitedEcmMode();
307     }
308 
handleCdmaConnectionTimeReset()309     private void handleCdmaConnectionTimeReset() {
310         boolean isImsCall = getOriginalConnection() instanceof ImsPhoneConnection;
311         if (!isImsCall && !mIsConnectionTimeReset && isOutgoingCall()
312                 && getOriginalConnection() != null
313                 && getOriginalConnection().getState() == Call.State.ACTIVE
314                 && getOriginalConnection().getDurationMillis() > 0) {
315             mIsConnectionTimeReset = true;
316             getOriginalConnection().resetConnectionTime();
317             resetConnectionTime();
318         }
319     }
320 
321     @Override
setOriginalConnection(com.android.internal.telephony.Connection originalConnection)322     void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
323         super.setOriginalConnection(originalConnection);
324         if (getPhone() != null) {
325             getPhone().registerForLineControlInfo(mHandler, MSG_CDMA_LINE_CONTROL_INFO_REC, null);
326         }
327     }
328 
329     @Override
close()330     public void close() {
331         mIsConnectionTimeReset = false;
332         if (getPhone() != null) {
333             getPhone().unregisterForLineControlInfo(mHandler);
334         }
335         super.close();
336     }
337 }
338