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