TelephonyConnection.java revision 3056b9bea92f47a4dac96c9d90e86fc9541c7acf
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.PhoneAccount; 26import android.telecomm.PhoneCapabilities; 27import android.telephony.DisconnectCause; 28 29import com.android.internal.telephony.Call; 30import com.android.internal.telephony.CallStateException; 31import com.android.internal.telephony.Connection.PostDialListener; 32import com.android.internal.telephony.Phone; 33import com.android.internal.telephony.imsphone.ImsPhoneConnection; 34 35import java.lang.Override; 36import java.util.Objects; 37 38/** 39 * Base class for CDMA and GSM connections. 40 */ 41abstract class TelephonyConnection extends Connection { 42 private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1; 43 private static final int MSG_RINGBACK_TONE = 2; 44 private static final int MSG_HANDOVER_STATE_CHANGED = 3; 45 46 private final Handler mHandler = new Handler() { 47 @Override 48 public void handleMessage(Message msg) { 49 switch (msg.what) { 50 case MSG_PRECISE_CALL_STATE_CHANGED: 51 Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED"); 52 updateState(); 53 break; 54 case MSG_HANDOVER_STATE_CHANGED: 55 Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED"); 56 AsyncResult ar = (AsyncResult) msg.obj; 57 com.android.internal.telephony.Connection connection = 58 (com.android.internal.telephony.Connection) ar.result; 59 setOriginalConnection(connection); 60 break; 61 case MSG_RINGBACK_TONE: 62 Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE"); 63 // TODO: This code assumes that there is only one connection in the foreground 64 // call, in other words, it punts on network-mediated conference calling. 65 if (getOriginalConnection() != getForegroundConnection()) { 66 Log.v(TelephonyConnection.this, "handleMessage, original connection is " + 67 "not foreground connection, skipping"); 68 return; 69 } 70 setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result); 71 break; 72 } 73 } 74 }; 75 76 private final PostDialListener mPostDialListener = new PostDialListener() { 77 @Override 78 public void onPostDialWait() { 79 Log.v(TelephonyConnection.this, "onPostDialWait"); 80 if (mOriginalConnection != null) { 81 setPostDialWait(mOriginalConnection.getRemainingPostDialString()); 82 } 83 } 84 }; 85 86 /** 87 * Listener for listening to events in the {@link com.android.internal.telephony.Connection}. 88 */ 89 private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener = 90 new com.android.internal.telephony.Connection.ListenerBase() { 91 @Override 92 public void onVideoStateChanged(int videoState) { 93 setVideoState(videoState); 94 } 95 96 /** 97 * The {@link com.android.internal.telephony.Connection} has reported a change in local 98 * video capability. 99 * 100 * @param capable True if capable. 101 */ 102 @Override 103 public void onLocalVideoCapabilityChanged(boolean capable) { 104 setLocalVideoCapable(capable); 105 } 106 107 /** 108 * The {@link com.android.internal.telephony.Connection} has reported a change in remote 109 * video capability. 110 * 111 * @param capable True if capable. 112 */ 113 @Override 114 public void onRemoteVideoCapabilityChanged(boolean capable) { 115 setRemoteVideoCapable(capable); 116 } 117 118 /** 119 * The {@link com.android.internal.telephony.Connection} has reported a change in the 120 * video call provider. 121 * 122 * @param videoProvider The video call provider. 123 */ 124 @Override 125 public void onVideoProviderChanged(VideoProvider videoProvider) { 126 setVideoProvider(videoProvider); 127 } 128 129 /** 130 * Used by the {@link com.android.internal.telephony.Connection} to report a change in the 131 * audio quality for the current call. 132 * 133 * @param audioQuality The audio quality. 134 */ 135 @Override 136 public void onAudioQualityChanged(int audioQuality) { 137 setAudioQuality(audioQuality); 138 } 139 }; 140 141 private com.android.internal.telephony.Connection mOriginalConnection; 142 private Call.State mOriginalConnectionState = Call.State.IDLE; 143 144 /** 145 * Determines if the {@link TelephonyConnection} has local video capabilities. 146 * This is used when {@link TelephonyConnection#updateCallCapabilities()}} is called, 147 * ensuring the appropriate {@link PhoneCapabilities} are set. Since {@link PhoneCapabilities} 148 * can be rebuilt at any time it is necessary to track the video capabilities between rebuild. 149 * The {@link PhoneCapabilities} (including video capabilities) are communicated to the telecomm 150 * layer. 151 */ 152 private boolean mLocalVideoCapable; 153 154 /** 155 * Determines if the {@link TelephonyConnection} has remote video capabilities. 156 * This is used when {@link TelephonyConnection#updateCallCapabilities()}} is called, 157 * ensuring the appropriate {@link PhoneCapabilities} are set. Since {@link PhoneCapabilities} 158 * can be rebuilt at any time it is necessary to track the video capabilities between rebuild. 159 * The {@link PhoneCapabilities} (including video capabilities) are communicated to the telecomm 160 * layer. 161 */ 162 private boolean mRemoteVideoCapable; 163 164 /** 165 * Determines the current audio quality for the {@link TelephonyConnection}. 166 * This is used when {@link TelephonyConnection#updateCallCapabilities}} is called to indicate 167 * whether a call has the {@link android.telecomm.CallCapabilities#VoLTE} capability. 168 */ 169 private int mAudioQuality; 170 171 protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) { 172 if (originalConnection != null) { 173 setOriginalConnection(originalConnection); 174 } 175 } 176 177 @Override 178 public void onAudioStateChanged(AudioState audioState) { 179 // TODO: update TTY mode. 180 if (getPhone() != null) { 181 getPhone().setEchoSuppressionEnabled(); 182 } 183 } 184 185 @Override 186 public void onStateChanged(int state) { 187 Log.v(this, "onStateChanged, state: " + Connection.stateToString(state)); 188 } 189 190 @Override 191 public void onDisconnect() { 192 Log.v(this, "onDisconnect"); 193 hangup(DisconnectCause.LOCAL); 194 } 195 196 @Override 197 public void onSeparate() { 198 Log.v(this, "onSeparate"); 199 if (mOriginalConnection != null) { 200 try { 201 mOriginalConnection.separate(); 202 } catch (CallStateException e) { 203 Log.e(this, e, "Call to Connection.separate failed with exception"); 204 } 205 } 206 } 207 208 @Override 209 public void onAbort() { 210 Log.v(this, "onAbort"); 211 hangup(DisconnectCause.LOCAL); 212 } 213 214 @Override 215 public void onHold() { 216 performHold(); 217 } 218 219 @Override 220 public void onUnhold() { 221 performUnhold(); 222 } 223 224 @Override 225 public void onAnswer(int videoState) { 226 Log.v(this, "onAnswer"); 227 if (isValidRingingCall() && getPhone() != null) { 228 try { 229 getPhone().acceptCall(videoState); 230 } catch (CallStateException e) { 231 Log.e(this, e, "Failed to accept call."); 232 } 233 } 234 } 235 236 @Override 237 public void onReject() { 238 Log.v(this, "onReject"); 239 if (isValidRingingCall()) { 240 hangup(DisconnectCause.INCOMING_REJECTED); 241 } 242 super.onReject(); 243 } 244 245 @Override 246 public void onPostDialContinue(boolean proceed) { 247 Log.v(this, "onPostDialContinue, proceed: " + proceed); 248 if (mOriginalConnection != null) { 249 if (proceed) { 250 mOriginalConnection.proceedAfterWaitChar(); 251 } else { 252 mOriginalConnection.cancelPostDial(); 253 } 254 } 255 } 256 257 public void performHold() { 258 Log.v(this, "performHold"); 259 // TODO: Can dialing calls be put on hold as well since they take up the 260 // foreground call slot? 261 if (Call.State.ACTIVE == mOriginalConnectionState) { 262 Log.v(this, "Holding active call"); 263 try { 264 Phone phone = mOriginalConnection.getCall().getPhone(); 265 Call ringingCall = phone.getRingingCall(); 266 267 // Although the method says switchHoldingAndActive, it eventually calls a RIL method 268 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put 269 // a call on hold while a call-waiting call exists, it'll end up accepting the 270 // call-waiting call, which is bad if that was not the user's intention. We are 271 // cheating here and simply skipping it because we know any attempt to hold a call 272 // while a call-waiting call is happening is likely a request from Telecomm prior to 273 // accepting the call-waiting call. 274 // TODO: Investigate a better solution. It would be great here if we 275 // could "fake" hold by silencing the audio and microphone streams for this call 276 // instead of actually putting it on hold. 277 if (ringingCall.getState() != Call.State.WAITING) { 278 phone.switchHoldingAndActive(); 279 } 280 281 // TODO: Cdma calls are slightly different. 282 } catch (CallStateException e) { 283 Log.e(this, e, "Exception occurred while trying to put call on hold."); 284 } 285 } else { 286 Log.w(this, "Cannot put a call that is not currently active on hold."); 287 } 288 } 289 290 public void performUnhold() { 291 Log.v(this, "performUnhold"); 292 if (Call.State.HOLDING == mOriginalConnectionState) { 293 try { 294 // Here's the deal--Telephony hold/unhold is weird because whenever there exists 295 // more than one call, one of them must always be active. In other words, if you 296 // have an active call and holding call, and you put the active call on hold, it 297 // will automatically activate the holding call. This is weird with how Telecomm 298 // sends its commands. When a user opts to "unhold" a background call, telecomm 299 // issues hold commands to all active calls, and then the unhold command to the 300 // background call. This means that we get two commands...each of which reduces to 301 // switchHoldingAndActive(). The result is that they simply cancel each other out. 302 // To fix this so that it works well with telecomm we add a minor hack. If we 303 // have one telephony call, everything works as normally expected. But if we have 304 // two or more calls, we will ignore all requests to "unhold" knowing that the hold 305 // requests already do what we want. If you've read up to this point, I'm very sorry 306 // that we are doing this. I didn't think of a better solution that wouldn't also 307 // make the Telecomm APIs very ugly. 308 309 if (!hasMultipleTopLevelCalls()) { 310 mOriginalConnection.getCall().getPhone().switchHoldingAndActive(); 311 } else { 312 Log.i(this, "Skipping unhold command for %s", this); 313 } 314 } catch (CallStateException e) { 315 Log.e(this, e, "Exception occurred while trying to release call from hold."); 316 } 317 } else { 318 Log.w(this, "Cannot release a call that is not already on hold from hold."); 319 } 320 } 321 322 public void performConference(TelephonyConnection otherConnection) {} 323 324 protected abstract int buildCallCapabilities(); 325 326 protected final void updateCallCapabilities() { 327 int newCallCapabilities = buildCallCapabilities(); 328 newCallCapabilities = applyVideoCapabilities(newCallCapabilities); 329 newCallCapabilities = applyAudioQualityCapabilities(newCallCapabilities); 330 newCallCapabilities = applyConferenceTerminationCapabilities(newCallCapabilities); 331 332 if (getCallCapabilities() != newCallCapabilities) { 333 setCallCapabilities(newCallCapabilities); 334 } 335 } 336 337 protected final void updateAddress() { 338 updateCallCapabilities(); 339 if (mOriginalConnection != null) { 340 Uri address = getAddressFromNumber(mOriginalConnection.getAddress()); 341 int presentation = mOriginalConnection.getNumberPresentation(); 342 if (!Objects.equals(address, getAddress()) || 343 presentation != getAddressPresentation()) { 344 Log.v(this, "updateAddress, address changed"); 345 setAddress(address, presentation); 346 } 347 348 String name = mOriginalConnection.getCnapName(); 349 int namePresentation = mOriginalConnection.getCnapNamePresentation(); 350 if (!Objects.equals(name, getCallerDisplayName()) || 351 namePresentation != getCallerDisplayNamePresentation()) { 352 Log.v(this, "updateAddress, caller display name changed"); 353 setCallerDisplayName(name, namePresentation); 354 } 355 } 356 } 357 358 void onRemovedFromCallService() { 359 // Subclass can override this to do cleanup. 360 } 361 362 void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) { 363 Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); 364 if (mOriginalConnection != null) { 365 getPhone().unregisterForPreciseCallStateChanged(mHandler); 366 getPhone().unregisterForRingbackTone(mHandler); 367 getPhone().unregisterForHandoverStateChanged(mHandler); 368 } 369 mOriginalConnection = originalConnection; 370 getPhone().registerForPreciseCallStateChanged( 371 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null); 372 getPhone().registerForHandoverStateChanged( 373 mHandler, MSG_HANDOVER_STATE_CHANGED, null); 374 getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null); 375 mOriginalConnection.addPostDialListener(mPostDialListener); 376 mOriginalConnection.addListener(mOriginalConnectionListener); 377 378 // Set video state and capabilities 379 setVideoState(mOriginalConnection.getVideoState()); 380 setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable()); 381 setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable()); 382 setVideoProvider(mOriginalConnection.getVideoProvider()); 383 setAudioQuality(mOriginalConnection.getAudioQuality()); 384 385 updateAddress(); 386 } 387 388 protected void hangup(int disconnectCause) { 389 if (mOriginalConnection != null) { 390 try { 391 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to 392 // connection.hangup(). Without this change, the party originating the call will not 393 // get sent to voicemail if the user opts to reject the call. 394 if (isValidRingingCall()) { 395 Call call = getCall(); 396 if (call != null) { 397 call.hangup(); 398 } else { 399 Log.w(this, "Attempting to hangup a connection without backing call."); 400 } 401 } else { 402 // We still prefer to call connection.hangup() for non-ringing calls in order 403 // to support hanging-up specific calls within a conference call. If we invoked 404 // call.hangup() while in a conference, we would end up hanging up the entire 405 // conference call instead of the specific connection. 406 mOriginalConnection.hangup(); 407 } 408 } catch (CallStateException e) { 409 Log.e(this, e, "Call to Connection.hangup failed with exception"); 410 } 411 } 412 413 // Set state deliberately since we are going to close() and will no longer be 414 // listening to state updates from mOriginalConnection 415 setDisconnected(disconnectCause, null); 416 close(); 417 } 418 419 com.android.internal.telephony.Connection getOriginalConnection() { 420 return mOriginalConnection; 421 } 422 423 protected Call getCall() { 424 if (mOriginalConnection != null) { 425 return mOriginalConnection.getCall(); 426 } 427 return null; 428 } 429 430 Phone getPhone() { 431 Call call = getCall(); 432 if (call != null) { 433 return call.getPhone(); 434 } 435 return null; 436 } 437 438 private boolean hasMultipleTopLevelCalls() { 439 int numCalls = 0; 440 Phone phone = getPhone(); 441 if (phone != null) { 442 if (!phone.getRingingCall().isIdle()) { 443 numCalls++; 444 } 445 if (!phone.getForegroundCall().isIdle()) { 446 numCalls++; 447 } 448 if (!phone.getBackgroundCall().isIdle()) { 449 numCalls++; 450 } 451 } 452 return numCalls > 1; 453 } 454 455 private com.android.internal.telephony.Connection getForegroundConnection() { 456 if (getPhone() != null) { 457 return getPhone().getForegroundCall().getEarliestConnection(); 458 } 459 return null; 460 } 461 462 /** 463 * Checks to see the original connection corresponds to an active incoming call. Returns false 464 * if there is no such actual call, or if the associated call is not incoming (See 465 * {@link Call.State#isRinging}). 466 */ 467 private boolean isValidRingingCall() { 468 if (getPhone() == null) { 469 Log.v(this, "isValidRingingCall, phone is null"); 470 return false; 471 } 472 473 Call ringingCall = getPhone().getRingingCall(); 474 if (!ringingCall.getState().isRinging()) { 475 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 476 return false; 477 } 478 479 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 480 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 481 return false; 482 } 483 484 Log.v(this, "isValidRingingCall, returning true"); 485 return true; 486 } 487 488 protected void updateState() { 489 if (mOriginalConnection == null) { 490 return; 491 } 492 493 Call.State newState = mOriginalConnection.getState(); 494 Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this); 495 if (mOriginalConnectionState != newState) { 496 mOriginalConnectionState = newState; 497 switch (newState) { 498 case IDLE: 499 break; 500 case ACTIVE: 501 setActive(); 502 break; 503 case HOLDING: 504 setOnHold(); 505 break; 506 case DIALING: 507 case ALERTING: 508 setDialing(); 509 break; 510 case INCOMING: 511 case WAITING: 512 setRinging(); 513 break; 514 case DISCONNECTED: 515 setDisconnected(mOriginalConnection.getDisconnectCause(), null); 516 close(); 517 break; 518 case DISCONNECTING: 519 break; 520 } 521 } 522 updateCallCapabilities(); 523 updateAddress(); 524 } 525 526 private void close() { 527 Log.v(this, "close"); 528 if (getPhone() != null) { 529 getPhone().unregisterForPreciseCallStateChanged(mHandler); 530 getPhone().unregisterForRingbackTone(mHandler); 531 getPhone().unregisterForHandoverStateChanged(mHandler); 532 } 533 mOriginalConnection = null; 534 destroy(); 535 } 536 537 /** 538 * Applies the video capability states to the CallCapabilities bit-mask. 539 * 540 * @param capabilities The CallCapabilities bit-mask. 541 * @return The capabilities with video capabilities applied. 542 */ 543 private int applyVideoCapabilities(int capabilities) { 544 int currentCapabilities = capabilities; 545 if (mRemoteVideoCapable) { 546 currentCapabilities = applyCapability(currentCapabilities, 547 PhoneCapabilities.SUPPORTS_VT_REMOTE); 548 } else { 549 currentCapabilities = removeCapability(currentCapabilities, 550 PhoneCapabilities.SUPPORTS_VT_REMOTE); 551 } 552 553 if (mLocalVideoCapable) { 554 currentCapabilities = applyCapability(currentCapabilities, 555 PhoneCapabilities.SUPPORTS_VT_LOCAL); 556 } else { 557 currentCapabilities = removeCapability(currentCapabilities, 558 PhoneCapabilities.SUPPORTS_VT_LOCAL); 559 } 560 return currentCapabilities; 561 } 562 563 /** 564 * Applies the audio capabilities to the {@code CallCapabilities} bit-mask. A call with high 565 * definition audio is considered to have the {@code VoLTE} call capability as VoLTE uses high 566 * definition audio. 567 * 568 * @param callCapabilities The {@code CallCapabilities} bit-mask. 569 * @return The capabilities with the audio capabilities applied. 570 */ 571 private int applyAudioQualityCapabilities(int callCapabilities) { 572 int currentCapabilities = callCapabilities; 573 574 if (mAudioQuality == 575 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION) { 576 currentCapabilities = applyCapability(currentCapabilities, PhoneCapabilities.VoLTE); 577 } else { 578 currentCapabilities = removeCapability(currentCapabilities, PhoneCapabilities.VoLTE); 579 } 580 581 return currentCapabilities; 582 } 583 584 /** 585 * Applies capabilities specific to conferences termination to the 586 * {@code CallCapabilities} bit-mask. 587 * 588 * @param callCapabilities The {@code CallCapabilities} bit-mask. 589 * @return The capabilities with the IMS conference capabilities applied. 590 */ 591 private int applyConferenceTerminationCapabilities(int callCapabilities) { 592 int currentCapabilities = callCapabilities; 593 594 // An IMS call cannot be individually disconnected or separated from its parent conference 595 boolean isImsCall = getOriginalConnection() instanceof ImsPhoneConnection; 596 if (!isImsCall) { 597 currentCapabilities |= 598 PhoneCapabilities.DISCONNECT_FROM_CONFERENCE 599 | PhoneCapabilities.SEPARATE_FROM_CONFERENCE; 600 } 601 602 return currentCapabilities; 603 } 604 605 /** 606 * Returns the local video capability state for the connection. 607 * 608 * @return {@code True} if the connection has local video capabilities. 609 */ 610 public boolean isLocalVideoCapable() { 611 return mLocalVideoCapable; 612 } 613 614 /** 615 * Returns the remote video capability state for the connection. 616 * 617 * @return {@code True} if the connection has remote video capabilities. 618 */ 619 public boolean isRemoteVideoCapable() { 620 return mRemoteVideoCapable; 621 } 622 623 /** 624 * Sets whether video capability is present locally. Used during rebuild of the 625 * {@link PhoneCapabilities} to set the video call capabilities. 626 * 627 * @param capable {@code True} if video capable. 628 */ 629 public void setLocalVideoCapable(boolean capable) { 630 mLocalVideoCapable = capable; 631 updateCallCapabilities(); 632 } 633 634 /** 635 * Sets whether video capability is present remotely. Used during rebuild of the 636 * {@link PhoneCapabilities} to set the video call capabilities. 637 * 638 * @param capable {@code True} if video capable. 639 */ 640 public void setRemoteVideoCapable(boolean capable) { 641 mRemoteVideoCapable = capable; 642 updateCallCapabilities(); 643 } 644 645 /** 646 * Sets the current call audio quality. Used during rebuild of the 647 * {@link PhoneCapabilities} to set or unset the {@link PhoneCapabilities#VoLTE} capability. 648 * 649 * @param audioQuality The audio quality. 650 */ 651 public void setAudioQuality(int audioQuality) { 652 mAudioQuality = audioQuality; 653 updateCallCapabilities(); 654 } 655 656 /** 657 * Obtains the current call audio quality. 658 */ 659 public int getAudioQuality() { 660 return mAudioQuality; 661 } 662 663 private static Uri getAddressFromNumber(String number) { 664 // Address can be null for blocked calls. 665 if (number == null) { 666 number = ""; 667 } 668 return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 669 } 670 671 /** 672 * Applies a capability to a capabilities bit-mask. 673 * 674 * @param capabilities The capabilities bit-mask. 675 * @param capability The capability to apply. 676 * @return The capabilities bit-mask with the capability applied. 677 */ 678 private int applyCapability(int capabilities, int capability) { 679 int newCapabilities = capabilities | capability; 680 return newCapabilities; 681 } 682 683 /** 684 * Removes a capability from a capabilities bit-mask. 685 * 686 * @param capabilities The capabilities bit-mask. 687 * @param capability The capability to remove. 688 * @return The capabilities bit-mask with the capability removed. 689 */ 690 private int removeCapability(int capabilities, int capability) { 691 int newCapabilities = capabilities & ~capability; 692 return newCapabilities; 693 } 694} 695