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