TelephonyConnection.java revision 5fc83bca2dd6e0c10ab1100b31cc337a370495db
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.net.Uri; 20import android.os.AsyncResult; 21import android.content.ComponentName; 22import android.os.Bundle; 23import android.os.Handler; 24import android.os.Message; 25import android.telecomm.CallAudioState; 26import android.telecomm.CallCapabilities; 27import android.telecomm.ConnectionService; 28import android.telecomm.StatusHints; 29import android.telecomm.TelecommConstants; 30import android.telephony.DisconnectCause; 31 32import com.android.internal.telephony.Call; 33import com.android.internal.telephony.CallStateException; 34import com.android.internal.telephony.Connection.PostDialListener; 35import com.android.internal.telephony.Phone; 36import com.android.phone.R; 37 38import android.telecomm.Connection; 39import android.telephony.PhoneNumberUtils; 40import android.telephony.TelephonyManager; 41 42import java.util.List; 43import java.util.Objects; 44 45/** 46 * Base class for CDMA and GSM connections. 47 */ 48abstract class TelephonyConnection extends Connection { 49 private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1; 50 private static final int MSG_RINGBACK_TONE = 2; 51 52 private final Handler mHandler = new Handler() { 53 @Override 54 public void handleMessage(Message msg) { 55 switch (msg.what) { 56 case MSG_PRECISE_CALL_STATE_CHANGED: 57 Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED"); 58 updateState(false); 59 break; 60 case MSG_RINGBACK_TONE: 61 Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE"); 62 // TODO: This code assumes that there is only one connection in the foreground 63 // call, in other words, it punts on network-mediated conference calling. 64 if (getOriginalConnection() != getForegroundConnection()) { 65 Log.v(TelephonyConnection.this, "handleMessage, original connection is " + 66 "not foreground connection, skipping"); 67 return; 68 } 69 setRequestingRingback((Boolean) ((AsyncResult) msg.obj).result); 70 break; 71 } 72 } 73 }; 74 75 private final PostDialListener mPostDialListener = new PostDialListener() { 76 @Override 77 public void onPostDialWait() { 78 Log.v(TelephonyConnection.this, "onPostDialWait"); 79 if (mOriginalConnection != null) { 80 setPostDialWait(mOriginalConnection.getRemainingPostDialString()); 81 } 82 } 83 }; 84 85 /** 86 * Listener for listening to events in the {@link com.android.internal.telephony.Connection}. 87 */ 88 private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener = 89 new com.android.internal.telephony.Connection.ListenerBase() { 90 @Override 91 public void onVideoStateChanged(int videoState) { 92 setVideoState(videoState); 93 } 94 95 /** 96 * The {@link com.android.internal.telephony.Connection} has reported a change in local 97 * video capability. 98 * 99 * @param capable True if capable. 100 */ 101 @Override 102 public void onLocalVideoCapabilityChanged(boolean capable) { 103 setLocalVideoCapable(capable); 104 } 105 106 /** 107 * The {@link com.android.internal.telephony.Connection} has reported a change in remote 108 * video capability. 109 * 110 * @param capable True if capable. 111 */ 112 @Override 113 public void onRemoteVideoCapabilityChanged(boolean capable) { 114 setRemoteVideoCapable(capable); 115 } 116 }; 117 118 private com.android.internal.telephony.Connection mOriginalConnection; 119 private Call.State mOriginalConnectionState = Call.State.IDLE; 120 121 /** 122 * Determines if the {@link TelephonyConnection} has local video capabilities. 123 * This is used when {@link TelephonyConnection#updateCallCapabilities(boolean)}} is called, 124 * ensuring the appropriate {@link CallCapabilities} are set. Since {@link CallCapabilities} 125 * can be rebuilt at any time it is necessary to track the video capabilities between rebuild. 126 * The {@link CallCapabilities} (including video capabilities) are communicated to the telecomm 127 * layer. 128 */ 129 private boolean mLocalVideoCapable; 130 131 /** 132 * Determines if the {@link TelephonyConnection} has remote video capabilities. 133 * This is used when {@link TelephonyConnection#updateCallCapabilities(boolean)}} is called, 134 * ensuring the appropriate {@link CallCapabilities} are set. Since {@link CallCapabilities} 135 * can be rebuilt at any time it is necessary to track the video capabilities between rebuild. 136 * The {@link CallCapabilities} (including video capabilities) are communicated to the telecomm 137 * layer. 138 */ 139 private boolean mRemoteVideoCapable; 140 141 protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) { 142 Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); 143 mOriginalConnection = originalConnection; 144 getPhone().registerForPreciseCallStateChanged( 145 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null); 146 getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null); 147 mOriginalConnection.addPostDialListener(mPostDialListener); 148 mOriginalConnection.addListener(mOriginalConnectionListener); 149 150 // Set video state and capabilities 151 setVideoState(mOriginalConnection.getVideoState()); 152 setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable()); 153 setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable()); 154 } 155 156 @Override 157 protected void onSetAudioState(CallAudioState audioState) { 158 // TODO: update TTY mode. 159 if (getPhone() != null) { 160 getPhone().setEchoSuppressionEnabled(); 161 } 162 } 163 164 @Override 165 protected void onSetState(int state) { 166 Log.v(this, "onSetState, state: " + Connection.stateToString(state)); 167 } 168 169 @Override 170 protected void onDisconnect() { 171 Log.v(this, "onDisconnect"); 172 hangup(DisconnectCause.LOCAL); 173 } 174 175 @Override 176 protected void onSeparate() { 177 Log.v(this, "onSeparate"); 178 if (mOriginalConnection != null) { 179 try { 180 mOriginalConnection.separate(); 181 } catch (CallStateException e) { 182 Log.e(this, e, "Call to Connection.separate failed with exception"); 183 } 184 } 185 } 186 187 @Override 188 protected void onAbort() { 189 Log.v(this, "onAbort"); 190 hangup(DisconnectCause.LOCAL); 191 } 192 193 @Override 194 protected void onHold() { 195 Log.v(this, "onHold"); 196 // TODO(santoscordon): Can dialing calls be put on hold as well since they take up the 197 // foreground call slot? 198 if (Call.State.ACTIVE == mOriginalConnectionState) { 199 Log.v(this, "Holding active call"); 200 try { 201 Phone phone = mOriginalConnection.getCall().getPhone(); 202 Call ringingCall = phone.getRingingCall(); 203 204 // Although the method says switchHoldingAndActive, it eventually calls a RIL method 205 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put 206 // a call on hold while a call-waiting call exists, it'll end up accepting the 207 // call-waiting call, which is bad if that was not the user's intention. We are 208 // cheating here and simply skipping it because we know any attempt to hold a call 209 // while a call-waiting call is happening is likely a request from Telecomm prior to 210 // accepting the call-waiting call. 211 // TODO(santoscordon): Investigate a better solution. It would be great here if we 212 // could "fake" hold by silencing the audio and microphone streams for this call 213 // instead of actually putting it on hold. 214 if (ringingCall.getState() != Call.State.WAITING) { 215 phone.switchHoldingAndActive(); 216 } 217 218 // TODO(santoscordon): Cdma calls are slightly different. 219 } catch (CallStateException e) { 220 Log.e(this, e, "Exception occurred while trying to put call on hold."); 221 } 222 } else { 223 Log.w(this, "Cannot put a call that is not currently active on hold."); 224 } 225 } 226 227 @Override 228 protected void onUnhold() { 229 Log.v(this, "onUnhold"); 230 if (Call.State.HOLDING == mOriginalConnectionState) { 231 try { 232 // TODO: This doesn't handle multiple calls across connection services yet 233 mOriginalConnection.getCall().getPhone().switchHoldingAndActive(); 234 } catch (CallStateException e) { 235 Log.e(this, e, "Exception occurred while trying to release call from hold."); 236 } 237 } else { 238 Log.w(this, "Cannot release a call that is not already on hold from hold."); 239 } 240 } 241 242 @Override 243 protected void onAnswer(int videoState) { 244 Log.v(this, "onAnswer"); 245 // TODO(santoscordon): Tons of hairy logic is missing here around multiple active calls on 246 // CDMA devices. See {@link CallManager.acceptCall}. 247 248 if (isValidRingingCall() && getPhone() != null) { 249 try { 250 getPhone().acceptCall(videoState); 251 } catch (CallStateException e) { 252 Log.e(this, e, "Failed to accept call."); 253 } 254 } 255 } 256 257 @Override 258 protected void onReject() { 259 Log.v(this, "onReject"); 260 if (isValidRingingCall()) { 261 hangup(DisconnectCause.INCOMING_REJECTED); 262 } 263 super.onReject(); 264 } 265 266 @Override 267 protected void onPostDialContinue(boolean proceed) { 268 Log.v(this, "onPostDialContinue, proceed: " + proceed); 269 if (mOriginalConnection != null) { 270 if (proceed) { 271 mOriginalConnection.proceedAfterWaitChar(); 272 } else { 273 mOriginalConnection.cancelPostDial(); 274 } 275 } 276 } 277 278 @Override 279 protected void onSwapWithBackgroundCall() { 280 Log.v(this, "onSwapWithBackgroundCall"); 281 } 282 283 @Override 284 protected void onChildrenChanged(List<Connection> children) { 285 Log.v(this, "onChildrenChanged, children: " + children); 286 } 287 288 @Override 289 protected void onPhoneAccountClicked() { 290 Log.v(this, "onPhoneAccountClicked"); 291 } 292 293 protected abstract int buildCallCapabilities(); 294 295 protected final void updateCallCapabilities(boolean force) { 296 int newCallCapabilities = buildCallCapabilities(); 297 newCallCapabilities = applyVideoCapabilities(newCallCapabilities); 298 299 if (force || getCallCapabilities() != newCallCapabilities) { 300 setCallCapabilities(newCallCapabilities); 301 } 302 } 303 304 protected final void updateHandle(boolean force) { 305 updateCallCapabilities(force); 306 if (mOriginalConnection != null) { 307 Uri handle = TelephonyConnectionService.getHandleFromAddress( 308 mOriginalConnection.getAddress()); 309 int presentation = mOriginalConnection.getNumberPresentation(); 310 if (force || !Objects.equals(handle, getHandle()) || 311 presentation != getHandlePresentation()) { 312 Log.v(this, "updateHandle, handle changed"); 313 setHandle(handle, presentation); 314 } 315 316 String name = mOriginalConnection.getCnapName(); 317 int namePresentation = mOriginalConnection.getCnapNamePresentation(); 318 if (force || !Objects.equals(name, getCallerDisplayName()) || 319 namePresentation != getCallerDisplayNamePresentation()) { 320 Log.v(this, "updateHandle, caller display name changed"); 321 setCallerDisplayName(name, namePresentation); 322 } 323 } 324 } 325 326 void onAddedToCallService() { 327 updateState(false); 328 } 329 330 void onRemovedFromCallService() { 331 // Subclass can override this to do cleanup. 332 } 333 334 private void hangup(int disconnectCause) { 335 if (mOriginalConnection != null) { 336 try { 337 Call call = mOriginalConnection.getCall(); 338 if (call != null && !call.isMultiparty()) { 339 call.hangup(); 340 } else { 341 mOriginalConnection.hangup(); 342 } 343 // Set state deliberately since we are going to close() and will no longer be 344 // listening to state updates from mOriginalConnection 345 setDisconnected(disconnectCause, null); 346 } catch (CallStateException e) { 347 Log.e(this, e, "Call to Connection.hangup failed with exception"); 348 } 349 } 350 close(); 351 } 352 353 com.android.internal.telephony.Connection getOriginalConnection() { 354 return mOriginalConnection; 355 } 356 357 protected Call getCall() { 358 if (mOriginalConnection != null) { 359 return mOriginalConnection.getCall(); 360 } 361 return null; 362 } 363 364 Phone getPhone() { 365 Call call = getCall(); 366 if (call != null) { 367 return call.getPhone(); 368 } 369 return null; 370 } 371 372 private com.android.internal.telephony.Connection getForegroundConnection() { 373 if (getPhone() != null) { 374 return getPhone().getForegroundCall().getEarliestConnection(); 375 } 376 return null; 377 } 378 379 /** 380 * Checks to see the original connection corresponds to an active incoming call. Returns false 381 * if there is no such actual call, or if the associated call is not incoming (See 382 * {@link Call.State#isRinging}). 383 */ 384 private boolean isValidRingingCall() { 385 if (getPhone() == null) { 386 Log.v(this, "isValidRingingCall, phone is null"); 387 return false; 388 } 389 390 Call ringingCall = getPhone().getRingingCall(); 391 if (!ringingCall.getState().isRinging()) { 392 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 393 return false; 394 } 395 396 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 397 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 398 return false; 399 } 400 401 Log.v(this, "isValidRingingCall, returning true"); 402 return true; 403 } 404 405 private void updateState(boolean force) { 406 if (mOriginalConnection == null) { 407 return; 408 } 409 410 Call.State newState = mOriginalConnection.getState(); 411 Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this); 412 if (force || mOriginalConnectionState != newState) { 413 mOriginalConnectionState = newState; 414 switch (newState) { 415 case IDLE: 416 break; 417 case ACTIVE: 418 setActive(); 419 break; 420 case HOLDING: 421 setOnHold(); 422 break; 423 case DIALING: 424 case ALERTING: 425 setDialing(); 426 break; 427 case INCOMING: 428 case WAITING: 429 setRinging(); 430 break; 431 case DISCONNECTED: 432 setDisconnected(mOriginalConnection.getDisconnectCause(), null); 433 close(); 434 break; 435 case DISCONNECTING: 436 break; 437 } 438 } 439 updateCallCapabilities(force); 440 updateHandle(force); 441 } 442 443 private void close() { 444 Log.v(this, "close"); 445 if (getPhone() != null) { 446 getPhone().unregisterForPreciseCallStateChanged(mHandler); 447 getPhone().unregisterForRingbackTone(mHandler); 448 } 449 mOriginalConnection = null; 450 setDestroyed(); 451 } 452 453 /** 454 * Applies the video capability states to the CallCapabilities bit-mask. 455 * 456 * @param capabilities The CallCapabilities bit-mask. 457 * @return The capabilities with video capabilities applied. 458 */ 459 private int applyVideoCapabilities(int capabilities) { 460 int currentCapabilities = capabilities; 461 if (mRemoteVideoCapable) { 462 currentCapabilities |= CallCapabilities.SUPPORTS_VT_REMOTE; 463 } else { 464 currentCapabilities &= ~CallCapabilities.SUPPORTS_VT_REMOTE; 465 } 466 467 if (mLocalVideoCapable) { 468 currentCapabilities |= CallCapabilities.SUPPORTS_VT_LOCAL; 469 } else { 470 currentCapabilities &= ~CallCapabilities.SUPPORTS_VT_LOCAL; 471 } 472 return currentCapabilities; 473 } 474 475 /** 476 * Returns the local video capability state for the connection. 477 * 478 * @return {@code True} if the connection has local video capabilities. 479 */ 480 public boolean isLocalVideoCapable() { 481 return mLocalVideoCapable; 482 } 483 484 /** 485 * Returns the remote video capability state for the connection. 486 * 487 * @return {@code True} if the connection has remote video capabilities. 488 */ 489 public boolean isRemoteVideoCapable() { 490 return mRemoteVideoCapable; 491 } 492 493 /** 494 * Sets whether video capability is present locally. Used during rebuild of the 495 * {@link CallCapabilities} to set the video call capabilities. 496 * 497 * @param capable {@code True} if video capable. 498 */ 499 public void setLocalVideoCapable(boolean capable) { 500 mLocalVideoCapable = capable; 501 updateCallCapabilities(false); 502 } 503 504 /** 505 * Sets whether video capability is present remotely. Used during rebuild of the 506 * {@link CallCapabilities} to set the video call capabilities. 507 * 508 * @param capable {@code True} if video capable. 509 */ 510 public void setRemoteVideoCapable(boolean capable) { 511 mRemoteVideoCapable = capable; 512 updateCallCapabilities(false); 513 } 514} 515