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