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