TelephonyConnection.java revision 3199aa7f8bcb48569eb8289abc055ba0f8496ba8
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.AsyncResult; 20import android.os.Handler; 21import android.os.Message; 22import android.telecomm.CallAudioState; 23import android.telephony.DisconnectCause; 24 25import com.android.internal.telephony.Call; 26import com.android.internal.telephony.CallStateException; 27import com.android.internal.telephony.Connection.PostDialListener; 28import com.android.internal.telephony.Phone; 29import android.telecomm.Connection; 30 31import java.util.List; 32 33/** 34 * Base class for CDMA and GSM connections. 35 */ 36abstract class TelephonyConnection extends Connection { 37 private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1; 38 private static final int MSG_RINGBACK_TONE = 2; 39 40 private final Handler mHandler = new Handler() { 41 @Override 42 public void handleMessage(Message msg) { 43 // TODO: This code assumes that there is only one connection in the foreground call, 44 // in other words, it punts on network-mediated conference calling. 45 if (getOriginalConnection() != getForegroundConnection()) { 46 Log.v(TelephonyConnection.this, "handleMessage, original connection is not " + 47 "foreground connection, skipping"); 48 return; 49 } 50 51 switch (msg.what) { 52 case MSG_PRECISE_CALL_STATE_CHANGED: 53 Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED"); 54 updateState(); 55 break; 56 case MSG_RINGBACK_TONE: 57 Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE"); 58 setRequestingRingback((Boolean) ((AsyncResult) msg.obj).result); 59 break; 60 } 61 } 62 }; 63 64 private final PostDialListener mPostDialListener = new PostDialListener() { 65 @Override 66 public void onPostDialWait() { 67 Log.v(TelephonyConnection.this, "onPostDialWait"); 68 if (mOriginalConnection != null) { 69 setPostDialWait(mOriginalConnection.getRemainingPostDialString()); 70 } 71 } 72 }; 73 74 private com.android.internal.telephony.Connection mOriginalConnection; 75 private Call.State mOriginalConnectionState = Call.State.IDLE; 76 77 protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) { 78 Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); 79 mOriginalConnection = originalConnection; 80 getPhone().registerForPreciseCallStateChanged( 81 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null); 82 getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null); 83 mOriginalConnection.addPostDialListener(mPostDialListener); 84 updateState(); 85 } 86 87 @Override 88 protected void onSetAudioState(CallAudioState audioState) { 89 // TODO: update TTY mode. 90 if (getPhone() != null) { 91 getPhone().setEchoSuppressionEnabled(); 92 } 93 } 94 95 @Override 96 protected void onSetState(int state) { 97 Log.v(this, "onSetState, state: " + Connection.stateToString(state)); 98 } 99 100 @Override 101 protected void onDisconnect() { 102 Log.v(this, "onDisconnect"); 103 hangup(DisconnectCause.LOCAL); 104 } 105 106 @Override 107 protected void onSeparate() { 108 Log.v(this, "onSeparate"); 109 if (mOriginalConnection != null) { 110 try { 111 mOriginalConnection.separate(); 112 } catch (CallStateException e) { 113 Log.e(this, e, "Call to Connection.separate failed with exception"); 114 } 115 } 116 } 117 118 @Override 119 protected void onAbort() { 120 Log.v(this, "onAbort"); 121 hangup(DisconnectCause.LOCAL); 122 } 123 124 @Override 125 protected void onHold() { 126 Log.v(this, "onHold"); 127 // TODO(santoscordon): Can dialing calls be put on hold as well since they take up the 128 // foreground call slot? 129 if (Call.State.ACTIVE == mOriginalConnectionState) { 130 Log.v(this, "Holding active call"); 131 try { 132 Phone phone = mOriginalConnection.getCall().getPhone(); 133 Call ringingCall = phone.getRingingCall(); 134 135 // Although the method says switchHoldingAndActive, it eventually calls a RIL method 136 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put 137 // a call on hold while a call-waiting call exists, it'll end up accepting the 138 // call-waiting call, which is bad if that was not the user's intention. We are 139 // cheating here and simply skipping it because we know any attempt to hold a call 140 // while a call-waiting call is happening is likely a request from Telecomm prior to 141 // accepting the call-waiting call. 142 // TODO(santoscordon): Investigate a better solution. It would be great here if we 143 // could "fake" hold by silencing the audio and microphone streams for this call 144 // instead of actually putting it on hold. 145 if (ringingCall.getState() != Call.State.WAITING) { 146 phone.switchHoldingAndActive(); 147 } 148 149 // TODO(santoscordon): Cdma calls are slightly different. 150 } catch (CallStateException e) { 151 Log.e(this, e, "Exception occurred while trying to put call on hold."); 152 } 153 } else { 154 Log.w(this, "Cannot put a call that is not currently active on hold."); 155 } 156 } 157 158 @Override 159 protected void onUnhold() { 160 Log.v(this, "onUnhold"); 161 if (Call.State.HOLDING == mOriginalConnectionState) { 162 try { 163 // TODO: This doesn't handle multiple calls across connection services yet 164 mOriginalConnection.getCall().getPhone().switchHoldingAndActive(); 165 } catch (CallStateException e) { 166 Log.e(this, e, "Exception occurred while trying to release call from hold."); 167 } 168 } else { 169 Log.w(this, "Cannot release a call that is not already on hold from hold."); 170 } 171 } 172 173 @Override 174 protected void onAnswer() { 175 Log.v(this, "onAnswer"); 176 // TODO(santoscordon): Tons of hairy logic is missing here around multiple active calls on 177 // CDMA devices. See {@link CallManager.acceptCall}. 178 179 if (isValidRingingCall() && getPhone() != null) { 180 try { 181 getPhone().acceptCall(); 182 } catch (CallStateException e) { 183 Log.e(this, e, "Failed to accept call."); 184 } 185 } 186 } 187 188 @Override 189 protected void onReject() { 190 Log.v(this, "onReject"); 191 if (isValidRingingCall()) { 192 hangup(DisconnectCause.INCOMING_REJECTED); 193 } 194 super.onReject(); 195 } 196 197 @Override 198 protected void onPostDialContinue(boolean proceed) { 199 Log.v(this, "onPostDialContinue, proceed: " + proceed); 200 if (mOriginalConnection != null) { 201 if (proceed) { 202 mOriginalConnection.proceedAfterWaitChar(); 203 } else { 204 mOriginalConnection.cancelPostDial(); 205 } 206 } 207 } 208 209 @Override 210 protected void onSwapWithBackgroundCall() { 211 Log.v(this, "onSwapWithBackgroundCall"); 212 } 213 214 @Override 215 protected void onChildrenChanged(List<Connection> children) { 216 Log.v(this, "onChildrenChanged, children: " + children); 217 } 218 219 @Override 220 protected void onPhoneAccountClicked() { 221 Log.v(this, "onPhoneAccountClicked"); 222 } 223 224 protected abstract int buildCallCapabilities(); 225 226 final void updateCallCapabilities() { 227 int newCallCapabilities = buildCallCapabilities(); 228 if (getCallCapabilities() != newCallCapabilities) { 229 setCallCapabilities(newCallCapabilities); 230 } 231 } 232 233 void onAddedToCallService() { 234 updateCallCapabilities(); 235 if (mOriginalConnection != null) { 236 setCallerDisplayName( 237 mOriginalConnection.getCnapName(), 238 mOriginalConnection.getCnapNamePresentation()); 239 } 240 } 241 242 void onRemovedFromCallService() { 243 } 244 245 private void hangup(int disconnectCause) { 246 if (mOriginalConnection != null) { 247 try { 248 Call call = mOriginalConnection.getCall(); 249 if (call != null && !call.isMultiparty()) { 250 call.hangup(); 251 } else { 252 mOriginalConnection.hangup(); 253 } 254 // Set state deliberately since we are going to close() and will no longer be 255 // listening to state updates from mOriginalConnection 256 setDisconnected(disconnectCause, null); 257 } catch (CallStateException e) { 258 Log.e(this, e, "Call to Connection.hangup failed with exception"); 259 } 260 } 261 close(); 262 } 263 264 com.android.internal.telephony.Connection getOriginalConnection() { 265 return mOriginalConnection; 266 } 267 268 protected Call getCall() { 269 if (mOriginalConnection != null) { 270 return mOriginalConnection.getCall(); 271 } 272 return null; 273 } 274 275 Phone getPhone() { 276 Call call = getCall(); 277 if (call != null) { 278 return call.getPhone(); 279 } 280 return null; 281 } 282 283 private com.android.internal.telephony.Connection getForegroundConnection() { 284 if (getPhone() != null) { 285 return getPhone().getForegroundCall().getEarliestConnection(); 286 } 287 return null; 288 } 289 290 /** 291 * Checks to see the original connection corresponds to an active incoming call. Returns false 292 * if there is no such actual call, or if the associated call is not incoming (See 293 * {@link Call.State#isRinging}). 294 */ 295 private boolean isValidRingingCall() { 296 if (getPhone() == null) { 297 Log.v(this, "isValidRingingCall, phone is null"); 298 return false; 299 } 300 301 Call ringingCall = getPhone().getRingingCall(); 302 if (!ringingCall.getState().isRinging()) { 303 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 304 return false; 305 } 306 307 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 308 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 309 return false; 310 } 311 312 Log.v(this, "isValidRingingCall, returning true"); 313 return true; 314 } 315 316 private void updateState() { 317 if (mOriginalConnection == null) { 318 return; 319 } 320 321 Call.State newState = mOriginalConnection.getState(); 322 Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this); 323 if (mOriginalConnectionState != newState) { 324 mOriginalConnectionState = newState; 325 switch (newState) { 326 case IDLE: 327 break; 328 case ACTIVE: 329 setActive(); 330 break; 331 case HOLDING: 332 setOnHold(); 333 break; 334 case DIALING: 335 case ALERTING: 336 setDialing(); 337 break; 338 case INCOMING: 339 case WAITING: 340 setRinging(); 341 break; 342 case DISCONNECTED: 343 setDisconnected(mOriginalConnection.getDisconnectCause(), null); 344 break; 345 case DISCONNECTING: 346 break; 347 } 348 updateCallCapabilities(); 349 } 350 } 351 352 private void close() { 353 Log.v(this, "close"); 354 if (getPhone() != null) { 355 getPhone().unregisterForPreciseCallStateChanged(mHandler); 356 getPhone().unregisterForRingbackTone(mHandler); 357 } 358 mOriginalConnection = null; 359 setDestroyed(); 360 } 361} 362