TelephonyConnection.java revision 8a6eb45d1307630111713a07c5e2077a7d34ecbd
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 mOriginalConnectionExtras.clear(); 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 758 // Ensure extras are propagated to Telecom. 759 Bundle connectionExtras = getExtras(); 760 if (connectionExtras == null) { 761 connectionExtras = new Bundle(); 762 } 763 connectionExtras.putAll(mOriginalConnectionExtras); 764 setExtras(connectionExtras); 765 } else { 766 Log.d(this, "Extras update not required"); 767 } 768 } else { 769 Log.d(this, "updateExtras extras: " + Log.pii(extras)); 770 } 771 } 772 } 773 774 private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) { 775 if (extras == null || newExtras == null) { 776 return extras == newExtras; 777 } 778 779 if (extras.size() != newExtras.size()) { 780 return false; 781 } 782 783 for(String key : extras.keySet()) { 784 if (key != null) { 785 final Object value = extras.get(key); 786 final Object newValue = newExtras.get(key); 787 if (!Objects.equals(value, newValue)) { 788 return false; 789 } 790 } 791 } 792 return true; 793 } 794 795 void updateState() { 796 if (mOriginalConnection == null) { 797 return; 798 } 799 800 Call.State newState = mOriginalConnection.getState(); 801 Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this); 802 if (mOriginalConnectionState != newState) { 803 mOriginalConnectionState = newState; 804 switch (newState) { 805 case IDLE: 806 break; 807 case ACTIVE: 808 setActiveInternal(); 809 break; 810 case HOLDING: 811 setOnHold(); 812 break; 813 case DIALING: 814 case ALERTING: 815 setDialing(); 816 break; 817 case INCOMING: 818 case WAITING: 819 setRinging(); 820 break; 821 case DISCONNECTED: 822 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 823 mOriginalConnection.getDisconnectCause(), 824 mOriginalConnection.getVendorDisconnectCause())); 825 close(); 826 break; 827 case DISCONNECTING: 828 break; 829 } 830 } 831 updateStatusHints(); 832 updateConnectionCapabilities(); 833 updateAddress(); 834 updateMultiparty(); 835 updateExtras(); 836 } 837 838 /** 839 * Checks for changes to the multiparty bit. If a conference has started, informs listeners. 840 */ 841 private void updateMultiparty() { 842 if (mOriginalConnection == null) { 843 return; 844 } 845 846 if (mIsMultiParty != mOriginalConnection.isMultiparty()) { 847 mIsMultiParty = mOriginalConnection.isMultiparty(); 848 849 if (mIsMultiParty) { 850 notifyConferenceStarted(); 851 } 852 } 853 } 854 855 /** 856 * Handles a failure when merging calls into a conference. 857 * {@link com.android.internal.telephony.Connection.Listener#onConferenceMergedFailed()} 858 * listener. 859 */ 860 private void handleConferenceMergeFailed(){ 861 mHandler.obtainMessage(MSG_CONFERENCE_MERGE_FAILED).sendToTarget(); 862 } 863 864 /** 865 * Handles requests to update the multiparty state received via the 866 * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)} 867 * listener. 868 * <p> 869 * Note: We post this to the mHandler to ensure that if a conference must be created as a 870 * result of the multiparty state change, the conference creation happens on the correct 871 * thread. This ensures that the thread check in 872 * {@link com.android.internal.telephony.PhoneBase#checkCorrectThread(android.os.Handler)} 873 * does not fire. 874 * 875 * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise. 876 */ 877 private void handleMultipartyStateChange(boolean isMultiParty) { 878 Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N"); 879 mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget(); 880 } 881 882 private void setActiveInternal() { 883 if (getState() == STATE_ACTIVE) { 884 Log.w(this, "Should not be called if this is already ACTIVE"); 885 return; 886 } 887 888 // When we set a call to active, we need to make sure that there are no other active 889 // calls. However, the ordering of state updates to connections can be non-deterministic 890 // since all connections register for state changes on the phone independently. 891 // To "optimize", we check here to see if there already exists any active calls. If so, 892 // we issue an update for those calls first to make sure we only have one top-level 893 // active call. 894 if (getConnectionService() != null) { 895 for (Connection current : getConnectionService().getAllConnections()) { 896 if (current != this && current instanceof TelephonyConnection) { 897 TelephonyConnection other = (TelephonyConnection) current; 898 if (other.getState() == STATE_ACTIVE) { 899 other.updateState(); 900 } 901 } 902 } 903 } 904 setActive(); 905 } 906 907 private void close() { 908 Log.v(this, "close"); 909 clearOriginalConnection(); 910 destroy(); 911 } 912 913 /** 914 * Applies capabilities specific to conferences termination to the 915 * {@code CallCapabilities} bit-mask. 916 * 917 * @param capabilities The {@code CallCapabilities} bit-mask. 918 * @return The capabilities with the IMS conference capabilities applied. 919 */ 920 private int applyConferenceTerminationCapabilities(int capabilities) { 921 int currentCapabilities = capabilities; 922 923 // An IMS call cannot be individually disconnected or separated from its parent conference. 924 // If the call was IMS, even if it hands over to GMS, these capabilities are not supported. 925 if (!mWasImsConnection) { 926 currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE; 927 currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE; 928 } 929 930 return currentCapabilities; 931 } 932 933 /** 934 * Returns the local video capability state for the connection. 935 * 936 * @return {@code True} if the connection has local video capabilities. 937 */ 938 public boolean isLocalVideoCapable() { 939 return mLocalVideoCapable; 940 } 941 942 /** 943 * Returns the remote video capability state for the connection. 944 * 945 * @return {@code True} if the connection has remote video capabilities. 946 */ 947 public boolean isRemoteVideoCapable() { 948 return mRemoteVideoCapable; 949 } 950 951 /** 952 * Sets whether video capability is present locally. Used during rebuild of the 953 * capabilities to set the video call capabilities. 954 * 955 * @param capable {@code True} if video capable. 956 */ 957 public void setLocalVideoCapable(boolean capable) { 958 mLocalVideoCapable = capable; 959 updateConnectionCapabilities(); 960 } 961 962 /** 963 * Sets whether video capability is present remotely. Used during rebuild of the 964 * capabilities to set the video call capabilities. 965 * 966 * @param capable {@code True} if video capable. 967 */ 968 public void setRemoteVideoCapable(boolean capable) { 969 mRemoteVideoCapable = capable; 970 updateConnectionCapabilities(); 971 } 972 973 /** 974 * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset 975 * the {@link Connection#CAPABILITY_WIFI} capability. 976 */ 977 public void setWifi(boolean isWifi) { 978 mIsWifi = isWifi; 979 updateConnectionCapabilities(); 980 updateStatusHints(); 981 } 982 983 /** 984 * Whether the call is using wifi. 985 */ 986 boolean isWifi() { 987 return mIsWifi; 988 } 989 990 /** 991 * Sets the current call audio quality. Used during rebuild of the capabilities 992 * to set or unset the {@link Connection#CAPABILITY_HIGH_DEF_AUDIO} capability. 993 * 994 * @param audioQuality The audio quality. 995 */ 996 public void setAudioQuality(int audioQuality) { 997 mHasHighDefAudio = audioQuality == 998 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION; 999 updateConnectionCapabilities(); 1000 } 1001 1002 void resetStateForConference() { 1003 if (getState() == Connection.STATE_HOLDING) { 1004 if (mOriginalConnection.getState() == Call.State.ACTIVE) { 1005 setActive(); 1006 } 1007 } 1008 } 1009 1010 boolean setHoldingForConference() { 1011 if (getState() == Connection.STATE_ACTIVE) { 1012 setOnHold(); 1013 return true; 1014 } 1015 return false; 1016 } 1017 1018 /** 1019 * For video calls, sets whether this connection supports pausing the outgoing video for the 1020 * call using the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. 1021 * 1022 * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise. 1023 */ 1024 public void setVideoPauseSupported(boolean isVideoPauseSupported) { 1025 mIsVideoPauseSupported = isVideoPauseSupported; 1026 } 1027 1028 /** 1029 * Whether the original connection is an IMS connection. 1030 * @return {@code True} if the original connection is an IMS connection, {@code false} 1031 * otherwise. 1032 */ 1033 protected boolean isImsConnection() { 1034 return getOriginalConnection() instanceof ImsPhoneConnection; 1035 } 1036 1037 /** 1038 * Whether the original connection was ever an IMS connection, either before or now. 1039 * @return {@code True} if the original connection was ever an IMS connection, {@code false} 1040 * otherwise. 1041 */ 1042 public boolean wasImsConnection() { 1043 return mWasImsConnection; 1044 } 1045 1046 private static Uri getAddressFromNumber(String number) { 1047 // Address can be null for blocked calls. 1048 if (number == null) { 1049 number = ""; 1050 } 1051 return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 1052 } 1053 1054 /** 1055 * Changes a capabilities bit-mask to add or remove a capability. 1056 * 1057 * @param capabilities The capabilities bit-mask. 1058 * @param capability The capability to change. 1059 * @param enabled Whether the capability should be set or removed. 1060 * @return The capabilities bit-mask with the capability changed. 1061 */ 1062 private int changeCapability(int capabilities, int capability, boolean enabled) { 1063 if (enabled) { 1064 return capabilities | capability; 1065 } else { 1066 return capabilities & ~capability; 1067 } 1068 } 1069 1070 private void updateStatusHints() { 1071 boolean isIncoming = isValidRingingCall(); 1072 if (mIsWifi && (isIncoming || getState() == STATE_ACTIVE)) { 1073 int labelId = isIncoming 1074 ? R.string.status_hint_label_incoming_wifi_call 1075 : R.string.status_hint_label_wifi_call; 1076 1077 Context context = getPhone().getContext(); 1078 setStatusHints(new StatusHints( 1079 context.getString(labelId), 1080 Icon.createWithResource( 1081 context.getResources(), 1082 R.drawable.ic_signal_wifi_4_bar_24dp), 1083 null /* extras */)); 1084 } else { 1085 setStatusHints(null); 1086 } 1087 } 1088 1089 /** 1090 * Register a listener for {@link TelephonyConnection} specific triggers. 1091 * @param l The instance of the listener to add 1092 * @return The connection being listened to 1093 */ 1094 public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) { 1095 mTelephonyListeners.add(l); 1096 // If we already have an original connection, let's call back immediately. 1097 // This would be the case for incoming calls. 1098 if (mOriginalConnection != null) { 1099 fireOnOriginalConnectionConfigured(); 1100 } 1101 return this; 1102 } 1103 1104 /** 1105 * Remove a listener for {@link TelephonyConnection} specific triggers. 1106 * @param l The instance of the listener to remove 1107 * @return The connection being listened to 1108 */ 1109 public final TelephonyConnection removeTelephonyConnectionListener( 1110 TelephonyConnectionListener l) { 1111 if (l != null) { 1112 mTelephonyListeners.remove(l); 1113 } 1114 return this; 1115 } 1116 1117 /** 1118 * Fire a callback to the various listeners for when the original connection is 1119 * set in this {@link TelephonyConnection} 1120 */ 1121 private final void fireOnOriginalConnectionConfigured() { 1122 for (TelephonyConnectionListener l : mTelephonyListeners) { 1123 l.onOriginalConnectionConfigured(this); 1124 } 1125 } 1126 1127 /** 1128 * Creates a string representation of this {@link TelephonyConnection}. Primarily intended for 1129 * use in log statements. 1130 * 1131 * @return String representation of the connection. 1132 */ 1133 @Override 1134 public String toString() { 1135 StringBuilder sb = new StringBuilder(); 1136 sb.append("[TelephonyConnection objId:"); 1137 sb.append(System.identityHashCode(this)); 1138 sb.append(" type:"); 1139 if (isImsConnection()) { 1140 sb.append("ims"); 1141 } else if (this instanceof com.android.services.telephony.GsmConnection) { 1142 sb.append("gsm"); 1143 } else if (this instanceof CdmaConnection) { 1144 sb.append("cdma"); 1145 } 1146 sb.append(" state:"); 1147 sb.append(Connection.stateToString(getState())); 1148 sb.append(" capabilities:"); 1149 sb.append(capabilitiesToString(getConnectionCapabilities())); 1150 sb.append(" address:"); 1151 sb.append(Log.pii(getAddress())); 1152 sb.append(" originalConnection:"); 1153 sb.append(mOriginalConnection); 1154 sb.append(" partOfConf:"); 1155 if (getConference() == null) { 1156 sb.append("N"); 1157 } else { 1158 sb.append("Y"); 1159 } 1160 sb.append("]"); 1161 return sb.toString(); 1162 } 1163} 1164