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