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