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