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 17package com.android.services.telephony; 18 19import android.os.Handler; 20import android.os.Message; 21 22import android.provider.Settings; 23import android.telephony.DisconnectCause; 24import android.telephony.PhoneNumberUtils; 25 26import com.android.internal.telephony.Call; 27import com.android.internal.telephony.CallStateException; 28import com.android.internal.telephony.Connection; 29import com.android.internal.telephony.imsphone.ImsPhoneConnection; 30import com.android.internal.telephony.Phone; 31import com.android.phone.settings.SettingsConstants; 32 33import java.util.LinkedList; 34import java.util.Queue; 35 36/** 37 * Manages a single phone call handled by CDMA. 38 */ 39final 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 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 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 109 public void onStopDtmfTone() { 110 if (!useBurstDtmf()) { 111 getPhone().stopDtmf(); 112 } 113 } 114 115 @Override 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 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 150 public TelephonyConnection cloneConnection() { 151 CdmaConnection cdmaConnection = new CdmaConnection(getOriginalConnection(), 152 mEmergencyTonePlayer, mAllowMute, mIsOutgoing, getTelecomCallId()); 153 return cdmaConnection; 154 } 155 156 @Override 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 178 protected int buildConnectionCapabilities() { 179 int capabilities = super.buildConnectionCapabilities(); 180 if (mAllowMute) { 181 capabilities |= CAPABILITY_MUTE; 182 } 183 return capabilities; 184 } 185 186 @Override 187 public void performConference(android.telecom.Connection 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 195 void forceAsDialing(boolean isDialing) { 196 if (isDialing) { 197 setStateOverride(Call.State.DIALING); 198 } else { 199 resetStateOverride(); 200 } 201 } 202 203 boolean isOutgoing() { 204 return mIsOutgoing; 205 } 206 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 */ 217 private void startCallWaitingTimer() { 218 mHandler.sendEmptyMessageDelayed(MSG_CALL_WAITING_MISSED, TIMEOUT_CALL_WAITING_MILLIS); 219 } 220 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 */ 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 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 258 private void sendBurstDtmfStringLocked(String dtmfString) { 259 getPhone().sendBurstDtmf( 260 dtmfString, 0, 0, mHandler.obtainMessage(MSG_DTMF_SEND_CONFIRMATION)); 261 mDtmfBurstConfirmationPending = true; 262 } 263 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 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 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