TelephonyConnection.java revision e01728b713bfe06a1214192b90c29fecbfdb064e
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.SUPPORT_HOLD; 400 if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) { 401 callCapabilities |= PhoneCapabilities.HOLD; 402 } 403 } 404 return callCapabilities; 405 } 406 407 protected final void updateCallCapabilities() { 408 int newCallCapabilities = buildCallCapabilities(); 409 newCallCapabilities = applyVideoCapabilities(newCallCapabilities); 410 newCallCapabilities = applyAudioQualityCapabilities(newCallCapabilities); 411 newCallCapabilities = applyConferenceTerminationCapabilities(newCallCapabilities); 412 413 if (getCallCapabilities() != newCallCapabilities) { 414 setCallCapabilities(newCallCapabilities); 415 } 416 } 417 418 protected final void updateAddress() { 419 updateCallCapabilities(); 420 if (mOriginalConnection != null) { 421 Uri address = getAddressFromNumber(mOriginalConnection.getAddress()); 422 int presentation = mOriginalConnection.getNumberPresentation(); 423 if (!Objects.equals(address, getAddress()) || 424 presentation != getAddressPresentation()) { 425 Log.v(this, "updateAddress, address changed"); 426 setAddress(address, presentation); 427 } 428 429 String name = mOriginalConnection.getCnapName(); 430 int namePresentation = mOriginalConnection.getCnapNamePresentation(); 431 if (!Objects.equals(name, getCallerDisplayName()) || 432 namePresentation != getCallerDisplayNamePresentation()) { 433 Log.v(this, "updateAddress, caller display name changed"); 434 setCallerDisplayName(name, namePresentation); 435 } 436 } 437 } 438 439 void onRemovedFromCallService() { 440 // Subclass can override this to do cleanup. 441 } 442 443 void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) { 444 Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); 445 if (mOriginalConnection != null) { 446 getPhone().unregisterForPreciseCallStateChanged(mHandler); 447 getPhone().unregisterForRingbackTone(mHandler); 448 getPhone().unregisterForHandoverStateChanged(mHandler); 449 getPhone().unregisterForDisconnect(mHandler); 450 } 451 mOriginalConnection = originalConnection; 452 getPhone().registerForPreciseCallStateChanged( 453 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null); 454 getPhone().registerForHandoverStateChanged( 455 mHandler, MSG_HANDOVER_STATE_CHANGED, null); 456 getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null); 457 getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null); 458 mOriginalConnection.addPostDialListener(mPostDialListener); 459 mOriginalConnection.addListener(mOriginalConnectionListener); 460 461 // Set video state and capabilities 462 setVideoState(mOriginalConnection.getVideoState()); 463 setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable()); 464 setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable()); 465 setVideoProvider(mOriginalConnection.getVideoProvider()); 466 setAudioQuality(mOriginalConnection.getAudioQuality()); 467 468 if (isImsConnection()) { 469 mWasImsConnection = true; 470 } 471 472 fireOnOriginalConnectionConfigured(); 473 updateAddress(); 474 } 475 476 protected void hangup(int telephonyDisconnectCode) { 477 if (mOriginalConnection != null) { 478 try { 479 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to 480 // connection.hangup(). Without this change, the party originating the call will not 481 // get sent to voicemail if the user opts to reject the call. 482 if (isValidRingingCall()) { 483 Call call = getCall(); 484 if (call != null) { 485 call.hangup(); 486 } else { 487 Log.w(this, "Attempting to hangup a connection without backing call."); 488 } 489 } else { 490 // We still prefer to call connection.hangup() for non-ringing calls in order 491 // to support hanging-up specific calls within a conference call. If we invoked 492 // call.hangup() while in a conference, we would end up hanging up the entire 493 // conference call instead of the specific connection. 494 mOriginalConnection.hangup(); 495 } 496 } catch (CallStateException e) { 497 Log.e(this, e, "Call to Connection.hangup failed with exception"); 498 } 499 } 500 } 501 502 com.android.internal.telephony.Connection getOriginalConnection() { 503 return mOriginalConnection; 504 } 505 506 protected Call getCall() { 507 if (mOriginalConnection != null) { 508 return mOriginalConnection.getCall(); 509 } 510 return null; 511 } 512 513 Phone getPhone() { 514 Call call = getCall(); 515 if (call != null) { 516 return call.getPhone(); 517 } 518 return null; 519 } 520 521 private boolean hasMultipleTopLevelCalls() { 522 int numCalls = 0; 523 Phone phone = getPhone(); 524 if (phone != null) { 525 if (!phone.getRingingCall().isIdle()) { 526 numCalls++; 527 } 528 if (!phone.getForegroundCall().isIdle()) { 529 numCalls++; 530 } 531 if (!phone.getBackgroundCall().isIdle()) { 532 numCalls++; 533 } 534 } 535 return numCalls > 1; 536 } 537 538 private com.android.internal.telephony.Connection getForegroundConnection() { 539 if (getPhone() != null) { 540 return getPhone().getForegroundCall().getEarliestConnection(); 541 } 542 return null; 543 } 544 545 /** 546 * Checks to see the original connection corresponds to an active incoming call. Returns false 547 * if there is no such actual call, or if the associated call is not incoming (See 548 * {@link Call.State#isRinging}). 549 */ 550 private boolean isValidRingingCall() { 551 if (getPhone() == null) { 552 Log.v(this, "isValidRingingCall, phone is null"); 553 return false; 554 } 555 556 Call ringingCall = getPhone().getRingingCall(); 557 if (!ringingCall.getState().isRinging()) { 558 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 559 return false; 560 } 561 562 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 563 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 564 return false; 565 } 566 567 Log.v(this, "isValidRingingCall, returning true"); 568 return true; 569 } 570 571 void updateState() { 572 if (mOriginalConnection == null) { 573 return; 574 } 575 576 Call.State newState = mOriginalConnection.getState(); 577 Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this); 578 if (mOriginalConnectionState != newState) { 579 mOriginalConnectionState = newState; 580 switch (newState) { 581 case IDLE: 582 break; 583 case ACTIVE: 584 setActiveInternal(); 585 break; 586 case HOLDING: 587 setOnHold(); 588 break; 589 case DIALING: 590 case ALERTING: 591 setDialing(); 592 break; 593 case INCOMING: 594 case WAITING: 595 setRinging(); 596 break; 597 case DISCONNECTED: 598 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 599 mOriginalConnection.getDisconnectCause())); 600 close(); 601 break; 602 case DISCONNECTING: 603 break; 604 } 605 } 606 updateCallCapabilities(); 607 updateAddress(); 608 } 609 610 private void setActiveInternal() { 611 if (getState() == STATE_ACTIVE) { 612 Log.w(this, "Should not be called if this is already ACTIVE"); 613 return; 614 } 615 616 // When we set a call to active, we need to make sure that there are no other active 617 // calls. However, the ordering of state updates to connections can be non-deterministic 618 // since all connections register for state changes on the phone independently. 619 // To "optimize", we check here to see if there already exists any active calls. If so, 620 // we issue an update for those calls first to make sure we only have one top-level 621 // active call. 622 if (getConnectionService() != null) { 623 for (Connection current : getConnectionService().getAllConnections()) { 624 if (current != this && current instanceof TelephonyConnection) { 625 TelephonyConnection other = (TelephonyConnection) current; 626 if (other.getState() == STATE_ACTIVE) { 627 other.updateState(); 628 } 629 } 630 } 631 } 632 setActive(); 633 } 634 635 private void close() { 636 Log.v(this, "close"); 637 if (getPhone() != null) { 638 getPhone().unregisterForPreciseCallStateChanged(mHandler); 639 getPhone().unregisterForRingbackTone(mHandler); 640 getPhone().unregisterForHandoverStateChanged(mHandler); 641 } 642 mOriginalConnection = null; 643 destroy(); 644 } 645 646 /** 647 * Applies the video capability states to the CallCapabilities bit-mask. 648 * 649 * @param capabilities The CallCapabilities bit-mask. 650 * @return The capabilities with video capabilities applied. 651 */ 652 private int applyVideoCapabilities(int capabilities) { 653 int currentCapabilities = capabilities; 654 if (mRemoteVideoCapable) { 655 currentCapabilities = applyCapability(currentCapabilities, 656 PhoneCapabilities.SUPPORTS_VT_REMOTE); 657 } else { 658 currentCapabilities = removeCapability(currentCapabilities, 659 PhoneCapabilities.SUPPORTS_VT_REMOTE); 660 } 661 662 if (mLocalVideoCapable) { 663 currentCapabilities = applyCapability(currentCapabilities, 664 PhoneCapabilities.SUPPORTS_VT_LOCAL); 665 } else { 666 currentCapabilities = removeCapability(currentCapabilities, 667 PhoneCapabilities.SUPPORTS_VT_LOCAL); 668 } 669 return currentCapabilities; 670 } 671 672 /** 673 * Applies the audio capabilities to the {@code CallCapabilities} bit-mask. A call with high 674 * definition audio is considered to have the {@code VoLTE} call capability as VoLTE uses high 675 * definition audio. 676 * 677 * @param callCapabilities The {@code CallCapabilities} bit-mask. 678 * @return The capabilities with the audio capabilities applied. 679 */ 680 private int applyAudioQualityCapabilities(int callCapabilities) { 681 int currentCapabilities = callCapabilities; 682 683 if (mAudioQuality == 684 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION) { 685 currentCapabilities = applyCapability(currentCapabilities, PhoneCapabilities.VoLTE); 686 } else { 687 currentCapabilities = removeCapability(currentCapabilities, PhoneCapabilities.VoLTE); 688 } 689 690 return currentCapabilities; 691 } 692 693 /** 694 * Applies capabilities specific to conferences termination to the 695 * {@code CallCapabilities} bit-mask. 696 * 697 * @param callCapabilities The {@code CallCapabilities} bit-mask. 698 * @return The capabilities with the IMS conference capabilities applied. 699 */ 700 private int applyConferenceTerminationCapabilities(int callCapabilities) { 701 int currentCapabilities = callCapabilities; 702 703 // An IMS call cannot be individually disconnected or separated from its parent conference. 704 // If the call was IMS, even if it hands over to GMS, these capabilities are not supported. 705 if (!mWasImsConnection) { 706 currentCapabilities |= PhoneCapabilities.DISCONNECT_FROM_CONFERENCE; 707 currentCapabilities |= PhoneCapabilities.SEPARATE_FROM_CONFERENCE; 708 } 709 710 return currentCapabilities; 711 } 712 713 /** 714 * Returns the local video capability state for the connection. 715 * 716 * @return {@code True} if the connection has local video capabilities. 717 */ 718 public boolean isLocalVideoCapable() { 719 return mLocalVideoCapable; 720 } 721 722 /** 723 * Returns the remote video capability state for the connection. 724 * 725 * @return {@code True} if the connection has remote video capabilities. 726 */ 727 public boolean isRemoteVideoCapable() { 728 return mRemoteVideoCapable; 729 } 730 731 /** 732 * Sets whether video capability is present locally. Used during rebuild of the 733 * {@link PhoneCapabilities} to set the video call capabilities. 734 * 735 * @param capable {@code True} if video capable. 736 */ 737 public void setLocalVideoCapable(boolean capable) { 738 mLocalVideoCapable = capable; 739 updateCallCapabilities(); 740 } 741 742 /** 743 * Sets whether video capability is present remotely. Used during rebuild of the 744 * {@link PhoneCapabilities} to set the video call capabilities. 745 * 746 * @param capable {@code True} if video capable. 747 */ 748 public void setRemoteVideoCapable(boolean capable) { 749 mRemoteVideoCapable = capable; 750 updateCallCapabilities(); 751 } 752 753 /** 754 * Sets the current call audio quality. Used during rebuild of the 755 * {@link PhoneCapabilities} to set or unset the {@link PhoneCapabilities#VoLTE} capability. 756 * 757 * @param audioQuality The audio quality. 758 */ 759 public void setAudioQuality(int audioQuality) { 760 mAudioQuality = audioQuality; 761 updateCallCapabilities(); 762 } 763 764 /** 765 * Obtains the current call audio quality. 766 */ 767 public int getAudioQuality() { 768 return mAudioQuality; 769 } 770 771 void resetStateForConference() { 772 if (getState() == Connection.STATE_HOLDING) { 773 if (mOriginalConnection.getState() == Call.State.ACTIVE) { 774 setActive(); 775 } 776 } 777 } 778 779 boolean setHoldingForConference() { 780 if (getState() == Connection.STATE_ACTIVE) { 781 setOnHold(); 782 return true; 783 } 784 return false; 785 } 786 787 /** 788 * Whether the original connection is an IMS connection. 789 * @return {@code True} if the original connection is an IMS connection, {@code false} 790 * otherwise. 791 */ 792 protected boolean isImsConnection() { 793 return getOriginalConnection() instanceof ImsPhoneConnection; 794 } 795 796 /** 797 * Whether the original connection was ever an IMS connection, either before or now. 798 * @return {@code True} if the original connection was ever an IMS connection, {@code false} 799 * otherwise. 800 */ 801 public boolean wasImsConnection() { 802 return mWasImsConnection; 803 } 804 805 private static Uri getAddressFromNumber(String number) { 806 // Address can be null for blocked calls. 807 if (number == null) { 808 number = ""; 809 } 810 return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 811 } 812 813 /** 814 * Applies a capability to a capabilities bit-mask. 815 * 816 * @param capabilities The capabilities bit-mask. 817 * @param capability The capability to apply. 818 * @return The capabilities bit-mask with the capability applied. 819 */ 820 private int applyCapability(int capabilities, int capability) { 821 int newCapabilities = capabilities | capability; 822 return newCapabilities; 823 } 824 825 /** 826 * Removes a capability from a capabilities bit-mask. 827 * 828 * @param capabilities The capabilities bit-mask. 829 * @param capability The capability to remove. 830 * @return The capabilities bit-mask with the capability removed. 831 */ 832 private int removeCapability(int capabilities, int capability) { 833 int newCapabilities = capabilities & ~capability; 834 return newCapabilities; 835 } 836 837 /** 838 * Register a listener for {@link TelephonyConnection} specific triggers. 839 * @param l The instance of the listener to add 840 * @return The connection being listened to 841 */ 842 public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) { 843 mTelephonyListeners.add(l); 844 // If we already have an original connection, let's call back immediately. 845 // This would be the case for incoming calls. 846 if (mOriginalConnection != null) { 847 fireOnOriginalConnectionConfigured(); 848 } 849 return this; 850 } 851 852 /** 853 * Remove a listener for {@link TelephonyConnection} specific triggers. 854 * @param l The instance of the listener to remove 855 * @return The connection being listened to 856 */ 857 public final TelephonyConnection removeTelephonyConnectionListener( 858 TelephonyConnectionListener l) { 859 if (l != null) { 860 mTelephonyListeners.remove(l); 861 } 862 return this; 863 } 864 865 /** 866 * Fire a callback to the various listeners for when the original connection is 867 * set in this {@link TelephonyConnection} 868 */ 869 private final void fireOnOriginalConnectionConfigured() { 870 for (TelephonyConnectionListener l : mTelephonyListeners) { 871 l.onOriginalConnectionConfigured(this); 872 } 873 } 874} 875