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