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