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