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