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 { onOriginalConnectionConfigured(TelephonyConnection c)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 TelephonyConnection(com.android.internal.telephony.Connection originalConnection)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 */ cloneConnection()236 public abstract TelephonyConnection cloneConnection(); 237 238 @Override onAudioStateChanged(AudioState audioState)239 public void onAudioStateChanged(AudioState audioState) { 240 // TODO: update TTY mode. 241 if (getPhone() != null) { 242 getPhone().setEchoSuppressionEnabled(); 243 } 244 } 245 246 @Override onStateChanged(int state)247 public void onStateChanged(int state) { 248 Log.v(this, "onStateChanged, state: " + Connection.stateToString(state)); 249 } 250 251 @Override onDisconnect()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 onDisconnectConferenceParticipant(Uri endpoint)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 onSeparate()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 onAbort()287 public void onAbort() { 288 Log.v(this, "onAbort"); 289 hangup(android.telephony.DisconnectCause.LOCAL); 290 } 291 292 @Override onHold()293 public void onHold() { 294 performHold(); 295 } 296 297 @Override onUnhold()298 public void onUnhold() { 299 performUnhold(); 300 } 301 302 @Override onAnswer(int videoState)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 onReject()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 onPostDialContinue(boolean proceed)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 performHold()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 performUnhold()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 performConference(TelephonyConnection otherConnection)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 */ buildConnectionCapabilities()420 protected int buildConnectionCapabilities() { 421 int callCapabilities = 0; 422 if (isImsConnection()) { 423 callCapabilities |= CAPABILITY_SUPPORT_HOLD; 424 if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) { 425 callCapabilities |= CAPABILITY_HOLD; 426 } 427 } 428 return callCapabilities; 429 } 430 updateConnectionCapabilities()431 protected final void updateConnectionCapabilities() { 432 int newCapabilities = buildConnectionCapabilities(); 433 newCapabilities = applyVideoCapabilities(newCapabilities); 434 newCapabilities = applyAudioQualityCapabilities(newCapabilities); 435 newCapabilities = applyConferenceTerminationCapabilities(newCapabilities); 436 437 if (getConnectionCapabilities() != newCapabilities) { 438 setConnectionCapabilities(newCapabilities); 439 } 440 } 441 updateAddress()442 protected final void updateAddress() { 443 updateConnectionCapabilities(); 444 if (mOriginalConnection != null) { 445 Uri address = getAddressFromNumber(mOriginalConnection.getAddress()); 446 int presentation = mOriginalConnection.getNumberPresentation(); 447 if (!Objects.equals(address, getAddress()) || 448 presentation != getAddressPresentation()) { 449 Log.v(this, "updateAddress, address changed"); 450 setAddress(address, presentation); 451 } 452 453 String name = mOriginalConnection.getCnapName(); 454 int namePresentation = mOriginalConnection.getCnapNamePresentation(); 455 if (!Objects.equals(name, getCallerDisplayName()) || 456 namePresentation != getCallerDisplayNamePresentation()) { 457 Log.v(this, "updateAddress, caller display name changed"); 458 setCallerDisplayName(name, namePresentation); 459 } 460 } 461 } 462 onRemovedFromCallService()463 void onRemovedFromCallService() { 464 // Subclass can override this to do cleanup. 465 } 466 setOriginalConnection(com.android.internal.telephony.Connection originalConnection)467 void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) { 468 Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); 469 clearOriginalConnection(); 470 471 mOriginalConnection = originalConnection; 472 getPhone().registerForPreciseCallStateChanged( 473 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null); 474 getPhone().registerForHandoverStateChanged( 475 mHandler, MSG_HANDOVER_STATE_CHANGED, null); 476 getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null); 477 getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null); 478 mOriginalConnection.addPostDialListener(mPostDialListener); 479 mOriginalConnection.addListener(mOriginalConnectionListener); 480 481 // Set video state and capabilities 482 setVideoState(mOriginalConnection.getVideoState()); 483 setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable()); 484 setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable()); 485 setVideoProvider(mOriginalConnection.getVideoProvider()); 486 setAudioQuality(mOriginalConnection.getAudioQuality()); 487 488 if (isImsConnection()) { 489 mWasImsConnection = true; 490 } 491 mIsMultiParty = mOriginalConnection.isMultiparty(); 492 493 fireOnOriginalConnectionConfigured(); 494 updateAddress(); 495 } 496 497 /** 498 * Un-sets the underlying radio connection. 499 */ clearOriginalConnection()500 void clearOriginalConnection() { 501 if (mOriginalConnection != null) { 502 getPhone().unregisterForPreciseCallStateChanged(mHandler); 503 getPhone().unregisterForRingbackTone(mHandler); 504 getPhone().unregisterForHandoverStateChanged(mHandler); 505 getPhone().unregisterForDisconnect(mHandler); 506 mOriginalConnection = null; 507 } 508 } 509 hangup(int telephonyDisconnectCode)510 protected void hangup(int telephonyDisconnectCode) { 511 if (mOriginalConnection != null) { 512 try { 513 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to 514 // connection.hangup(). Without this change, the party originating the call will not 515 // get sent to voicemail if the user opts to reject the call. 516 if (isValidRingingCall()) { 517 Call call = getCall(); 518 if (call != null) { 519 call.hangup(); 520 } else { 521 Log.w(this, "Attempting to hangup a connection without backing call."); 522 } 523 } else { 524 // We still prefer to call connection.hangup() for non-ringing calls in order 525 // to support hanging-up specific calls within a conference call. If we invoked 526 // call.hangup() while in a conference, we would end up hanging up the entire 527 // conference call instead of the specific connection. 528 mOriginalConnection.hangup(); 529 } 530 } catch (CallStateException e) { 531 Log.e(this, e, "Call to Connection.hangup failed with exception"); 532 } 533 } 534 } 535 getOriginalConnection()536 com.android.internal.telephony.Connection getOriginalConnection() { 537 return mOriginalConnection; 538 } 539 getCall()540 protected Call getCall() { 541 if (mOriginalConnection != null) { 542 return mOriginalConnection.getCall(); 543 } 544 return null; 545 } 546 getPhone()547 Phone getPhone() { 548 Call call = getCall(); 549 if (call != null) { 550 return call.getPhone(); 551 } 552 return null; 553 } 554 hasMultipleTopLevelCalls()555 private boolean hasMultipleTopLevelCalls() { 556 int numCalls = 0; 557 Phone phone = getPhone(); 558 if (phone != null) { 559 if (!phone.getRingingCall().isIdle()) { 560 numCalls++; 561 } 562 if (!phone.getForegroundCall().isIdle()) { 563 numCalls++; 564 } 565 if (!phone.getBackgroundCall().isIdle()) { 566 numCalls++; 567 } 568 } 569 return numCalls > 1; 570 } 571 getForegroundConnection()572 private com.android.internal.telephony.Connection getForegroundConnection() { 573 if (getPhone() != null) { 574 return getPhone().getForegroundCall().getEarliestConnection(); 575 } 576 return null; 577 } 578 579 /** 580 * Checks to see the original connection corresponds to an active incoming call. Returns false 581 * if there is no such actual call, or if the associated call is not incoming (See 582 * {@link Call.State#isRinging}). 583 */ isValidRingingCall()584 private boolean isValidRingingCall() { 585 if (getPhone() == null) { 586 Log.v(this, "isValidRingingCall, phone is null"); 587 return false; 588 } 589 590 Call ringingCall = getPhone().getRingingCall(); 591 if (!ringingCall.getState().isRinging()) { 592 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 593 return false; 594 } 595 596 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 597 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 598 return false; 599 } 600 601 Log.v(this, "isValidRingingCall, returning true"); 602 return true; 603 } 604 updateState()605 void updateState() { 606 if (mOriginalConnection == null) { 607 return; 608 } 609 610 Call.State newState = mOriginalConnection.getState(); 611 Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this); 612 if (mOriginalConnectionState != newState) { 613 mOriginalConnectionState = newState; 614 switch (newState) { 615 case IDLE: 616 break; 617 case ACTIVE: 618 setActiveInternal(); 619 break; 620 case HOLDING: 621 setOnHold(); 622 break; 623 case DIALING: 624 case ALERTING: 625 setDialing(); 626 break; 627 case INCOMING: 628 case WAITING: 629 setRinging(); 630 break; 631 case DISCONNECTED: 632 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 633 mOriginalConnection.getDisconnectCause())); 634 close(); 635 break; 636 case DISCONNECTING: 637 break; 638 } 639 } 640 updateConnectionCapabilities(); 641 updateAddress(); 642 updateMultiparty(); 643 } 644 645 /** 646 * Checks for changes to the multiparty bit. If a conference has started, informs listeners. 647 */ updateMultiparty()648 private void updateMultiparty() { 649 if (mOriginalConnection == null) { 650 return; 651 } 652 653 if (mIsMultiParty != mOriginalConnection.isMultiparty()) { 654 mIsMultiParty = mOriginalConnection.isMultiparty(); 655 656 if (mIsMultiParty) { 657 notifyConferenceStarted(); 658 } 659 } 660 } 661 setActiveInternal()662 private void setActiveInternal() { 663 if (getState() == STATE_ACTIVE) { 664 Log.w(this, "Should not be called if this is already ACTIVE"); 665 return; 666 } 667 668 // When we set a call to active, we need to make sure that there are no other active 669 // calls. However, the ordering of state updates to connections can be non-deterministic 670 // since all connections register for state changes on the phone independently. 671 // To "optimize", we check here to see if there already exists any active calls. If so, 672 // we issue an update for those calls first to make sure we only have one top-level 673 // active call. 674 if (getConnectionService() != null) { 675 for (Connection current : getConnectionService().getAllConnections()) { 676 if (current != this && current instanceof TelephonyConnection) { 677 TelephonyConnection other = (TelephonyConnection) current; 678 if (other.getState() == STATE_ACTIVE) { 679 other.updateState(); 680 } 681 } 682 } 683 } 684 setActive(); 685 } 686 close()687 private void close() { 688 Log.v(this, "close"); 689 if (getPhone() != null) { 690 getPhone().unregisterForPreciseCallStateChanged(mHandler); 691 getPhone().unregisterForRingbackTone(mHandler); 692 getPhone().unregisterForHandoverStateChanged(mHandler); 693 } 694 mOriginalConnection = null; 695 destroy(); 696 } 697 698 /** 699 * Applies the video capability states to the CallCapabilities bit-mask. 700 * 701 * @param capabilities The CallCapabilities bit-mask. 702 * @return The capabilities with video capabilities applied. 703 */ applyVideoCapabilities(int capabilities)704 private int applyVideoCapabilities(int capabilities) { 705 int currentCapabilities = capabilities; 706 if (mRemoteVideoCapable) { 707 currentCapabilities = applyCapability(currentCapabilities, 708 CAPABILITY_SUPPORTS_VT_REMOTE); 709 } else { 710 currentCapabilities = removeCapability(currentCapabilities, 711 CAPABILITY_SUPPORTS_VT_REMOTE); 712 } 713 714 if (mLocalVideoCapable) { 715 currentCapabilities = applyCapability(currentCapabilities, 716 CAPABILITY_SUPPORTS_VT_LOCAL); 717 } else { 718 currentCapabilities = removeCapability(currentCapabilities, 719 CAPABILITY_SUPPORTS_VT_LOCAL); 720 } 721 return currentCapabilities; 722 } 723 724 /** 725 * Applies the audio capabilities to the {@code CallCapabilities} bit-mask. A call with high 726 * definition audio is considered to have the {@code HIGH_DEF_AUDIO} call capability. 727 * 728 * @param capabilities The {@code CallCapabilities} bit-mask. 729 * @return The capabilities with the audio capabilities applied. 730 */ applyAudioQualityCapabilities(int capabilities)731 private int applyAudioQualityCapabilities(int capabilities) { 732 int currentCapabilities = capabilities; 733 734 if (mAudioQuality == 735 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION) { 736 currentCapabilities = applyCapability(currentCapabilities, CAPABILITY_HIGH_DEF_AUDIO); 737 } else { 738 currentCapabilities = removeCapability(currentCapabilities, CAPABILITY_HIGH_DEF_AUDIO); 739 } 740 741 return currentCapabilities; 742 } 743 744 /** 745 * Applies capabilities specific to conferences termination to the 746 * {@code CallCapabilities} bit-mask. 747 * 748 * @param capabilities The {@code CallCapabilities} bit-mask. 749 * @return The capabilities with the IMS conference capabilities applied. 750 */ applyConferenceTerminationCapabilities(int capabilities)751 private int applyConferenceTerminationCapabilities(int capabilities) { 752 int currentCapabilities = capabilities; 753 754 // An IMS call cannot be individually disconnected or separated from its parent conference. 755 // If the call was IMS, even if it hands over to GMS, these capabilities are not supported. 756 if (!mWasImsConnection) { 757 currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE; 758 currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE; 759 } 760 761 return currentCapabilities; 762 } 763 764 /** 765 * Returns the local video capability state for the connection. 766 * 767 * @return {@code True} if the connection has local video capabilities. 768 */ isLocalVideoCapable()769 public boolean isLocalVideoCapable() { 770 return mLocalVideoCapable; 771 } 772 773 /** 774 * Returns the remote video capability state for the connection. 775 * 776 * @return {@code True} if the connection has remote video capabilities. 777 */ isRemoteVideoCapable()778 public boolean isRemoteVideoCapable() { 779 return mRemoteVideoCapable; 780 } 781 782 /** 783 * Sets whether video capability is present locally. Used during rebuild of the 784 * capabilities to set the video call capabilities. 785 * 786 * @param capable {@code True} if video capable. 787 */ setLocalVideoCapable(boolean capable)788 public void setLocalVideoCapable(boolean capable) { 789 mLocalVideoCapable = capable; 790 updateConnectionCapabilities(); 791 } 792 793 /** 794 * Sets whether video capability is present remotely. Used during rebuild of the 795 * capabilities to set the video call capabilities. 796 * 797 * @param capable {@code True} if video capable. 798 */ setRemoteVideoCapable(boolean capable)799 public void setRemoteVideoCapable(boolean capable) { 800 mRemoteVideoCapable = capable; 801 updateConnectionCapabilities(); 802 } 803 804 /** 805 * Sets the current call audio quality. Used during rebuild of the capabilities 806 * to set or unset the {@link Connection#CAPABILITY_HIGH_DEF_AUDIO} capability. 807 * 808 * @param audioQuality The audio quality. 809 */ setAudioQuality(int audioQuality)810 public void setAudioQuality(int audioQuality) { 811 mAudioQuality = audioQuality; 812 updateConnectionCapabilities(); 813 } 814 815 /** 816 * Obtains the current call audio quality. 817 */ getAudioQuality()818 public int getAudioQuality() { 819 return mAudioQuality; 820 } 821 resetStateForConference()822 void resetStateForConference() { 823 if (getState() == Connection.STATE_HOLDING) { 824 if (mOriginalConnection.getState() == Call.State.ACTIVE) { 825 setActive(); 826 } 827 } 828 } 829 setHoldingForConference()830 boolean setHoldingForConference() { 831 if (getState() == Connection.STATE_ACTIVE) { 832 setOnHold(); 833 return true; 834 } 835 return false; 836 } 837 838 /** 839 * Whether the original connection is an IMS connection. 840 * @return {@code True} if the original connection is an IMS connection, {@code false} 841 * otherwise. 842 */ isImsConnection()843 protected boolean isImsConnection() { 844 return getOriginalConnection() instanceof ImsPhoneConnection; 845 } 846 847 /** 848 * Whether the original connection was ever an IMS connection, either before or now. 849 * @return {@code True} if the original connection was ever an IMS connection, {@code false} 850 * otherwise. 851 */ wasImsConnection()852 public boolean wasImsConnection() { 853 return mWasImsConnection; 854 } 855 getAddressFromNumber(String number)856 private static Uri getAddressFromNumber(String number) { 857 // Address can be null for blocked calls. 858 if (number == null) { 859 number = ""; 860 } 861 return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 862 } 863 864 /** 865 * Applies a capability to a capabilities bit-mask. 866 * 867 * @param capabilities The capabilities bit-mask. 868 * @param capability The capability to apply. 869 * @return The capabilities bit-mask with the capability applied. 870 */ applyCapability(int capabilities, int capability)871 private int applyCapability(int capabilities, int capability) { 872 int newCapabilities = capabilities | capability; 873 return newCapabilities; 874 } 875 876 /** 877 * Removes a capability from a capabilities bit-mask. 878 * 879 * @param capabilities The capabilities bit-mask. 880 * @param capability The capability to remove. 881 * @return The capabilities bit-mask with the capability removed. 882 */ removeCapability(int capabilities, int capability)883 private int removeCapability(int capabilities, int capability) { 884 int newCapabilities = capabilities & ~capability; 885 return newCapabilities; 886 } 887 888 /** 889 * Register a listener for {@link TelephonyConnection} specific triggers. 890 * @param l The instance of the listener to add 891 * @return The connection being listened to 892 */ addTelephonyConnectionListener(TelephonyConnectionListener l)893 public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) { 894 mTelephonyListeners.add(l); 895 // If we already have an original connection, let's call back immediately. 896 // This would be the case for incoming calls. 897 if (mOriginalConnection != null) { 898 fireOnOriginalConnectionConfigured(); 899 } 900 return this; 901 } 902 903 /** 904 * Remove a listener for {@link TelephonyConnection} specific triggers. 905 * @param l The instance of the listener to remove 906 * @return The connection being listened to 907 */ removeTelephonyConnectionListener( TelephonyConnectionListener l)908 public final TelephonyConnection removeTelephonyConnectionListener( 909 TelephonyConnectionListener l) { 910 if (l != null) { 911 mTelephonyListeners.remove(l); 912 } 913 return this; 914 } 915 916 /** 917 * Fire a callback to the various listeners for when the original connection is 918 * set in this {@link TelephonyConnection} 919 */ fireOnOriginalConnectionConfigured()920 private final void fireOnOriginalConnectionConfigured() { 921 for (TelephonyConnectionListener l : mTelephonyListeners) { 922 l.onOriginalConnectionConfigured(this); 923 } 924 } 925 926 /** 927 * Creates a string representation of this {@link TelephonyConnection}. Primarily intended for 928 * use in log statements. 929 * 930 * @return String representation of the connection. 931 */ 932 @Override toString()933 public String toString() { 934 StringBuilder sb = new StringBuilder(); 935 sb.append("[TelephonyConnection objId:"); 936 sb.append(System.identityHashCode(this)); 937 sb.append(" type:"); 938 if (isImsConnection()) { 939 sb.append("ims"); 940 } else if (this instanceof com.android.services.telephony.GsmConnection) { 941 sb.append("gsm"); 942 } else if (this instanceof CdmaConnection) { 943 sb.append("cdma"); 944 } 945 sb.append(" state:"); 946 sb.append(Connection.stateToString(getState())); 947 sb.append(" capabilities:"); 948 sb.append(capabilitiesToString(getConnectionCapabilities())); 949 sb.append(" address:"); 950 sb.append(Log.pii(getAddress())); 951 sb.append(" originalConnection:"); 952 sb.append(mOriginalConnection); 953 sb.append(" partOfConf:"); 954 if (getConference() == null) { 955 sb.append("N"); 956 } else { 957 sb.append("Y"); 958 } 959 sb.append("]"); 960 return sb.toString(); 961 } 962 } 963