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