TelephonyConnection.java revision ad6f3dce3b32d69fb2a353753163358f88708405
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.telecom.AudioState; 24import android.telecom.Conference; 25import android.telecom.ConferenceParticipant; 26import android.telecom.Connection; 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.Collections; 38import java.util.List; 39import java.util.Objects; 40import java.util.Set; 41import java.util.concurrent.ConcurrentHashMap; 42 43/** 44 * Base class for CDMA and GSM connections. 45 */ 46abstract class TelephonyConnection extends Connection { 47 private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1; 48 private static final int MSG_RINGBACK_TONE = 2; 49 private static final int MSG_HANDOVER_STATE_CHANGED = 3; 50 private static final int MSG_DISCONNECT = 4; 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(); 59 break; 60 case MSG_HANDOVER_STATE_CHANGED: 61 Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED"); 62 AsyncResult ar = (AsyncResult) msg.obj; 63 com.android.internal.telephony.Connection connection = 64 (com.android.internal.telephony.Connection) ar.result; 65 setOriginalConnection(connection); 66 break; 67 case MSG_RINGBACK_TONE: 68 Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE"); 69 // TODO: This code assumes that there is only one connection in the foreground 70 // call, in other words, it punts on network-mediated conference calling. 71 if (getOriginalConnection() != getForegroundConnection()) { 72 Log.v(TelephonyConnection.this, "handleMessage, original connection is " + 73 "not foreground connection, skipping"); 74 return; 75 } 76 setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result); 77 break; 78 case MSG_DISCONNECT: 79 updateState(); 80 break; 81 } 82 } 83 }; 84 85 /** 86 * A listener/callback mechanism that is specific communication from TelephonyConnections 87 * to TelephonyConnectionService (for now). It is more specific that Connection.Listener 88 * because it is only exposed in Telephony. 89 */ 90 public abstract static class TelephonyConnectionListener { 91 public void onOriginalConnectionConfigured(TelephonyConnection c) {} 92 } 93 94 private final PostDialListener mPostDialListener = new PostDialListener() { 95 @Override 96 public void onPostDialWait() { 97 Log.v(TelephonyConnection.this, "onPostDialWait"); 98 if (mOriginalConnection != null) { 99 setPostDialWait(mOriginalConnection.getRemainingPostDialString()); 100 } 101 } 102 }; 103 104 /** 105 * Listener for listening to events in the {@link com.android.internal.telephony.Connection}. 106 */ 107 private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener = 108 new com.android.internal.telephony.Connection.ListenerBase() { 109 @Override 110 public void onVideoStateChanged(int videoState) { 111 setVideoState(videoState); 112 } 113 114 /** 115 * The {@link com.android.internal.telephony.Connection} has reported a change in local 116 * video capability. 117 * 118 * @param capable True if capable. 119 */ 120 @Override 121 public void onLocalVideoCapabilityChanged(boolean capable) { 122 setLocalVideoCapable(capable); 123 } 124 125 /** 126 * The {@link com.android.internal.telephony.Connection} has reported a change in remote 127 * video capability. 128 * 129 * @param capable True if capable. 130 */ 131 @Override 132 public void onRemoteVideoCapabilityChanged(boolean capable) { 133 setRemoteVideoCapable(capable); 134 } 135 136 /** 137 * The {@link com.android.internal.telephony.Connection} has reported a change in the 138 * video call provider. 139 * 140 * @param videoProvider The video call provider. 141 */ 142 @Override 143 public void onVideoProviderChanged(VideoProvider videoProvider) { 144 setVideoProvider(videoProvider); 145 } 146 147 /** 148 * Used by the {@link com.android.internal.telephony.Connection} to report a change in the 149 * audio quality for the current call. 150 * 151 * @param audioQuality The audio quality. 152 */ 153 @Override 154 public void onAudioQualityChanged(int audioQuality) { 155 setAudioQuality(audioQuality); 156 } 157 158 /** 159 * Handles a change in the state of conference participant(s), as reported by the 160 * {@link com.android.internal.telephony.Connection}. 161 * 162 * @param participants The participant(s) which changed. 163 */ 164 @Override 165 public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) { 166 updateConferenceParticipants(participants); 167 } 168 }; 169 170 private com.android.internal.telephony.Connection mOriginalConnection; 171 private Call.State mOriginalConnectionState = Call.State.IDLE; 172 173 private boolean mWasImsConnection; 174 175 /** 176 * Determines if the {@link TelephonyConnection} has local video capabilities. 177 * This is used when {@link TelephonyConnection#updateCallCapabilities()}} is called, 178 * ensuring the appropriate {@link PhoneCapabilities} are set. Since {@link PhoneCapabilities} 179 * can be rebuilt at any time it is necessary to track the video capabilities between rebuild. 180 * The {@link PhoneCapabilities} (including video capabilities) are communicated to the telecom 181 * layer. 182 */ 183 private boolean mLocalVideoCapable; 184 185 /** 186 * Determines if the {@link TelephonyConnection} has remote video capabilities. 187 * This is used when {@link TelephonyConnection#updateCallCapabilities()}} is called, 188 * ensuring the appropriate {@link PhoneCapabilities} are set. Since {@link PhoneCapabilities} 189 * can be rebuilt at any time it is necessary to track the video capabilities between rebuild. 190 * The {@link PhoneCapabilities} (including video capabilities) are communicated to the telecom 191 * layer. 192 */ 193 private boolean mRemoteVideoCapable; 194 195 /** 196 * Determines the current audio quality for the {@link TelephonyConnection}. 197 * This is used when {@link TelephonyConnection#updateCallCapabilities}} is called to indicate 198 * whether a call has the {@link android.telecom.CallCapabilities#VoLTE} capability. 199 */ 200 private int mAudioQuality; 201 202 /** 203 * Listeners to our TelephonyConnection specific callbacks 204 */ 205 private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap( 206 new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1)); 207 208 protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) { 209 if (originalConnection != null) { 210 setOriginalConnection(originalConnection); 211 } 212 } 213 214 @Override 215 public void onAudioStateChanged(AudioState audioState) { 216 // TODO: update TTY mode. 217 if (getPhone() != null) { 218 getPhone().setEchoSuppressionEnabled(); 219 } 220 } 221 222 @Override 223 public void onStateChanged(int state) { 224 Log.v(this, "onStateChanged, state: " + Connection.stateToString(state)); 225 } 226 227 @Override 228 public void onDisconnect() { 229 Log.v(this, "onDisconnect"); 230 hangup(android.telephony.DisconnectCause.LOCAL); 231 } 232 233 /** 234 * Notifies this Connection of a request to disconnect a participant of the conference managed 235 * by the connection. 236 * 237 * @param endpoint the {@link Uri} of the participant to disconnect. 238 */ 239 @Override 240 public void onDisconnectConferenceParticipant(Uri endpoint) { 241 Log.v(this, "onDisconnectConferenceParticipant %s", endpoint); 242 243 if (mOriginalConnection == null) { 244 return; 245 } 246 247 mOriginalConnection.onDisconnectConferenceParticipant(endpoint); 248 } 249 250 @Override 251 public void onSeparate() { 252 Log.v(this, "onSeparate"); 253 if (mOriginalConnection != null) { 254 try { 255 mOriginalConnection.separate(); 256 } catch (CallStateException e) { 257 Log.e(this, e, "Call to Connection.separate failed with exception"); 258 } 259 } 260 } 261 262 @Override 263 public void onAbort() { 264 Log.v(this, "onAbort"); 265 hangup(android.telephony.DisconnectCause.LOCAL); 266 } 267 268 @Override 269 public void onHold() { 270 performHold(); 271 } 272 273 @Override 274 public void onUnhold() { 275 performUnhold(); 276 } 277 278 @Override 279 public void onAnswer(int videoState) { 280 Log.v(this, "onAnswer"); 281 if (isValidRingingCall() && getPhone() != null) { 282 try { 283 getPhone().acceptCall(videoState); 284 } catch (CallStateException e) { 285 Log.e(this, e, "Failed to accept call."); 286 } 287 } 288 } 289 290 @Override 291 public void onReject() { 292 Log.v(this, "onReject"); 293 if (isValidRingingCall()) { 294 hangup(android.telephony.DisconnectCause.INCOMING_REJECTED); 295 } 296 super.onReject(); 297 } 298 299 @Override 300 public void onPostDialContinue(boolean proceed) { 301 Log.v(this, "onPostDialContinue, proceed: " + proceed); 302 if (mOriginalConnection != null) { 303 if (proceed) { 304 mOriginalConnection.proceedAfterWaitChar(); 305 } else { 306 mOriginalConnection.cancelPostDial(); 307 } 308 } 309 } 310 311 public void performHold() { 312 Log.v(this, "performHold"); 313 // TODO: Can dialing calls be put on hold as well since they take up the 314 // foreground call slot? 315 if (Call.State.ACTIVE == mOriginalConnectionState) { 316 Log.v(this, "Holding active call"); 317 try { 318 Phone phone = mOriginalConnection.getCall().getPhone(); 319 Call ringingCall = phone.getRingingCall(); 320 321 // Although the method says switchHoldingAndActive, it eventually calls a RIL method 322 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put 323 // a call on hold while a call-waiting call exists, it'll end up accepting the 324 // call-waiting call, which is bad if that was not the user's intention. We are 325 // cheating here and simply skipping it because we know any attempt to hold a call 326 // while a call-waiting call is happening is likely a request from Telecom prior to 327 // accepting the call-waiting call. 328 // TODO: Investigate a better solution. It would be great here if we 329 // could "fake" hold by silencing the audio and microphone streams for this call 330 // instead of actually putting it on hold. 331 if (ringingCall.getState() != Call.State.WAITING) { 332 phone.switchHoldingAndActive(); 333 } 334 335 // TODO: Cdma calls are slightly different. 336 } catch (CallStateException e) { 337 Log.e(this, e, "Exception occurred while trying to put call on hold."); 338 } 339 } else { 340 Log.w(this, "Cannot put a call that is not currently active on hold."); 341 } 342 } 343 344 public void performUnhold() { 345 Log.v(this, "performUnhold"); 346 if (Call.State.HOLDING == mOriginalConnectionState) { 347 try { 348 // Here's the deal--Telephony hold/unhold is weird because whenever there exists 349 // more than one call, one of them must always be active. In other words, if you 350 // have an active call and holding call, and you put the active call on hold, it 351 // will automatically activate the holding call. This is weird with how Telecom 352 // sends its commands. When a user opts to "unhold" a background call, telecom 353 // issues hold commands to all active calls, and then the unhold command to the 354 // background call. This means that we get two commands...each of which reduces to 355 // switchHoldingAndActive(). The result is that they simply cancel each other out. 356 // To fix this so that it works well with telecom we add a minor hack. If we 357 // have one telephony call, everything works as normally expected. But if we have 358 // two or more calls, we will ignore all requests to "unhold" knowing that the hold 359 // requests already do what we want. If you've read up to this point, I'm very sorry 360 // that we are doing this. I didn't think of a better solution that wouldn't also 361 // make the Telecom APIs very ugly. 362 363 if (!hasMultipleTopLevelCalls()) { 364 mOriginalConnection.getCall().getPhone().switchHoldingAndActive(); 365 } else { 366 Log.i(this, "Skipping unhold command for %s", this); 367 } 368 } catch (CallStateException e) { 369 Log.e(this, e, "Exception occurred while trying to release call from hold."); 370 } 371 } else { 372 Log.w(this, "Cannot release a call that is not already on hold from hold."); 373 } 374 } 375 376 public void performConference(TelephonyConnection otherConnection) { 377 Log.d(this, "performConference - %s", this); 378 if (getPhone() != null) { 379 try { 380 // We dont use the "other" connection because there is no concept of that in the 381 // implementation of calls inside telephony. Basically, you can "conference" and it 382 // will conference with the background call. We know that otherConnection is the 383 // background call because it would never have called setConferenceableConnections() 384 // otherwise. 385 getPhone().conference(); 386 } catch (CallStateException e) { 387 Log.e(this, e, "Failed to conference call."); 388 } 389 } 390 } 391 392 /** 393 * Builds call capabilities common to all TelephonyConnections. Namely, apply IMS-based 394 * capabilities. 395 */ 396 protected int buildCallCapabilities() { 397 int callCapabilities = 0; 398 if (isImsConnection()) { 399 callCapabilities |= PhoneCapabilities.ADD_CALL; 400 callCapabilities |= PhoneCapabilities.SUPPORT_HOLD; 401 if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) { 402 callCapabilities |= PhoneCapabilities.HOLD; 403 } 404 } 405 return callCapabilities; 406 } 407 408 protected final void updateCallCapabilities() { 409 int newCallCapabilities = buildCallCapabilities(); 410 newCallCapabilities = applyVideoCapabilities(newCallCapabilities); 411 newCallCapabilities = applyAudioQualityCapabilities(newCallCapabilities); 412 newCallCapabilities = applyConferenceTerminationCapabilities(newCallCapabilities); 413 414 if (getCallCapabilities() != newCallCapabilities) { 415 setCallCapabilities(newCallCapabilities); 416 } 417 } 418 419 protected final void updateAddress() { 420 updateCallCapabilities(); 421 if (mOriginalConnection != null) { 422 Uri address = getAddressFromNumber(mOriginalConnection.getAddress()); 423 int presentation = mOriginalConnection.getNumberPresentation(); 424 if (!Objects.equals(address, getAddress()) || 425 presentation != getAddressPresentation()) { 426 Log.v(this, "updateAddress, address changed"); 427 setAddress(address, presentation); 428 } 429 430 String name = mOriginalConnection.getCnapName(); 431 int namePresentation = mOriginalConnection.getCnapNamePresentation(); 432 if (!Objects.equals(name, getCallerDisplayName()) || 433 namePresentation != getCallerDisplayNamePresentation()) { 434 Log.v(this, "updateAddress, caller display name changed"); 435 setCallerDisplayName(name, namePresentation); 436 } 437 } 438 } 439 440 void onRemovedFromCallService() { 441 // Subclass can override this to do cleanup. 442 } 443 444 void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) { 445 Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); 446 if (mOriginalConnection != null) { 447 getPhone().unregisterForPreciseCallStateChanged(mHandler); 448 getPhone().unregisterForRingbackTone(mHandler); 449 getPhone().unregisterForHandoverStateChanged(mHandler); 450 getPhone().unregisterForDisconnect(mHandler); 451 } 452 mOriginalConnection = originalConnection; 453 getPhone().registerForPreciseCallStateChanged( 454 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null); 455 getPhone().registerForHandoverStateChanged( 456 mHandler, MSG_HANDOVER_STATE_CHANGED, null); 457 getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null); 458 getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null); 459 mOriginalConnection.addPostDialListener(mPostDialListener); 460 mOriginalConnection.addListener(mOriginalConnectionListener); 461 462 // Set video state and capabilities 463 setVideoState(mOriginalConnection.getVideoState()); 464 setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable()); 465 setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable()); 466 setVideoProvider(mOriginalConnection.getVideoProvider()); 467 setAudioQuality(mOriginalConnection.getAudioQuality()); 468 469 if (isImsConnection()) { 470 mWasImsConnection = true; 471 } 472 473 fireOnOriginalConnectionConfigured(); 474 updateAddress(); 475 } 476 477 protected void hangup(int telephonyDisconnectCode) { 478 if (mOriginalConnection != null) { 479 try { 480 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to 481 // connection.hangup(). Without this change, the party originating the call will not 482 // get sent to voicemail if the user opts to reject the call. 483 if (isValidRingingCall()) { 484 Call call = getCall(); 485 if (call != null) { 486 call.hangup(); 487 } else { 488 Log.w(this, "Attempting to hangup a connection without backing call."); 489 } 490 } else { 491 // We still prefer to call connection.hangup() for non-ringing calls in order 492 // to support hanging-up specific calls within a conference call. If we invoked 493 // call.hangup() while in a conference, we would end up hanging up the entire 494 // conference call instead of the specific connection. 495 mOriginalConnection.hangup(); 496 } 497 } catch (CallStateException e) { 498 Log.e(this, e, "Call to Connection.hangup failed with exception"); 499 } 500 } 501 } 502 503 com.android.internal.telephony.Connection getOriginalConnection() { 504 return mOriginalConnection; 505 } 506 507 protected Call getCall() { 508 if (mOriginalConnection != null) { 509 return mOriginalConnection.getCall(); 510 } 511 return null; 512 } 513 514 Phone getPhone() { 515 Call call = getCall(); 516 if (call != null) { 517 return call.getPhone(); 518 } 519 return null; 520 } 521 522 private boolean hasMultipleTopLevelCalls() { 523 int numCalls = 0; 524 Phone phone = getPhone(); 525 if (phone != null) { 526 if (!phone.getRingingCall().isIdle()) { 527 numCalls++; 528 } 529 if (!phone.getForegroundCall().isIdle()) { 530 numCalls++; 531 } 532 if (!phone.getBackgroundCall().isIdle()) { 533 numCalls++; 534 } 535 } 536 return numCalls > 1; 537 } 538 539 private com.android.internal.telephony.Connection getForegroundConnection() { 540 if (getPhone() != null) { 541 return getPhone().getForegroundCall().getEarliestConnection(); 542 } 543 return null; 544 } 545 546 /** 547 * Checks to see the original connection corresponds to an active incoming call. Returns false 548 * if there is no such actual call, or if the associated call is not incoming (See 549 * {@link Call.State#isRinging}). 550 */ 551 private boolean isValidRingingCall() { 552 if (getPhone() == null) { 553 Log.v(this, "isValidRingingCall, phone is null"); 554 return false; 555 } 556 557 Call ringingCall = getPhone().getRingingCall(); 558 if (!ringingCall.getState().isRinging()) { 559 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 560 return false; 561 } 562 563 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 564 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 565 return false; 566 } 567 568 Log.v(this, "isValidRingingCall, returning true"); 569 return true; 570 } 571 572 void updateState() { 573 if (mOriginalConnection == null) { 574 return; 575 } 576 577 Call.State newState = mOriginalConnection.getState(); 578 Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this); 579 if (mOriginalConnectionState != newState) { 580 mOriginalConnectionState = newState; 581 switch (newState) { 582 case IDLE: 583 break; 584 case ACTIVE: 585 setActiveInternal(); 586 break; 587 case HOLDING: 588 setOnHold(); 589 break; 590 case DIALING: 591 case ALERTING: 592 setDialing(); 593 break; 594 case INCOMING: 595 case WAITING: 596 setRinging(); 597 break; 598 case DISCONNECTED: 599 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 600 mOriginalConnection.getDisconnectCause())); 601 close(); 602 break; 603 case DISCONNECTING: 604 break; 605 } 606 } 607 updateCallCapabilities(); 608 updateAddress(); 609 } 610 611 private void setActiveInternal() { 612 if (getState() == STATE_ACTIVE) { 613 Log.w(this, "Should not be called if this is already ACTIVE"); 614 return; 615 } 616 617 // When we set a call to active, we need to make sure that there are no other active 618 // calls. However, the ordering of state updates to connections can be non-deterministic 619 // since all connections register for state changes on the phone independently. 620 // To "optimize", we check here to see if there already exists any active calls. If so, 621 // we issue an update for those calls first to make sure we only have one top-level 622 // active call. 623 if (getConnectionService() != null) { 624 for (Connection current : getConnectionService().getAllConnections()) { 625 if (current != this && current instanceof TelephonyConnection) { 626 TelephonyConnection other = (TelephonyConnection) current; 627 if (other.getState() == STATE_ACTIVE) { 628 other.updateState(); 629 } 630 } 631 } 632 } 633 setActive(); 634 } 635 636 private void close() { 637 Log.v(this, "close"); 638 if (getPhone() != null) { 639 getPhone().unregisterForPreciseCallStateChanged(mHandler); 640 getPhone().unregisterForRingbackTone(mHandler); 641 getPhone().unregisterForHandoverStateChanged(mHandler); 642 } 643 mOriginalConnection = null; 644 destroy(); 645 } 646 647 /** 648 * Applies the video capability states to the CallCapabilities bit-mask. 649 * 650 * @param capabilities The CallCapabilities bit-mask. 651 * @return The capabilities with video capabilities applied. 652 */ 653 private int applyVideoCapabilities(int capabilities) { 654 int currentCapabilities = capabilities; 655 if (mRemoteVideoCapable) { 656 currentCapabilities = applyCapability(currentCapabilities, 657 PhoneCapabilities.SUPPORTS_VT_REMOTE); 658 } else { 659 currentCapabilities = removeCapability(currentCapabilities, 660 PhoneCapabilities.SUPPORTS_VT_REMOTE); 661 } 662 663 if (mLocalVideoCapable) { 664 currentCapabilities = applyCapability(currentCapabilities, 665 PhoneCapabilities.SUPPORTS_VT_LOCAL); 666 } else { 667 currentCapabilities = removeCapability(currentCapabilities, 668 PhoneCapabilities.SUPPORTS_VT_LOCAL); 669 } 670 return currentCapabilities; 671 } 672 673 /** 674 * Applies the audio capabilities to the {@code CallCapabilities} bit-mask. A call with high 675 * definition audio is considered to have the {@code VoLTE} call capability as VoLTE uses high 676 * definition audio. 677 * 678 * @param callCapabilities The {@code CallCapabilities} bit-mask. 679 * @return The capabilities with the audio capabilities applied. 680 */ 681 private int applyAudioQualityCapabilities(int callCapabilities) { 682 int currentCapabilities = callCapabilities; 683 684 if (mAudioQuality == 685 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION) { 686 currentCapabilities = applyCapability(currentCapabilities, PhoneCapabilities.VoLTE); 687 } else { 688 currentCapabilities = removeCapability(currentCapabilities, PhoneCapabilities.VoLTE); 689 } 690 691 return currentCapabilities; 692 } 693 694 /** 695 * Applies capabilities specific to conferences termination to the 696 * {@code CallCapabilities} bit-mask. 697 * 698 * @param callCapabilities The {@code CallCapabilities} bit-mask. 699 * @return The capabilities with the IMS conference capabilities applied. 700 */ 701 private int applyConferenceTerminationCapabilities(int callCapabilities) { 702 int currentCapabilities = callCapabilities; 703 704 // An IMS call cannot be individually disconnected or separated from its parent conference. 705 // If the call was IMS, even if it hands over to GMS, these capabilities are not supported. 706 if (!mWasImsConnection) { 707 currentCapabilities |= PhoneCapabilities.DISCONNECT_FROM_CONFERENCE; 708 currentCapabilities |= PhoneCapabilities.SEPARATE_FROM_CONFERENCE; 709 } 710 711 return currentCapabilities; 712 } 713 714 /** 715 * Returns the local video capability state for the connection. 716 * 717 * @return {@code True} if the connection has local video capabilities. 718 */ 719 public boolean isLocalVideoCapable() { 720 return mLocalVideoCapable; 721 } 722 723 /** 724 * Returns the remote video capability state for the connection. 725 * 726 * @return {@code True} if the connection has remote video capabilities. 727 */ 728 public boolean isRemoteVideoCapable() { 729 return mRemoteVideoCapable; 730 } 731 732 /** 733 * Sets whether video capability is present locally. Used during rebuild of the 734 * {@link PhoneCapabilities} to set the video call capabilities. 735 * 736 * @param capable {@code True} if video capable. 737 */ 738 public void setLocalVideoCapable(boolean capable) { 739 mLocalVideoCapable = capable; 740 updateCallCapabilities(); 741 } 742 743 /** 744 * Sets whether video capability is present remotely. Used during rebuild of the 745 * {@link PhoneCapabilities} to set the video call capabilities. 746 * 747 * @param capable {@code True} if video capable. 748 */ 749 public void setRemoteVideoCapable(boolean capable) { 750 mRemoteVideoCapable = capable; 751 updateCallCapabilities(); 752 } 753 754 /** 755 * Sets the current call audio quality. Used during rebuild of the 756 * {@link PhoneCapabilities} to set or unset the {@link PhoneCapabilities#VoLTE} capability. 757 * 758 * @param audioQuality The audio quality. 759 */ 760 public void setAudioQuality(int audioQuality) { 761 mAudioQuality = audioQuality; 762 updateCallCapabilities(); 763 } 764 765 /** 766 * Obtains the current call audio quality. 767 */ 768 public int getAudioQuality() { 769 return mAudioQuality; 770 } 771 772 void resetStateForConference() { 773 if (getState() == Connection.STATE_HOLDING) { 774 if (mOriginalConnection.getState() == Call.State.ACTIVE) { 775 setActive(); 776 } 777 } 778 } 779 780 boolean setHoldingForConference() { 781 if (getState() == Connection.STATE_ACTIVE) { 782 setOnHold(); 783 return true; 784 } 785 return false; 786 } 787 788 /** 789 * Whether the original connection is an IMS connection. 790 * @return {@code True} if the original connection is an IMS connection, {@code false} 791 * otherwise. 792 */ 793 protected boolean isImsConnection() { 794 return getOriginalConnection() instanceof ImsPhoneConnection; 795 } 796 797 /** 798 * Whether the original connection was ever an IMS connection, either before or now. 799 * @return {@code True} if the original connection was ever an IMS connection, {@code false} 800 * otherwise. 801 */ 802 public boolean wasImsConnection() { 803 return mWasImsConnection; 804 } 805 806 private static Uri getAddressFromNumber(String number) { 807 // Address can be null for blocked calls. 808 if (number == null) { 809 number = ""; 810 } 811 return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 812 } 813 814 /** 815 * Applies a capability to a capabilities bit-mask. 816 * 817 * @param capabilities The capabilities bit-mask. 818 * @param capability The capability to apply. 819 * @return The capabilities bit-mask with the capability applied. 820 */ 821 private int applyCapability(int capabilities, int capability) { 822 int newCapabilities = capabilities | capability; 823 return newCapabilities; 824 } 825 826 /** 827 * Removes a capability from a capabilities bit-mask. 828 * 829 * @param capabilities The capabilities bit-mask. 830 * @param capability The capability to remove. 831 * @return The capabilities bit-mask with the capability removed. 832 */ 833 private int removeCapability(int capabilities, int capability) { 834 int newCapabilities = capabilities & ~capability; 835 return newCapabilities; 836 } 837 838 /** 839 * Register a listener for {@link TelephonyConnection} specific triggers. 840 * @param l The instance of the listener to add 841 * @return The connection being listened to 842 */ 843 public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) { 844 mTelephonyListeners.add(l); 845 // If we already have an original connection, let's call back immediately. 846 // This would be the case for incoming calls. 847 if (mOriginalConnection != null) { 848 fireOnOriginalConnectionConfigured(); 849 } 850 return this; 851 } 852 853 /** 854 * Remove a listener for {@link TelephonyConnection} specific triggers. 855 * @param l The instance of the listener to remove 856 * @return The connection being listened to 857 */ 858 public final TelephonyConnection removeTelephonyConnectionListener( 859 TelephonyConnectionListener l) { 860 if (l != null) { 861 mTelephonyListeners.remove(l); 862 } 863 return this; 864 } 865 866 /** 867 * Fire a callback to the various listeners for when the original connection is 868 * set in this {@link TelephonyConnection} 869 */ 870 private final void fireOnOriginalConnectionConfigured() { 871 for (TelephonyConnectionListener l : mTelephonyListeners) { 872 l.onOriginalConnectionConfigured(this); 873 } 874 } 875} 876