• 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.telecom.PhoneCapabilities;
24 import android.telephony.DisconnectCause;
25 import android.telephony.PhoneNumberUtils;
26 
27 import com.android.internal.telephony.Call;
28 import com.android.internal.telephony.CallStateException;
29 import com.android.internal.telephony.Connection;
30 import com.android.internal.telephony.Phone;
31 import com.android.phone.Constants;
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 final 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)77     CdmaConnection(
78             Connection connection,
79             EmergencyTonePlayer emergencyTonePlayer,
80             boolean allowMute,
81             boolean isOutgoing) {
82         super(connection);
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     @Override
onStateChanged(int state)140     public void onStateChanged(int state) {
141         Connection originalConnection = getOriginalConnection();
142         mIsCallWaiting = originalConnection != null &&
143                 originalConnection.getState() == Call.State.WAITING;
144 
145         if (state == android.telecom.Connection.STATE_DIALING) {
146             if (isEmergency()) {
147                 mEmergencyTonePlayer.start();
148             }
149         } else {
150             // No need to check if it is an emergency call, since it is a no-op if it isn't started.
151             mEmergencyTonePlayer.stop();
152         }
153 
154         super.onStateChanged(state);
155     }
156 
157     @Override
buildCallCapabilities()158     protected int buildCallCapabilities() {
159         int capabilities = 0;
160         if (mAllowMute) {
161             capabilities = PhoneCapabilities.MUTE;
162         }
163         return capabilities;
164     }
165 
forceAsDialing(boolean isDialing)166     void forceAsDialing(boolean isDialing) {
167         if (isDialing) {
168             setDialing();
169         } else {
170             updateState();
171         }
172     }
173 
isOutgoing()174     boolean isOutgoing() {
175         return mIsOutgoing;
176     }
177 
isCallWaiting()178     boolean isCallWaiting() {
179         return mIsCallWaiting;
180     }
181 
182     /**
183      * We do not get much in the way of confirmation for Cdma call waiting calls. There is no
184      * indication that a rejected call succeeded, a call waiting call has stopped. Instead we
185      * simulate this for the user. We allow TIMEOUT_CALL_WAITING_MILLIS milliseconds before we
186      * assume that the call was missed and reject it ourselves. reject the call automatically.
187      */
startCallWaitingTimer()188     private void startCallWaitingTimer() {
189         mHandler.sendEmptyMessageDelayed(MSG_CALL_WAITING_MISSED, TIMEOUT_CALL_WAITING_MILLIS);
190     }
191 
hangupCallWaiting(int telephonyDisconnectCause)192     private void hangupCallWaiting(int telephonyDisconnectCause) {
193         Connection originalConnection = getOriginalConnection();
194         if (originalConnection != null) {
195             try {
196                 originalConnection.hangup();
197             } catch (CallStateException e) {
198                 Log.e(this, e, "Failed to hangup call waiting call");
199             }
200             setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause));
201         }
202     }
203 
204     /**
205      * Read the settings to determine which type of DTMF method this CDMA phone calls.
206      */
useBurstDtmf()207     private boolean useBurstDtmf() {
208         int dtmfTypeSetting = Settings.System.getInt(
209                 getPhone().getContext().getContentResolver(),
210                 Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
211                 Constants.DTMF_TONE_TYPE_NORMAL);
212         return dtmfTypeSetting == Constants.DTMF_TONE_TYPE_NORMAL;
213     }
214 
sendShortDtmfToNetwork(char digit)215     private void sendShortDtmfToNetwork(char digit) {
216         synchronized(mDtmfQueue) {
217             if (mDtmfBurstConfirmationPending) {
218                 mDtmfQueue.add(new Character(digit));
219             } else {
220                 sendBurstDtmfStringLocked(Character.toString(digit));
221             }
222         }
223     }
224 
sendBurstDtmfStringLocked(String dtmfString)225     private void sendBurstDtmfStringLocked(String dtmfString) {
226         getPhone().sendBurstDtmf(
227                 dtmfString, 0, 0, mHandler.obtainMessage(MSG_DTMF_SEND_CONFIRMATION));
228         mDtmfBurstConfirmationPending = true;
229     }
230 
handleBurstDtmfConfirmation()231     private void handleBurstDtmfConfirmation() {
232         String dtmfDigits = null;
233         synchronized(mDtmfQueue) {
234             mDtmfBurstConfirmationPending = false;
235             if (!mDtmfQueue.isEmpty()) {
236                 StringBuilder builder = new StringBuilder(mDtmfQueue.size());
237                 while (!mDtmfQueue.isEmpty()) {
238                     builder.append(mDtmfQueue.poll());
239                 }
240                 dtmfDigits = builder.toString();
241 
242                 // It would be nice to log the digit, but since DTMF digits can be passwords
243                 // to things, or other secure account numbers, we want to keep it away from
244                 // the logs.
245                 Log.i(this, "%d dtmf character[s] removed from the queue", dtmfDigits.length());
246             }
247             if (dtmfDigits != null) {
248                 sendBurstDtmfStringLocked(dtmfDigits);
249             }
250         }
251     }
252 
isEmergency()253     private boolean isEmergency() {
254         Phone phone = getPhone();
255         return phone != null &&
256                 PhoneNumberUtils.isLocalEmergencyNumber(
257                     phone.getContext(), getAddress().getSchemeSpecificPart());
258     }
259 }
260