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