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