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