• 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.os.Handler;
20 import android.os.Message;
21 
22 import android.provider.Settings;
23 import android.telephony.DisconnectCause;
24 import android.telephony.PhoneNumberUtils;
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.imsphone.ImsPhoneConnection;
30 import com.android.internal.telephony.Phone;
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 TIMEOUT_CALL_WAITING_MILLIS = 20 * 1000;
44 
45     private final Handler mHandler = new Handler() {
46 
47         /** ${inheritDoc} */
48         @Override
49         public void handleMessage(Message msg) {
50             switch (msg.what) {
51                 case MSG_CALL_WAITING_MISSED:
52                     hangupCallWaiting(DisconnectCause.INCOMING_MISSED);
53                     break;
54                 case MSG_DTMF_SEND_CONFIRMATION:
55                     handleBurstDtmfConfirmation();
56                     break;
57                 default:
58                     break;
59             }
60         }
61 
62     };
63 
64     /**
65      * {@code True} if the CDMA connection should allow mute.
66      */
67     private boolean mAllowMute;
68     private final boolean mIsOutgoing;
69     // Queue of pending short-DTMF characters.
70     private final Queue<Character> mDtmfQueue = new LinkedList<>();
71     private final EmergencyTonePlayer mEmergencyTonePlayer;
72 
73     // Indicates that the DTMF confirmation from telephony is pending.
74     private boolean mDtmfBurstConfirmationPending = false;
75     private boolean mIsCallWaiting;
76 
CdmaConnection( Connection connection, EmergencyTonePlayer emergencyTonePlayer, boolean allowMute, boolean isOutgoing, String telecomCallId)77     CdmaConnection(
78             Connection connection,
79             EmergencyTonePlayer emergencyTonePlayer,
80             boolean allowMute,
81             boolean isOutgoing,
82             String telecomCallId) {
83         super(connection, telecomCallId);
84         mEmergencyTonePlayer = emergencyTonePlayer;
85         mAllowMute = allowMute;
86         mIsOutgoing = isOutgoing;
87         mIsCallWaiting = connection != null && connection.getState() == Call.State.WAITING;
88         boolean isImsCall = getOriginalConnection() instanceof ImsPhoneConnection;
89         // Start call waiting timer for CDMA waiting call.
90         if (mIsCallWaiting && !isImsCall) {
91             startCallWaitingTimer();
92         }
93     }
94 
95     /** {@inheritDoc} */
96     @Override
onPlayDtmfTone(char digit)97     public void onPlayDtmfTone(char digit) {
98         if (useBurstDtmf()) {
99             Log.i(this, "sending dtmf digit as burst");
100             sendShortDtmfToNetwork(digit);
101         } else {
102             Log.i(this, "sending dtmf digit directly");
103             getPhone().startDtmf(digit);
104         }
105     }
106 
107     /** {@inheritDoc} */
108     @Override
onStopDtmfTone()109     public void onStopDtmfTone() {
110         if (!useBurstDtmf()) {
111             getPhone().stopDtmf();
112         }
113     }
114 
115     @Override
onReject()116     public void onReject() {
117         Connection connection = getOriginalConnection();
118         if (connection != null) {
119             switch (connection.getState()) {
120                 case INCOMING:
121                     // Normal ringing calls are handled the generic way.
122                     super.onReject();
123                     break;
124                 case WAITING:
125                     hangupCallWaiting(DisconnectCause.INCOMING_REJECTED);
126                     break;
127                 default:
128                     Log.e(this, new Exception(), "Rejecting a non-ringing call");
129                     // might as well hang this up, too.
130                     super.onReject();
131                     break;
132             }
133         }
134     }
135 
136     @Override
onAnswer()137     public void onAnswer() {
138         mHandler.removeMessages(MSG_CALL_WAITING_MISSED);
139         super.onAnswer();
140     }
141 
142     /**
143      * Clones the current {@link CdmaConnection}.
144      * <p>
145      * Listeners are not copied to the new instance.
146      *
147      * @return The cloned connection.
148      */
149     @Override
cloneConnection()150     public TelephonyConnection cloneConnection() {
151         CdmaConnection cdmaConnection = new CdmaConnection(getOriginalConnection(),
152                 mEmergencyTonePlayer, mAllowMute, mIsOutgoing, getTelecomCallId());
153         return cdmaConnection;
154     }
155 
156     @Override
onStateChanged(int state)157     public void onStateChanged(int state) {
158         Connection originalConnection = getOriginalConnection();
159         mIsCallWaiting = originalConnection != null &&
160                 originalConnection.getState() == Call.State.WAITING;
161 
162         if (mEmergencyTonePlayer != null) {
163             if (state == android.telecom.Connection.STATE_DIALING) {
164                 if (isEmergency()) {
165                     mEmergencyTonePlayer.start();
166                 }
167             } else {
168                 // No need to check if it is an emergency call, since it is a no-op if it
169                 // isn't started.
170                 mEmergencyTonePlayer.stop();
171             }
172         }
173 
174         super.onStateChanged(state);
175     }
176 
177     @Override
buildConnectionCapabilities()178     protected int buildConnectionCapabilities() {
179         int capabilities = super.buildConnectionCapabilities();
180         if (mAllowMute) {
181             capabilities |= CAPABILITY_MUTE;
182         }
183         return capabilities;
184     }
185 
186     @Override
performConference(TelephonyConnection otherConnection)187     public void performConference(TelephonyConnection otherConnection) {
188         if (isImsConnection()) {
189             super.performConference(otherConnection);
190         } else {
191             Log.w(this, "Non-IMS CDMA Connection attempted to call performConference.");
192         }
193     }
194 
forceAsDialing(boolean isDialing)195     void forceAsDialing(boolean isDialing) {
196         if (isDialing) {
197             setStateOverride(Call.State.DIALING);
198         } else {
199             resetStateOverride();
200         }
201     }
202 
isOutgoing()203     boolean isOutgoing() {
204         return mIsOutgoing;
205     }
206 
isCallWaiting()207     boolean isCallWaiting() {
208         return mIsCallWaiting;
209     }
210 
211     /**
212      * We do not get much in the way of confirmation for Cdma call waiting calls. There is no
213      * indication that a rejected call succeeded, a call waiting call has stopped. Instead we
214      * simulate this for the user. We allow TIMEOUT_CALL_WAITING_MILLIS milliseconds before we
215      * assume that the call was missed and reject it ourselves. reject the call automatically.
216      */
startCallWaitingTimer()217     private void startCallWaitingTimer() {
218         mHandler.sendEmptyMessageDelayed(MSG_CALL_WAITING_MISSED, TIMEOUT_CALL_WAITING_MILLIS);
219     }
220 
hangupCallWaiting(int telephonyDisconnectCause)221     private void hangupCallWaiting(int telephonyDisconnectCause) {
222         Connection originalConnection = getOriginalConnection();
223         if (originalConnection != null) {
224             try {
225                 originalConnection.hangup();
226             } catch (CallStateException e) {
227                 Log.e(this, e, "Failed to hangup call waiting call");
228             }
229             setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause));
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         return phone != null &&
289                 PhoneNumberUtils.isLocalEmergencyNumber(
290                     phone.getContext(), getAddress().getSchemeSpecificPart());
291     }
292 
293     /**
294      * Called when ECM mode is exited; set the connection to allow mute and update the connection
295      * capabilities.
296      */
297     @Override
handleExitedEcmMode()298     protected void handleExitedEcmMode() {
299         // We allow mute upon existing ECM mode and rebuild the capabilities.
300         mAllowMute = true;
301         super.handleExitedEcmMode();
302     }
303 }
304