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