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