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