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.telecom.PhoneCapabilities; 24 import android.telephony.DisconnectCause; 25 import android.telephony.PhoneNumberUtils; 26 27 import com.android.internal.telephony.Call; 28 import com.android.internal.telephony.CallStateException; 29 import com.android.internal.telephony.Connection; 30 import com.android.internal.telephony.Phone; 31 import com.android.phone.Constants; 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 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 final 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 CdmaConnection( Connection connection, EmergencyTonePlayer emergencyTonePlayer, boolean allowMute, boolean isOutgoing)77 CdmaConnection( 78 Connection connection, 79 EmergencyTonePlayer emergencyTonePlayer, 80 boolean allowMute, 81 boolean isOutgoing) { 82 super(connection); 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 @Override onStateChanged(int state)140 public void onStateChanged(int state) { 141 Connection originalConnection = getOriginalConnection(); 142 mIsCallWaiting = originalConnection != null && 143 originalConnection.getState() == Call.State.WAITING; 144 145 if (state == android.telecom.Connection.STATE_DIALING) { 146 if (isEmergency()) { 147 mEmergencyTonePlayer.start(); 148 } 149 } else { 150 // No need to check if it is an emergency call, since it is a no-op if it isn't started. 151 mEmergencyTonePlayer.stop(); 152 } 153 154 super.onStateChanged(state); 155 } 156 157 @Override buildCallCapabilities()158 protected int buildCallCapabilities() { 159 int capabilities = 0; 160 if (mAllowMute) { 161 capabilities = PhoneCapabilities.MUTE; 162 } 163 return capabilities; 164 } 165 forceAsDialing(boolean isDialing)166 void forceAsDialing(boolean isDialing) { 167 if (isDialing) { 168 setDialing(); 169 } else { 170 updateState(); 171 } 172 } 173 isOutgoing()174 boolean isOutgoing() { 175 return mIsOutgoing; 176 } 177 isCallWaiting()178 boolean isCallWaiting() { 179 return mIsCallWaiting; 180 } 181 182 /** 183 * We do not get much in the way of confirmation for Cdma call waiting calls. There is no 184 * indication that a rejected call succeeded, a call waiting call has stopped. Instead we 185 * simulate this for the user. We allow TIMEOUT_CALL_WAITING_MILLIS milliseconds before we 186 * assume that the call was missed and reject it ourselves. reject the call automatically. 187 */ startCallWaitingTimer()188 private void startCallWaitingTimer() { 189 mHandler.sendEmptyMessageDelayed(MSG_CALL_WAITING_MISSED, TIMEOUT_CALL_WAITING_MILLIS); 190 } 191 hangupCallWaiting(int telephonyDisconnectCause)192 private void hangupCallWaiting(int telephonyDisconnectCause) { 193 Connection originalConnection = getOriginalConnection(); 194 if (originalConnection != null) { 195 try { 196 originalConnection.hangup(); 197 } catch (CallStateException e) { 198 Log.e(this, e, "Failed to hangup call waiting call"); 199 } 200 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause)); 201 } 202 } 203 204 /** 205 * Read the settings to determine which type of DTMF method this CDMA phone calls. 206 */ useBurstDtmf()207 private boolean useBurstDtmf() { 208 int dtmfTypeSetting = Settings.System.getInt( 209 getPhone().getContext().getContentResolver(), 210 Settings.System.DTMF_TONE_TYPE_WHEN_DIALING, 211 Constants.DTMF_TONE_TYPE_NORMAL); 212 return dtmfTypeSetting == Constants.DTMF_TONE_TYPE_NORMAL; 213 } 214 sendShortDtmfToNetwork(char digit)215 private void sendShortDtmfToNetwork(char digit) { 216 synchronized(mDtmfQueue) { 217 if (mDtmfBurstConfirmationPending) { 218 mDtmfQueue.add(new Character(digit)); 219 } else { 220 sendBurstDtmfStringLocked(Character.toString(digit)); 221 } 222 } 223 } 224 sendBurstDtmfStringLocked(String dtmfString)225 private void sendBurstDtmfStringLocked(String dtmfString) { 226 getPhone().sendBurstDtmf( 227 dtmfString, 0, 0, mHandler.obtainMessage(MSG_DTMF_SEND_CONFIRMATION)); 228 mDtmfBurstConfirmationPending = true; 229 } 230 handleBurstDtmfConfirmation()231 private void handleBurstDtmfConfirmation() { 232 String dtmfDigits = null; 233 synchronized(mDtmfQueue) { 234 mDtmfBurstConfirmationPending = false; 235 if (!mDtmfQueue.isEmpty()) { 236 StringBuilder builder = new StringBuilder(mDtmfQueue.size()); 237 while (!mDtmfQueue.isEmpty()) { 238 builder.append(mDtmfQueue.poll()); 239 } 240 dtmfDigits = builder.toString(); 241 242 // It would be nice to log the digit, but since DTMF digits can be passwords 243 // to things, or other secure account numbers, we want to keep it away from 244 // the logs. 245 Log.i(this, "%d dtmf character[s] removed from the queue", dtmfDigits.length()); 246 } 247 if (dtmfDigits != null) { 248 sendBurstDtmfStringLocked(dtmfDigits); 249 } 250 } 251 } 252 isEmergency()253 private boolean isEmergency() { 254 Phone phone = getPhone(); 255 return phone != null && 256 PhoneNumberUtils.isLocalEmergencyNumber( 257 phone.getContext(), getAddress().getSchemeSpecificPart()); 258 } 259 } 260