TelephonyConnection.java revision c15b5ed2e5a259b4c83b5e87cd379c997e819643
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.services.telephony; 18 19import android.content.Context; 20import android.graphics.drawable.Icon; 21import android.net.Uri; 22import android.os.AsyncResult; 23import android.os.Bundle; 24import android.os.Handler; 25import android.os.Message; 26import android.os.PersistableBundle; 27import android.telecom.CallAudioState; 28import android.telecom.ConferenceParticipant; 29import android.telecom.Connection; 30import android.telecom.PhoneAccount; 31import android.telecom.PhoneAccountHandle; 32import android.telecom.StatusHints; 33import android.telecom.TelecomManager; 34import android.telecom.VideoProfile; 35import android.telephony.CarrierConfigManager; 36import android.telephony.DisconnectCause; 37import android.telephony.PhoneNumberUtils; 38import android.telephony.TelephonyManager; 39import android.util.Pair; 40 41import com.android.ims.ImsCall; 42import com.android.ims.ImsCallProfile; 43import com.android.internal.telephony.Call; 44import com.android.internal.telephony.CallStateException; 45import com.android.internal.telephony.Connection.Capability; 46import com.android.internal.telephony.Connection.PostDialListener; 47import com.android.internal.telephony.PhoneConstants; 48import com.android.internal.telephony.gsm.SuppServiceNotification; 49 50import com.android.internal.telephony.Phone; 51import com.android.internal.telephony.imsphone.ImsPhone; 52import com.android.internal.telephony.imsphone.ImsPhoneCallTracker; 53import com.android.internal.telephony.imsphone.ImsPhoneConnection; 54import com.android.phone.ImsUtil; 55import com.android.phone.PhoneGlobals; 56import com.android.phone.PhoneUtils; 57import com.android.phone.R; 58 59import java.lang.Override; 60import java.util.Arrays; 61import java.util.ArrayList; 62import java.util.Collections; 63import java.util.HashMap; 64import java.util.List; 65import java.util.Map; 66import java.util.Objects; 67import java.util.Set; 68import java.util.concurrent.ConcurrentHashMap; 69 70/** 71 * Base class for CDMA and GSM connections. 72 */ 73abstract class TelephonyConnection extends Connection { 74 private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1; 75 private static final int MSG_RINGBACK_TONE = 2; 76 private static final int MSG_HANDOVER_STATE_CHANGED = 3; 77 private static final int MSG_DISCONNECT = 4; 78 private static final int MSG_MULTIPARTY_STATE_CHANGED = 5; 79 private static final int MSG_CONFERENCE_MERGE_FAILED = 6; 80 private static final int MSG_SUPP_SERVICE_NOTIFY = 7; 81 82 /** 83 * Mappings from {@link com.android.internal.telephony.Connection} extras keys to their 84 * equivalents defined in {@link android.telecom.Connection}. 85 */ 86 private static final Map<String, String> sExtrasMap = createExtrasMap(); 87 88 private static final int MSG_SET_VIDEO_STATE = 8; 89 private static final int MSG_SET_VIDEO_PROVIDER = 9; 90 private static final int MSG_SET_AUDIO_QUALITY = 10; 91 private static final int MSG_SET_CONFERENCE_PARTICIPANTS = 11; 92 private static final int MSG_CONNECTION_EXTRAS_CHANGED = 12; 93 private static final int MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES = 13; 94 private static final int MSG_ON_HOLD_TONE = 14; 95 private static final int MSG_CDMA_VOICE_PRIVACY_ON = 15; 96 private static final int MSG_CDMA_VOICE_PRIVACY_OFF = 16; 97 98 private final Handler mHandler = new Handler() { 99 @Override 100 public void handleMessage(Message msg) { 101 switch (msg.what) { 102 case MSG_PRECISE_CALL_STATE_CHANGED: 103 Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED"); 104 updateState(); 105 break; 106 case MSG_HANDOVER_STATE_CHANGED: 107 Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED"); 108 AsyncResult ar = (AsyncResult) msg.obj; 109 com.android.internal.telephony.Connection connection = 110 (com.android.internal.telephony.Connection) ar.result; 111 if (mOriginalConnection != null) { 112 if (connection != null && 113 ((connection.getAddress() != null && 114 mOriginalConnection.getAddress() != null && 115 mOriginalConnection.getAddress().contains(connection.getAddress())) || 116 connection.getState() == mOriginalConnection.getStateBeforeHandover())) { 117 Log.d(TelephonyConnection.this, 118 "SettingOriginalConnection " + mOriginalConnection.toString() 119 + " with " + connection.toString()); 120 setOriginalConnection(connection); 121 mWasImsConnection = false; 122 } 123 } else { 124 Log.w(TelephonyConnection.this, 125 "MSG_HANDOVER_STATE_CHANGED: mOriginalConnection==null - invalid state (not cleaned up)"); 126 } 127 break; 128 case MSG_RINGBACK_TONE: 129 Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE"); 130 // TODO: This code assumes that there is only one connection in the foreground 131 // call, in other words, it punts on network-mediated conference calling. 132 if (getOriginalConnection() != getForegroundConnection()) { 133 Log.v(TelephonyConnection.this, "handleMessage, original connection is " + 134 "not foreground connection, skipping"); 135 return; 136 } 137 setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result); 138 break; 139 case MSG_DISCONNECT: 140 updateState(); 141 break; 142 case MSG_MULTIPARTY_STATE_CHANGED: 143 boolean isMultiParty = (Boolean) msg.obj; 144 Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N"); 145 mIsMultiParty = isMultiParty; 146 if (isMultiParty) { 147 notifyConferenceStarted(); 148 } 149 break; 150 case MSG_CONFERENCE_MERGE_FAILED: 151 notifyConferenceMergeFailed(); 152 break; 153 case MSG_SUPP_SERVICE_NOTIFY: 154 Log.v(TelephonyConnection.this, "MSG_SUPP_SERVICE_NOTIFY on phoneId : " 155 +getPhone().getPhoneId()); 156 SuppServiceNotification mSsNotification = null; 157 if (msg.obj != null && ((AsyncResult) msg.obj).result != null) { 158 mSsNotification = 159 (SuppServiceNotification)((AsyncResult) msg.obj).result; 160 if (mOriginalConnection != null && mSsNotification.history != null) { 161 Bundle lastForwardedNumber = new Bundle(); 162 Log.v(TelephonyConnection.this, 163 "Updating call history info in extras."); 164 lastForwardedNumber.putStringArrayList( 165 Connection.EXTRA_LAST_FORWARDED_NUMBER, 166 new ArrayList(Arrays.asList(mSsNotification.history))); 167 putExtras(lastForwardedNumber); 168 } 169 } 170 break; 171 172 case MSG_SET_VIDEO_STATE: 173 int videoState = (int) msg.obj; 174 setVideoState(videoState); 175 176 // A change to the video state of the call can influence whether or not it 177 // can be part of a conference, whether another call can be added, and 178 // whether the call should have the HD audio property set. 179 refreshConferenceSupported(); 180 refreshDisableAddCall(); 181 updateConnectionProperties(); 182 break; 183 184 case MSG_SET_VIDEO_PROVIDER: 185 VideoProvider videoProvider = (VideoProvider) msg.obj; 186 setVideoProvider(videoProvider); 187 break; 188 189 case MSG_SET_AUDIO_QUALITY: 190 int audioQuality = (int) msg.obj; 191 setAudioQuality(audioQuality); 192 break; 193 194 case MSG_SET_CONFERENCE_PARTICIPANTS: 195 List<ConferenceParticipant> participants = (List<ConferenceParticipant>) msg.obj; 196 updateConferenceParticipants(participants); 197 break; 198 199 case MSG_CONNECTION_EXTRAS_CHANGED: 200 final Bundle extras = (Bundle) msg.obj; 201 updateExtras(extras); 202 break; 203 204 case MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES: 205 setOriginalConnectionCapabilities(msg.arg1); 206 break; 207 208 case MSG_ON_HOLD_TONE: 209 AsyncResult asyncResult = (AsyncResult) msg.obj; 210 Pair<com.android.internal.telephony.Connection, Boolean> heldInfo = 211 (Pair<com.android.internal.telephony.Connection, Boolean>) 212 asyncResult.result; 213 214 // Determines if the hold tone is starting or stopping. 215 boolean playTone = ((Boolean) (heldInfo.second)).booleanValue(); 216 217 // Determine which connection the hold tone is stopping or starting for 218 com.android.internal.telephony.Connection heldConnection = heldInfo.first; 219 220 // Only start or stop the hold tone if this is the connection which is starting 221 // or stopping the hold tone. 222 if (heldConnection == mOriginalConnection) { 223 // If starting the hold tone, send a connection event to Telecom which will 224 // cause it to play the on hold tone. 225 if (playTone) { 226 sendConnectionEvent(EVENT_ON_HOLD_TONE_START, null); 227 } else { 228 sendConnectionEvent(EVENT_ON_HOLD_TONE_END, null); 229 } 230 } 231 break; 232 233 case MSG_CDMA_VOICE_PRIVACY_ON: 234 Log.d(this, "MSG_CDMA_VOICE_PRIVACY_ON received"); 235 setCdmaVoicePrivacy(true); 236 break; 237 case MSG_CDMA_VOICE_PRIVACY_OFF: 238 Log.d(this, "MSG_CDMA_VOICE_PRIVACY_OFF received"); 239 setCdmaVoicePrivacy(false); 240 break; 241 } 242 } 243 }; 244 245 /** 246 * @return {@code true} if carrier video conferencing is supported, {@code false} otherwise. 247 */ 248 public boolean isCarrierVideoConferencingSupported() { 249 return mIsCarrierVideoConferencingSupported; 250 } 251 252 /** 253 * A listener/callback mechanism that is specific communication from TelephonyConnections 254 * to TelephonyConnectionService (for now). It is more specific that Connection.Listener 255 * because it is only exposed in Telephony. 256 */ 257 public abstract static class TelephonyConnectionListener { 258 public void onOriginalConnectionConfigured(TelephonyConnection c) {} 259 public void onOriginalConnectionRetry(TelephonyConnection c) {} 260 } 261 262 private final PostDialListener mPostDialListener = new PostDialListener() { 263 @Override 264 public void onPostDialWait() { 265 Log.v(TelephonyConnection.this, "onPostDialWait"); 266 if (mOriginalConnection != null) { 267 setPostDialWait(mOriginalConnection.getRemainingPostDialString()); 268 } 269 } 270 271 @Override 272 public void onPostDialChar(char c) { 273 Log.v(TelephonyConnection.this, "onPostDialChar: %s", c); 274 if (mOriginalConnection != null) { 275 setNextPostDialChar(c); 276 } 277 } 278 }; 279 280 /** 281 * Listener for listening to events in the {@link com.android.internal.telephony.Connection}. 282 */ 283 private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener = 284 new com.android.internal.telephony.Connection.ListenerBase() { 285 @Override 286 public void onVideoStateChanged(int videoState) { 287 mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState).sendToTarget(); 288 } 289 290 /* 291 * The {@link com.android.internal.telephony.Connection} has reported a change in 292 * connection capability. 293 * @param capabilities bit mask containing voice or video or both capabilities. 294 */ 295 @Override 296 public void onConnectionCapabilitiesChanged(int capabilities) { 297 mHandler.obtainMessage(MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES, 298 capabilities, 0).sendToTarget(); 299 } 300 301 /** 302 * The {@link com.android.internal.telephony.Connection} has reported a change in the 303 * video call provider. 304 * 305 * @param videoProvider The video call provider. 306 */ 307 @Override 308 public void onVideoProviderChanged(VideoProvider videoProvider) { 309 mHandler.obtainMessage(MSG_SET_VIDEO_PROVIDER, videoProvider).sendToTarget(); 310 } 311 312 /** 313 * Used by {@link com.android.internal.telephony.Connection} to report a change in whether 314 * the call is being made over a wifi network. 315 * 316 * @param isWifi True if call is made over wifi. 317 */ 318 @Override 319 public void onWifiChanged(boolean isWifi) { 320 setWifi(isWifi); 321 } 322 323 /** 324 * Used by the {@link com.android.internal.telephony.Connection} to report a change in the 325 * audio quality for the current call. 326 * 327 * @param audioQuality The audio quality. 328 */ 329 @Override 330 public void onAudioQualityChanged(int audioQuality) { 331 mHandler.obtainMessage(MSG_SET_AUDIO_QUALITY, audioQuality).sendToTarget(); 332 } 333 /** 334 * Handles a change in the state of conference participant(s), as reported by the 335 * {@link com.android.internal.telephony.Connection}. 336 * 337 * @param participants The participant(s) which changed. 338 */ 339 @Override 340 public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) { 341 mHandler.obtainMessage(MSG_SET_CONFERENCE_PARTICIPANTS, participants).sendToTarget(); 342 } 343 344 /* 345 * Handles a change to the multiparty state for this connection. 346 * 347 * @param isMultiParty {@code true} if the call became multiparty, {@code false} 348 * otherwise. 349 */ 350 @Override 351 public void onMultipartyStateChanged(boolean isMultiParty) { 352 handleMultipartyStateChange(isMultiParty); 353 } 354 355 /** 356 * Handles the event that the request to merge calls failed. 357 */ 358 @Override 359 public void onConferenceMergedFailed() { 360 handleConferenceMergeFailed(); 361 } 362 363 @Override 364 public void onExtrasChanged(Bundle extras) { 365 mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, extras).sendToTarget(); 366 } 367 368 /** 369 * Handles the phone exiting ECM mode by updating the connection capabilities. During an 370 * ongoing call, if ECM mode is exited, we will re-enable mute for CDMA calls. 371 */ 372 @Override 373 public void onExitedEcmMode() { 374 handleExitedEcmMode(); 375 } 376 377 /** 378 * Called from {@link ImsPhoneCallTracker} when a request to pull an external call has 379 * failed. 380 * @param externalConnection 381 */ 382 @Override 383 public void onCallPullFailed(com.android.internal.telephony.Connection externalConnection) { 384 if (externalConnection == null) { 385 return; 386 } 387 388 Log.i(this, "onCallPullFailed - pull failed; swapping back to call: %s", 389 externalConnection); 390 391 // Inform the InCallService of the fact that the call pull failed (it may choose to 392 // display a message informing the user of the pull failure). 393 sendConnectionEvent(Connection.EVENT_CALL_PULL_FAILED, null); 394 395 // Swap the ImsPhoneConnection we used to do the pull for the ImsExternalConnection 396 // which originally represented the call. 397 setOriginalConnection(externalConnection); 398 399 // Set our state to active again since we're no longer pulling. 400 setActiveInternal(); 401 } 402 403 /** 404 * Called from {@link ImsPhoneCallTracker} when a handover to WIFI has failed. 405 */ 406 @Override 407 public void onHandoverToWifiFailed() { 408 sendConnectionEvent(TelephonyManager.EVENT_HANDOVER_TO_WIFI_FAILED, null); 409 } 410 411 /** 412 * Informs the {@link android.telecom.ConnectionService} of a connection event raised by the 413 * original connection. 414 * @param event The connection event. 415 * @param extras The extras. 416 */ 417 @Override 418 public void onConnectionEvent(String event, Bundle extras) { 419 sendConnectionEvent(event, extras); 420 } 421 422 @Override 423 public void onRttModifyRequestReceived() { 424 sendRemoteRttRequest(); 425 } 426 427 @Override 428 public void onRttModifyResponseReceived(int status) { 429 if (status == RttModifyStatus.SESSION_MODIFY_REQUEST_SUCCESS) { 430 sendRttInitiationSuccess(); 431 } else { 432 sendRttInitiationFailure(status); 433 } 434 } 435 }; 436 437 protected com.android.internal.telephony.Connection mOriginalConnection; 438 private Call.State mConnectionState = Call.State.IDLE; 439 private Bundle mOriginalConnectionExtras = new Bundle(); 440 private boolean mIsStateOverridden = false; 441 private Call.State mOriginalConnectionState = Call.State.IDLE; 442 private Call.State mConnectionOverriddenState = Call.State.IDLE; 443 444 private boolean mWasImsConnection; 445 446 /** 447 * Tracks the multiparty state of the ImsCall so that changes in the bit state can be detected. 448 */ 449 private boolean mIsMultiParty = false; 450 451 /** 452 * The {@link com.android.internal.telephony.Connection} capabilities associated with the 453 * current {@link #mOriginalConnection}. 454 */ 455 private int mOriginalConnectionCapabilities; 456 457 /** 458 * Determines if the {@link TelephonyConnection} is using wifi. 459 * This is used when {@link TelephonyConnection#updateConnectionProperties()} is called to 460 * indicate whether a call has the {@link Connection#PROPERTY_WIFI} property. 461 */ 462 private boolean mIsWifi; 463 464 /** 465 * Determines the audio quality is high for the {@link TelephonyConnection}. 466 * This is used when {@link TelephonyConnection#updateConnectionProperties}} is called to 467 * indicate whether a call has the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property. 468 */ 469 private boolean mHasHighDefAudio; 470 471 /** 472 * Indicates that the connection should be treated as an emergency call because the 473 * number dialed matches an internal list of emergency numbers. Does not guarantee whether 474 * the network will treat the call as an emergency call. 475 */ 476 private boolean mTreatAsEmergencyCall; 477 478 /** 479 * For video calls, indicates whether the outgoing video for the call can be paused using 480 * the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. 481 */ 482 private boolean mIsVideoPauseSupported; 483 484 /** 485 * Indicates whether this connection supports being a part of a conference.. 486 */ 487 private boolean mIsConferenceSupported; 488 489 /** 490 * Indicates whether the carrier supports video conferencing; captures the current state of the 491 * carrier config 492 * {@link android.telephony.CarrierConfigManager#KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL}. 493 */ 494 private boolean mIsCarrierVideoConferencingSupported; 495 496 /** 497 * Indicates whether or not this connection has CDMA Enhanced Voice Privacy enabled. 498 */ 499 private boolean mIsCdmaVoicePrivacyEnabled; 500 501 /** 502 * Listeners to our TelephonyConnection specific callbacks 503 */ 504 private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap( 505 new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1)); 506 507 protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection, 508 String callId) { 509 setTelecomCallId(callId); 510 if (originalConnection != null) { 511 setOriginalConnection(originalConnection); 512 } 513 } 514 515 /** 516 * Creates a clone of the current {@link TelephonyConnection}. 517 * 518 * @return The clone. 519 */ 520 public abstract TelephonyConnection cloneConnection(); 521 522 @Override 523 public void onCallAudioStateChanged(CallAudioState audioState) { 524 // TODO: update TTY mode. 525 if (getPhone() != null) { 526 getPhone().setEchoSuppressionEnabled(); 527 } 528 } 529 530 @Override 531 public void onStateChanged(int state) { 532 Log.v(this, "onStateChanged, state: " + Connection.stateToString(state)); 533 updateStatusHints(); 534 } 535 536 @Override 537 public void onDisconnect() { 538 Log.v(this, "onDisconnect"); 539 hangup(android.telephony.DisconnectCause.LOCAL); 540 } 541 542 /** 543 * Notifies this Connection of a request to disconnect a participant of the conference managed 544 * by the connection. 545 * 546 * @param endpoint the {@link Uri} of the participant to disconnect. 547 */ 548 @Override 549 public void onDisconnectConferenceParticipant(Uri endpoint) { 550 Log.v(this, "onDisconnectConferenceParticipant %s", endpoint); 551 552 if (mOriginalConnection == null) { 553 return; 554 } 555 556 mOriginalConnection.onDisconnectConferenceParticipant(endpoint); 557 } 558 559 @Override 560 public void onSeparate() { 561 Log.v(this, "onSeparate"); 562 if (mOriginalConnection != null) { 563 try { 564 mOriginalConnection.separate(); 565 } catch (CallStateException e) { 566 Log.e(this, e, "Call to Connection.separate failed with exception"); 567 } 568 } 569 } 570 571 @Override 572 public void onAbort() { 573 Log.v(this, "onAbort"); 574 hangup(android.telephony.DisconnectCause.LOCAL); 575 } 576 577 @Override 578 public void onHold() { 579 performHold(); 580 } 581 582 @Override 583 public void onUnhold() { 584 performUnhold(); 585 } 586 587 @Override 588 public void onAnswer(int videoState) { 589 Log.v(this, "onAnswer"); 590 if (isValidRingingCall() && getPhone() != null) { 591 try { 592 getPhone().acceptCall(videoState); 593 } catch (CallStateException e) { 594 Log.e(this, e, "Failed to accept call."); 595 } 596 } 597 } 598 599 @Override 600 public void onReject() { 601 Log.v(this, "onReject"); 602 if (isValidRingingCall()) { 603 hangup(android.telephony.DisconnectCause.INCOMING_REJECTED); 604 } 605 super.onReject(); 606 } 607 608 @Override 609 public void onPostDialContinue(boolean proceed) { 610 Log.v(this, "onPostDialContinue, proceed: " + proceed); 611 if (mOriginalConnection != null) { 612 if (proceed) { 613 mOriginalConnection.proceedAfterWaitChar(); 614 } else { 615 mOriginalConnection.cancelPostDial(); 616 } 617 } 618 } 619 620 /** 621 * Handles requests to pull an external call. 622 */ 623 @Override 624 public void onPullExternalCall() { 625 if ((getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) != 626 Connection.PROPERTY_IS_EXTERNAL_CALL) { 627 Log.w(this, "onPullExternalCall - cannot pull non-external call"); 628 return; 629 } 630 631 if (mOriginalConnection != null) { 632 mOriginalConnection.pullExternalCall(); 633 } 634 } 635 636 @Override 637 public void onStartRtt(RttTextStream textStream) { 638 if (isImsConnection()) { 639 ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection; 640 originalConnection.sendRttModifyRequest(textStream); 641 } else { 642 Log.w(this, "onStartRtt - not in IMS, so RTT cannot be enabled."); 643 } 644 } 645 646 @Override 647 public void onStopRtt() { 648 // This is not supported by carriers/vendor yet. No-op for now. 649 } 650 651 @Override 652 public void handleRttUpgradeResponse(RttTextStream textStream) { 653 if (!isImsConnection()) { 654 Log.w(this, "handleRttUpgradeResponse - not in IMS, so RTT cannot be enabled."); 655 return; 656 } 657 ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection; 658 originalConnection.sendRttModifyResponse(textStream); 659 } 660 661 public void performHold() { 662 Log.v(this, "performHold"); 663 // TODO: Can dialing calls be put on hold as well since they take up the 664 // foreground call slot? 665 if (Call.State.ACTIVE == mConnectionState) { 666 Log.v(this, "Holding active call"); 667 try { 668 Phone phone = mOriginalConnection.getCall().getPhone(); 669 Call ringingCall = phone.getRingingCall(); 670 671 // Although the method says switchHoldingAndActive, it eventually calls a RIL method 672 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put 673 // a call on hold while a call-waiting call exists, it'll end up accepting the 674 // call-waiting call, which is bad if that was not the user's intention. We are 675 // cheating here and simply skipping it because we know any attempt to hold a call 676 // while a call-waiting call is happening is likely a request from Telecom prior to 677 // accepting the call-waiting call. 678 // TODO: Investigate a better solution. It would be great here if we 679 // could "fake" hold by silencing the audio and microphone streams for this call 680 // instead of actually putting it on hold. 681 if (ringingCall.getState() != Call.State.WAITING) { 682 phone.switchHoldingAndActive(); 683 } 684 685 // TODO: Cdma calls are slightly different. 686 } catch (CallStateException e) { 687 Log.e(this, e, "Exception occurred while trying to put call on hold."); 688 } 689 } else { 690 Log.w(this, "Cannot put a call that is not currently active on hold."); 691 } 692 } 693 694 public void performUnhold() { 695 Log.v(this, "performUnhold"); 696 if (Call.State.HOLDING == mConnectionState) { 697 try { 698 // Here's the deal--Telephony hold/unhold is weird because whenever there exists 699 // more than one call, one of them must always be active. In other words, if you 700 // have an active call and holding call, and you put the active call on hold, it 701 // will automatically activate the holding call. This is weird with how Telecom 702 // sends its commands. When a user opts to "unhold" a background call, telecom 703 // issues hold commands to all active calls, and then the unhold command to the 704 // background call. This means that we get two commands...each of which reduces to 705 // switchHoldingAndActive(). The result is that they simply cancel each other out. 706 // To fix this so that it works well with telecom we add a minor hack. If we 707 // have one telephony call, everything works as normally expected. But if we have 708 // two or more calls, we will ignore all requests to "unhold" knowing that the hold 709 // requests already do what we want. If you've read up to this point, I'm very sorry 710 // that we are doing this. I didn't think of a better solution that wouldn't also 711 // make the Telecom APIs very ugly. 712 713 if (!hasMultipleTopLevelCalls()) { 714 mOriginalConnection.getCall().getPhone().switchHoldingAndActive(); 715 } else { 716 Log.i(this, "Skipping unhold command for %s", this); 717 } 718 } catch (CallStateException e) { 719 Log.e(this, e, "Exception occurred while trying to release call from hold."); 720 } 721 } else { 722 Log.w(this, "Cannot release a call that is not already on hold from hold."); 723 } 724 } 725 726 public void performConference(Connection otherConnection) { 727 Log.d(this, "performConference - %s", this); 728 if (getPhone() != null) { 729 try { 730 // We dont use the "other" connection because there is no concept of that in the 731 // implementation of calls inside telephony. Basically, you can "conference" and it 732 // will conference with the background call. We know that otherConnection is the 733 // background call because it would never have called setConferenceableConnections() 734 // otherwise. 735 getPhone().conference(); 736 } catch (CallStateException e) { 737 Log.e(this, e, "Failed to conference call."); 738 } 739 } 740 } 741 742 /** 743 * Builds connection capabilities common to all TelephonyConnections. Namely, apply IMS-based 744 * capabilities. 745 */ 746 protected int buildConnectionCapabilities() { 747 int callCapabilities = 0; 748 if (mOriginalConnection != null && mOriginalConnection.isIncoming()) { 749 callCapabilities |= CAPABILITY_SPEED_UP_MT_AUDIO; 750 } 751 if (!shouldTreatAsEmergencyCall() && isImsConnection() && canHoldImsCalls()) { 752 callCapabilities |= CAPABILITY_SUPPORT_HOLD; 753 if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) { 754 callCapabilities |= CAPABILITY_HOLD; 755 } 756 } 757 758 return callCapabilities; 759 } 760 761 protected final void updateConnectionCapabilities() { 762 int newCapabilities = buildConnectionCapabilities(); 763 764 newCapabilities = applyOriginalConnectionCapabilities(newCapabilities); 765 newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PAUSE_VIDEO, 766 mIsVideoPauseSupported && isVideoCapable()); 767 newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PULL_CALL, 768 isExternalConnection() && isPullable()); 769 newCapabilities = applyConferenceTerminationCapabilities(newCapabilities); 770 771 if (getConnectionCapabilities() != newCapabilities) { 772 setConnectionCapabilities(newCapabilities); 773 } 774 } 775 776 protected int buildConnectionProperties() { 777 int connectionProperties = 0; 778 779 // If the phone is in ECM mode, mark the call to indicate that the callback number should be 780 // shown. 781 Phone phone = getPhone(); 782 if (phone != null && phone.isInEcm()) { 783 connectionProperties |= PROPERTY_EMERGENCY_CALLBACK_MODE; 784 } 785 786 return connectionProperties; 787 } 788 789 /** 790 * Updates the properties of the connection. 791 */ 792 protected final void updateConnectionProperties() { 793 int newProperties = buildConnectionProperties(); 794 795 newProperties = changeBitmask(newProperties, PROPERTY_HIGH_DEF_AUDIO, 796 hasHighDefAudioProperty()); 797 newProperties = changeBitmask(newProperties, PROPERTY_WIFI, mIsWifi); 798 newProperties = changeBitmask(newProperties, PROPERTY_IS_EXTERNAL_CALL, 799 isExternalConnection()); 800 newProperties = changeBitmask(newProperties, PROPERTY_HAS_CDMA_VOICE_PRIVACY, 801 mIsCdmaVoicePrivacyEnabled); 802 803 if (getConnectionProperties() != newProperties) { 804 setConnectionProperties(newProperties); 805 } 806 } 807 808 protected final void updateAddress() { 809 updateConnectionCapabilities(); 810 updateConnectionProperties(); 811 if (mOriginalConnection != null) { 812 Uri address = getAddressFromNumber(mOriginalConnection.getAddress()); 813 int presentation = mOriginalConnection.getNumberPresentation(); 814 if (!Objects.equals(address, getAddress()) || 815 presentation != getAddressPresentation()) { 816 Log.v(this, "updateAddress, address changed"); 817 if ((getConnectionProperties() & PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0) { 818 address = null; 819 } 820 setAddress(address, presentation); 821 } 822 823 String name = filterCnapName(mOriginalConnection.getCnapName()); 824 int namePresentation = mOriginalConnection.getCnapNamePresentation(); 825 if (!Objects.equals(name, getCallerDisplayName()) || 826 namePresentation != getCallerDisplayNamePresentation()) { 827 Log.v(this, "updateAddress, caller display name changed"); 828 setCallerDisplayName(name, namePresentation); 829 } 830 831 if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) { 832 mTreatAsEmergencyCall = true; 833 } 834 835 // Changing the address of the connection can change whether it is an emergency call or 836 // not, which can impact whether it can be part of a conference. 837 refreshConferenceSupported(); 838 } 839 } 840 841 void onRemovedFromCallService() { 842 // Subclass can override this to do cleanup. 843 } 844 845 void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) { 846 Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); 847 clearOriginalConnection(); 848 mOriginalConnectionExtras.clear(); 849 mOriginalConnection = originalConnection; 850 mOriginalConnection.setTelecomCallId(getTelecomCallId()); 851 getPhone().registerForPreciseCallStateChanged( 852 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null); 853 getPhone().registerForHandoverStateChanged( 854 mHandler, MSG_HANDOVER_STATE_CHANGED, null); 855 getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null); 856 getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null); 857 getPhone().registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null); 858 getPhone().registerForOnHoldTone(mHandler, MSG_ON_HOLD_TONE, null); 859 getPhone().registerForInCallVoicePrivacyOn(mHandler, MSG_CDMA_VOICE_PRIVACY_ON, null); 860 getPhone().registerForInCallVoicePrivacyOff(mHandler, MSG_CDMA_VOICE_PRIVACY_OFF, null); 861 mOriginalConnection.addPostDialListener(mPostDialListener); 862 mOriginalConnection.addListener(mOriginalConnectionListener); 863 864 // Set video state and capabilities 865 setVideoState(mOriginalConnection.getVideoState()); 866 setOriginalConnectionCapabilities(mOriginalConnection.getConnectionCapabilities()); 867 setWifi(mOriginalConnection.isWifi()); 868 setAudioModeIsVoip(mOriginalConnection.getAudioModeIsVoip()); 869 setVideoProvider(mOriginalConnection.getVideoProvider()); 870 setAudioQuality(mOriginalConnection.getAudioQuality()); 871 setTechnologyTypeExtra(); 872 873 // Post update of extras to the handler; extras are updated via the handler to ensure thread 874 // safety. The Extras Bundle is cloned in case the original extras are modified while they 875 // are being added to mOriginalConnectionExtras in updateExtras. 876 Bundle connExtras = mOriginalConnection.getConnectionExtras(); 877 mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, connExtras == null ? null : 878 new Bundle(connExtras)).sendToTarget(); 879 880 if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) { 881 mTreatAsEmergencyCall = true; 882 } 883 884 if (isImsConnection()) { 885 mWasImsConnection = true; 886 } 887 mIsMultiParty = mOriginalConnection.isMultiparty(); 888 889 Bundle extrasToPut = new Bundle(); 890 List<String> extrasToRemove = new ArrayList<>(); 891 if (mOriginalConnection.isActiveCallDisconnectedOnAnswer()) { 892 extrasToPut.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true); 893 } else { 894 extrasToRemove.add(Connection.EXTRA_ANSWERING_DROPS_FG_CALL); 895 } 896 897 if (shouldSetDisableAddCallExtra()) { 898 extrasToPut.putBoolean(Connection.EXTRA_DISABLE_ADD_CALL, true); 899 } else { 900 extrasToRemove.add(Connection.EXTRA_DISABLE_ADD_CALL); 901 } 902 putExtras(extrasToPut); 903 removeExtras(extrasToRemove); 904 905 // updateState can set mOriginalConnection to null if its state is DISCONNECTED, so this 906 // should be executed *after* the above setters have run. 907 updateState(); 908 if (mOriginalConnection == null) { 909 Log.w(this, "original Connection was nulled out as part of setOriginalConnection. " + 910 originalConnection); 911 } 912 913 fireOnOriginalConnectionConfigured(); 914 } 915 916 /** 917 * Filters the CNAP name to not include a list of names that are unhelpful to the user for 918 * Caller ID purposes. 919 */ 920 private String filterCnapName(final String cnapName) { 921 if (cnapName == null) { 922 return null; 923 } 924 PersistableBundle carrierConfig = getCarrierConfig(); 925 String[] filteredCnapNames = null; 926 if (carrierConfig != null) { 927 filteredCnapNames = carrierConfig.getStringArray( 928 CarrierConfigManager.KEY_FILTERED_CNAP_NAMES_STRING_ARRAY); 929 } 930 if (filteredCnapNames != null) { 931 long cnapNameMatches = Arrays.asList(filteredCnapNames) 932 .stream() 933 .filter(filteredCnapName -> filteredCnapName.equals(cnapName.toUpperCase())) 934 .count(); 935 if (cnapNameMatches > 0) { 936 Log.i(this, "filterCnapName: Filtered CNAP Name: " + cnapName); 937 return ""; 938 } 939 } 940 return cnapName; 941 } 942 943 /** 944 * Sets the EXTRA_CALL_TECHNOLOGY_TYPE extra on the connection to report back to Telecom. 945 */ 946 private void setTechnologyTypeExtra() { 947 if (getPhone() != null) { 948 putExtra(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, getPhone().getPhoneType()); 949 } 950 } 951 952 private void refreshDisableAddCall() { 953 if (shouldSetDisableAddCallExtra()) { 954 putExtra(Connection.EXTRA_DISABLE_ADD_CALL, true); 955 } else { 956 removeExtras(Connection.EXTRA_DISABLE_ADD_CALL); 957 } 958 } 959 960 private boolean shouldSetDisableAddCallExtra() { 961 boolean carrierShouldAllowAddCall = mOriginalConnection.shouldAllowAddCallDuringVideoCall(); 962 if (carrierShouldAllowAddCall) { 963 return false; 964 } 965 Phone phone = getPhone(); 966 if (phone == null) { 967 return false; 968 } 969 boolean isCurrentVideoCall = false; 970 boolean wasVideoCall = false; 971 boolean isVowifiEnabled = false; 972 if (phone instanceof ImsPhone) { 973 ImsPhone imsPhone = (ImsPhone) phone; 974 if (imsPhone.getForegroundCall() != null 975 && imsPhone.getForegroundCall().getImsCall() != null) { 976 ImsCall call = imsPhone.getForegroundCall().getImsCall(); 977 isCurrentVideoCall = call.isVideoCall(); 978 wasVideoCall = call.wasVideoCall(); 979 } 980 981 isVowifiEnabled = ImsUtil.isWfcEnabled(phone.getContext()); 982 } 983 984 if (isCurrentVideoCall) { 985 return true; 986 } else if (wasVideoCall && mIsWifi && !isVowifiEnabled) { 987 return true; 988 } 989 return false; 990 } 991 992 private boolean hasHighDefAudioProperty() { 993 if (!mHasHighDefAudio) { 994 return false; 995 } 996 997 boolean isVideoCall = VideoProfile.isVideo(getVideoState()); 998 999 PersistableBundle b = getCarrierConfig(); 1000 boolean canWifiCallsBeHdAudio = 1001 b != null && b.getBoolean(CarrierConfigManager.KEY_WIFI_CALLS_CAN_BE_HD_AUDIO); 1002 boolean canVideoCallsBeHdAudio = 1003 b != null && b.getBoolean(CarrierConfigManager.KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO); 1004 boolean shouldDisplayHdAudio = 1005 b != null && b.getBoolean(CarrierConfigManager.KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL); 1006 1007 if (!shouldDisplayHdAudio) { 1008 return false; 1009 } 1010 1011 if (isVideoCall && !canVideoCallsBeHdAudio) { 1012 return false; 1013 } 1014 1015 if (mIsWifi && !canWifiCallsBeHdAudio) { 1016 return false; 1017 } 1018 1019 return true; 1020 } 1021 1022 private boolean canHoldImsCalls() { 1023 PersistableBundle b = getCarrierConfig(); 1024 // Return true if the CarrierConfig is unavailable 1025 return !doesDeviceRespectHoldCarrierConfig() || b == null || 1026 b.getBoolean(CarrierConfigManager.KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL); 1027 } 1028 1029 private PersistableBundle getCarrierConfig() { 1030 Phone phone = getPhone(); 1031 if (phone == null) { 1032 return null; 1033 } 1034 return PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId()); 1035 } 1036 1037 /** 1038 * Determines if the device will respect the value of the 1039 * {@link CarrierConfigManager#KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL} configuration option. 1040 * 1041 * @return {@code false} if the device always supports holding IMS calls, {@code true} if it 1042 * will use {@link CarrierConfigManager#KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL} to determine if 1043 * hold is supported. 1044 */ 1045 private boolean doesDeviceRespectHoldCarrierConfig() { 1046 Phone phone = getPhone(); 1047 if (phone == null) { 1048 return true; 1049 } 1050 return phone.getContext().getResources().getBoolean( 1051 com.android.internal.R.bool.config_device_respects_hold_carrier_config); 1052 } 1053 1054 /** 1055 * Whether the connection should be treated as an emergency. 1056 * @return {@code true} if the connection should be treated as an emergency call based 1057 * on the number dialed, {@code false} otherwise. 1058 */ 1059 protected boolean shouldTreatAsEmergencyCall() { 1060 return mTreatAsEmergencyCall; 1061 } 1062 1063 /** 1064 * Un-sets the underlying radio connection. 1065 */ 1066 void clearOriginalConnection() { 1067 if (mOriginalConnection != null) { 1068 if (getPhone() != null) { 1069 getPhone().unregisterForPreciseCallStateChanged(mHandler); 1070 getPhone().unregisterForRingbackTone(mHandler); 1071 getPhone().unregisterForHandoverStateChanged(mHandler); 1072 getPhone().unregisterForDisconnect(mHandler); 1073 getPhone().unregisterForSuppServiceNotification(mHandler); 1074 getPhone().unregisterForOnHoldTone(mHandler); 1075 getPhone().unregisterForInCallVoicePrivacyOn(mHandler); 1076 getPhone().unregisterForInCallVoicePrivacyOff(mHandler); 1077 } 1078 mOriginalConnection.removePostDialListener(mPostDialListener); 1079 mOriginalConnection.removeListener(mOriginalConnectionListener); 1080 mOriginalConnection = null; 1081 } 1082 } 1083 1084 protected void hangup(int telephonyDisconnectCode) { 1085 if (mOriginalConnection != null) { 1086 try { 1087 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to 1088 // connection.hangup(). Without this change, the party originating the call will not 1089 // get sent to voicemail if the user opts to reject the call. 1090 if (isValidRingingCall()) { 1091 Call call = getCall(); 1092 if (call != null) { 1093 call.hangup(); 1094 } else { 1095 Log.w(this, "Attempting to hangup a connection without backing call."); 1096 } 1097 } else { 1098 // We still prefer to call connection.hangup() for non-ringing calls in order 1099 // to support hanging-up specific calls within a conference call. If we invoked 1100 // call.hangup() while in a conference, we would end up hanging up the entire 1101 // conference call instead of the specific connection. 1102 mOriginalConnection.hangup(); 1103 } 1104 } catch (CallStateException e) { 1105 Log.e(this, e, "Call to Connection.hangup failed with exception"); 1106 } 1107 } else { 1108 if (getState() == STATE_DISCONNECTED) { 1109 Log.i(this, "hangup called on an already disconnected call!"); 1110 close(); 1111 } else { 1112 // There are a few cases where mOriginalConnection has not been set yet. For 1113 // example, when the radio has to be turned on to make an emergency call, 1114 // mOriginalConnection could not be set for many seconds. 1115 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 1116 android.telephony.DisconnectCause.LOCAL, 1117 "Local Disconnect before connection established.")); 1118 close(); 1119 } 1120 } 1121 } 1122 1123 com.android.internal.telephony.Connection getOriginalConnection() { 1124 return mOriginalConnection; 1125 } 1126 1127 protected Call getCall() { 1128 if (mOriginalConnection != null) { 1129 return mOriginalConnection.getCall(); 1130 } 1131 return null; 1132 } 1133 1134 Phone getPhone() { 1135 Call call = getCall(); 1136 if (call != null) { 1137 return call.getPhone(); 1138 } 1139 return null; 1140 } 1141 1142 private boolean hasMultipleTopLevelCalls() { 1143 int numCalls = 0; 1144 Phone phone = getPhone(); 1145 if (phone != null) { 1146 if (!phone.getRingingCall().isIdle()) { 1147 numCalls++; 1148 } 1149 if (!phone.getForegroundCall().isIdle()) { 1150 numCalls++; 1151 } 1152 if (!phone.getBackgroundCall().isIdle()) { 1153 numCalls++; 1154 } 1155 } 1156 return numCalls > 1; 1157 } 1158 1159 private com.android.internal.telephony.Connection getForegroundConnection() { 1160 if (getPhone() != null) { 1161 return getPhone().getForegroundCall().getEarliestConnection(); 1162 } 1163 return null; 1164 } 1165 1166 /** 1167 * Checks for and returns the list of conference participants 1168 * associated with this connection. 1169 */ 1170 public List<ConferenceParticipant> getConferenceParticipants() { 1171 if (mOriginalConnection == null) { 1172 Log.v(this, "Null mOriginalConnection, cannot get conf participants."); 1173 return null; 1174 } 1175 return mOriginalConnection.getConferenceParticipants(); 1176 } 1177 1178 /** 1179 * Checks to see the original connection corresponds to an active incoming call. Returns false 1180 * if there is no such actual call, or if the associated call is not incoming (See 1181 * {@link Call.State#isRinging}). 1182 */ 1183 private boolean isValidRingingCall() { 1184 if (getPhone() == null) { 1185 Log.v(this, "isValidRingingCall, phone is null"); 1186 return false; 1187 } 1188 1189 Call ringingCall = getPhone().getRingingCall(); 1190 if (!ringingCall.getState().isRinging()) { 1191 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 1192 return false; 1193 } 1194 1195 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 1196 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 1197 return false; 1198 } 1199 1200 Log.v(this, "isValidRingingCall, returning true"); 1201 return true; 1202 } 1203 1204 // Make sure the extras being passed into this method is a COPY of the original extras Bundle. 1205 // We do not want the extras to be cleared or modified during mOriginalConnectionExtras.putAll 1206 // below. 1207 protected void updateExtras(Bundle extras) { 1208 if (mOriginalConnection != null) { 1209 if (extras != null) { 1210 // Check if extras have changed and need updating. 1211 if (!areBundlesEqual(mOriginalConnectionExtras, extras)) { 1212 if (Log.DEBUG) { 1213 Log.d(TelephonyConnection.this, "Updating extras:"); 1214 for (String key : extras.keySet()) { 1215 Object value = extras.get(key); 1216 if (value instanceof String) { 1217 Log.d(this, "updateExtras Key=" + Log.pii(key) + 1218 " value=" + Log.pii((String)value)); 1219 } 1220 } 1221 } 1222 mOriginalConnectionExtras.clear(); 1223 1224 mOriginalConnectionExtras.putAll(extras); 1225 1226 // Remap any string extras that have a remapping defined. 1227 for (String key : mOriginalConnectionExtras.keySet()) { 1228 if (sExtrasMap.containsKey(key)) { 1229 String newKey = sExtrasMap.get(key); 1230 mOriginalConnectionExtras.putString(newKey, extras.getString(key)); 1231 mOriginalConnectionExtras.remove(key); 1232 } 1233 } 1234 1235 // Ensure extras are propagated to Telecom. 1236 putExtras(mOriginalConnectionExtras); 1237 } else { 1238 Log.d(this, "Extras update not required"); 1239 } 1240 } else { 1241 Log.d(this, "updateExtras extras: " + Log.pii(extras)); 1242 } 1243 } 1244 } 1245 1246 private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) { 1247 if (extras == null || newExtras == null) { 1248 return extras == newExtras; 1249 } 1250 1251 if (extras.size() != newExtras.size()) { 1252 return false; 1253 } 1254 1255 for(String key : extras.keySet()) { 1256 if (key != null) { 1257 final Object value = extras.get(key); 1258 final Object newValue = newExtras.get(key); 1259 if (!Objects.equals(value, newValue)) { 1260 return false; 1261 } 1262 } 1263 } 1264 return true; 1265 } 1266 1267 void setStateOverride(Call.State state) { 1268 mIsStateOverridden = true; 1269 mConnectionOverriddenState = state; 1270 // Need to keep track of the original connection's state before override. 1271 mOriginalConnectionState = mOriginalConnection.getState(); 1272 updateStateInternal(); 1273 } 1274 1275 void resetStateOverride() { 1276 mIsStateOverridden = false; 1277 updateStateInternal(); 1278 } 1279 1280 void updateStateInternal() { 1281 if (mOriginalConnection == null) { 1282 return; 1283 } 1284 Call.State newState; 1285 // If the state is overridden and the state of the original connection hasn't changed since, 1286 // then we continue in the overridden state, else we go to the original connection's state. 1287 if (mIsStateOverridden && mOriginalConnectionState == mOriginalConnection.getState()) { 1288 newState = mConnectionOverriddenState; 1289 } else { 1290 newState = mOriginalConnection.getState(); 1291 } 1292 Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState, this); 1293 1294 if (mConnectionState != newState) { 1295 mConnectionState = newState; 1296 switch (newState) { 1297 case IDLE: 1298 break; 1299 case ACTIVE: 1300 setActiveInternal(); 1301 break; 1302 case HOLDING: 1303 setOnHold(); 1304 break; 1305 case DIALING: 1306 case ALERTING: 1307 if (mOriginalConnection != null && mOriginalConnection.isPulledCall()) { 1308 setPulling(); 1309 } else { 1310 setDialing(); 1311 } 1312 break; 1313 case INCOMING: 1314 case WAITING: 1315 setRinging(); 1316 break; 1317 case DISCONNECTED: 1318 // We can get into a situation where the radio wants us to redial the same 1319 // emergency call on the other available slot. This will not set the state to 1320 // disconnected and will instead tell the TelephonyConnectionService to create 1321 // a new originalConnection using the new Slot. 1322 if (mOriginalConnection.getDisconnectCause() == 1323 DisconnectCause.DIALED_ON_WRONG_SLOT) { 1324 fireOnOriginalConnectionRetryDial(); 1325 } else { 1326 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 1327 mOriginalConnection.getDisconnectCause(), 1328 mOriginalConnection.getVendorDisconnectCause())); 1329 close(); 1330 } 1331 break; 1332 case DISCONNECTING: 1333 break; 1334 } 1335 } 1336 } 1337 1338 void updateState() { 1339 if (mOriginalConnection == null) { 1340 return; 1341 } 1342 1343 updateStateInternal(); 1344 updateStatusHints(); 1345 updateConnectionCapabilities(); 1346 updateConnectionProperties(); 1347 updateAddress(); 1348 updateMultiparty(); 1349 } 1350 1351 /** 1352 * Checks for changes to the multiparty bit. If a conference has started, informs listeners. 1353 */ 1354 private void updateMultiparty() { 1355 if (mOriginalConnection == null) { 1356 return; 1357 } 1358 1359 if (mIsMultiParty != mOriginalConnection.isMultiparty()) { 1360 mIsMultiParty = mOriginalConnection.isMultiparty(); 1361 1362 if (mIsMultiParty) { 1363 notifyConferenceStarted(); 1364 } 1365 } 1366 } 1367 1368 /** 1369 * Handles a failure when merging calls into a conference. 1370 * {@link com.android.internal.telephony.Connection.Listener#onConferenceMergedFailed()} 1371 * listener. 1372 */ 1373 private void handleConferenceMergeFailed(){ 1374 mHandler.obtainMessage(MSG_CONFERENCE_MERGE_FAILED).sendToTarget(); 1375 } 1376 1377 /** 1378 * Handles requests to update the multiparty state received via the 1379 * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)} 1380 * listener. 1381 * <p> 1382 * Note: We post this to the mHandler to ensure that if a conference must be created as a 1383 * result of the multiparty state change, the conference creation happens on the correct 1384 * thread. This ensures that the thread check in 1385 * {@link com.android.internal.telephony.Phone#checkCorrectThread(android.os.Handler)} 1386 * does not fire. 1387 * 1388 * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise. 1389 */ 1390 private void handleMultipartyStateChange(boolean isMultiParty) { 1391 Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N"); 1392 mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget(); 1393 } 1394 1395 private void setActiveInternal() { 1396 if (getState() == STATE_ACTIVE) { 1397 Log.w(this, "Should not be called if this is already ACTIVE"); 1398 return; 1399 } 1400 1401 // When we set a call to active, we need to make sure that there are no other active 1402 // calls. However, the ordering of state updates to connections can be non-deterministic 1403 // since all connections register for state changes on the phone independently. 1404 // To "optimize", we check here to see if there already exists any active calls. If so, 1405 // we issue an update for those calls first to make sure we only have one top-level 1406 // active call. 1407 if (getConnectionService() != null) { 1408 for (Connection current : getConnectionService().getAllConnections()) { 1409 if (current != this && current instanceof TelephonyConnection) { 1410 TelephonyConnection other = (TelephonyConnection) current; 1411 if (other.getState() == STATE_ACTIVE) { 1412 other.updateState(); 1413 } 1414 } 1415 } 1416 } 1417 setActive(); 1418 } 1419 1420 private void close() { 1421 Log.v(this, "close"); 1422 clearOriginalConnection(); 1423 destroy(); 1424 } 1425 1426 /** 1427 * Determines if the current connection is video capable. 1428 * 1429 * A connection is deemed to be video capable if the original connection capabilities state that 1430 * both local and remote video is supported. 1431 * 1432 * @return {@code true} if the connection is video capable, {@code false} otherwise. 1433 */ 1434 private boolean isVideoCapable() { 1435 return can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL) 1436 && can(mOriginalConnectionCapabilities, 1437 Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 1438 } 1439 1440 /** 1441 * Determines if the current connection is an external connection. 1442 * 1443 * A connection is deemed to be external if the original connection capabilities state that it 1444 * is. 1445 * 1446 * @return {@code true} if the connection is external, {@code false} otherwise. 1447 */ 1448 private boolean isExternalConnection() { 1449 return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION) 1450 && can(mOriginalConnectionCapabilities, 1451 Capability.IS_EXTERNAL_CONNECTION); 1452 } 1453 1454 /** 1455 * Determines if the current connection is pullable. 1456 * 1457 * A connection is deemed to be pullable if the original connection capabilities state that it 1458 * is. 1459 * 1460 * @return {@code true} if the connection is pullable, {@code false} otherwise. 1461 */ 1462 private boolean isPullable() { 1463 return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION) 1464 && can(mOriginalConnectionCapabilities, Capability.IS_PULLABLE); 1465 } 1466 1467 /** 1468 * Sets whether or not CDMA enhanced call privacy is enabled for this connection. 1469 */ 1470 private void setCdmaVoicePrivacy(boolean isEnabled) { 1471 if(mIsCdmaVoicePrivacyEnabled != isEnabled) { 1472 mIsCdmaVoicePrivacyEnabled = isEnabled; 1473 updateConnectionProperties(); 1474 } 1475 } 1476 1477 /** 1478 * Applies capabilities specific to conferences termination to the 1479 * {@code ConnectionCapabilities} bit-mask. 1480 * 1481 * @param capabilities The {@code ConnectionCapabilities} bit-mask. 1482 * @return The capabilities with the IMS conference capabilities applied. 1483 */ 1484 private int applyConferenceTerminationCapabilities(int capabilities) { 1485 int currentCapabilities = capabilities; 1486 1487 // An IMS call cannot be individually disconnected or separated from its parent conference. 1488 // If the call was IMS, even if it hands over to GMS, these capabilities are not supported. 1489 if (!mWasImsConnection) { 1490 currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE; 1491 currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE; 1492 } 1493 1494 return currentCapabilities; 1495 } 1496 1497 /** 1498 * Stores the new original connection capabilities, and applies them to the current connection, 1499 * notifying any listeners as necessary. 1500 * 1501 * @param connectionCapabilities The original connection capabilties. 1502 */ 1503 public void setOriginalConnectionCapabilities(int connectionCapabilities) { 1504 mOriginalConnectionCapabilities = connectionCapabilities; 1505 updateConnectionCapabilities(); 1506 updateConnectionProperties(); 1507 } 1508 1509 /** 1510 * Called to apply the capabilities present in the {@link #mOriginalConnection} to this 1511 * {@link Connection}. Provides a mapping between the capabilities present in the original 1512 * connection (see {@link com.android.internal.telephony.Connection.Capability}) and those in 1513 * this {@link Connection}. 1514 * 1515 * @param capabilities The capabilities bitmask from the {@link Connection}. 1516 * @return the capabilities bitmask with the original connection capabilities remapped and 1517 * applied. 1518 */ 1519 public int applyOriginalConnectionCapabilities(int capabilities) { 1520 // We only support downgrading to audio if both the remote and local side support 1521 // downgrading to audio. 1522 boolean supportsDowngradeToAudio = can(mOriginalConnectionCapabilities, 1523 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL | 1524 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE); 1525 capabilities = changeBitmask(capabilities, 1526 CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO, !supportsDowngradeToAudio); 1527 1528 capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, 1529 can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL)); 1530 1531 capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, 1532 can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL)); 1533 1534 return capabilities; 1535 } 1536 1537 /** 1538 * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset 1539 * the {@link Connection#PROPERTY_WIFI} property. 1540 */ 1541 public void setWifi(boolean isWifi) { 1542 mIsWifi = isWifi; 1543 updateConnectionProperties(); 1544 updateStatusHints(); 1545 refreshDisableAddCall(); 1546 } 1547 1548 /** 1549 * Whether the call is using wifi. 1550 */ 1551 boolean isWifi() { 1552 return mIsWifi; 1553 } 1554 1555 /** 1556 * Sets the current call audio quality. Used during rebuild of the properties 1557 * to set or unset the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property. 1558 * 1559 * @param audioQuality The audio quality. 1560 */ 1561 public void setAudioQuality(int audioQuality) { 1562 mHasHighDefAudio = audioQuality == 1563 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION; 1564 updateConnectionProperties(); 1565 } 1566 1567 void resetStateForConference() { 1568 if (getState() == Connection.STATE_HOLDING) { 1569 resetStateOverride(); 1570 } 1571 } 1572 1573 boolean setHoldingForConference() { 1574 if (getState() == Connection.STATE_ACTIVE) { 1575 setStateOverride(Call.State.HOLDING); 1576 return true; 1577 } 1578 return false; 1579 } 1580 1581 /** 1582 * For video calls, sets whether this connection supports pausing the outgoing video for the 1583 * call using the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. 1584 * 1585 * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise. 1586 */ 1587 public void setVideoPauseSupported(boolean isVideoPauseSupported) { 1588 mIsVideoPauseSupported = isVideoPauseSupported; 1589 } 1590 1591 /** 1592 * @return {@code true} if this connection supports pausing the outgoing video using the 1593 * {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. 1594 */ 1595 public boolean getVideoPauseSupported() { 1596 return mIsVideoPauseSupported; 1597 } 1598 1599 /** 1600 * Sets whether this connection supports conference calling. 1601 * @param isConferenceSupported {@code true} if conference calling is supported by this 1602 * connection, {@code false} otherwise. 1603 */ 1604 public void setConferenceSupported(boolean isConferenceSupported) { 1605 mIsConferenceSupported = isConferenceSupported; 1606 } 1607 1608 /** 1609 * @return {@code true} if this connection supports merging calls into a conference. 1610 */ 1611 public boolean isConferenceSupported() { 1612 return mIsConferenceSupported; 1613 } 1614 1615 /** 1616 * Whether the original connection is an IMS connection. 1617 * @return {@code True} if the original connection is an IMS connection, {@code false} 1618 * otherwise. 1619 */ 1620 protected boolean isImsConnection() { 1621 com.android.internal.telephony.Connection originalConnection = getOriginalConnection(); 1622 return originalConnection != null && 1623 originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS; 1624 } 1625 1626 /** 1627 * Whether the original connection was ever an IMS connection, either before or now. 1628 * @return {@code True} if the original connection was ever an IMS connection, {@code false} 1629 * otherwise. 1630 */ 1631 public boolean wasImsConnection() { 1632 return mWasImsConnection; 1633 } 1634 1635 private static Uri getAddressFromNumber(String number) { 1636 // Address can be null for blocked calls. 1637 if (number == null) { 1638 number = ""; 1639 } 1640 return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 1641 } 1642 1643 /** 1644 * Changes a capabilities bit-mask to add or remove a capability. 1645 * 1646 * @param bitmask The bit-mask. 1647 * @param bitfield The bit-field to change. 1648 * @param enabled Whether the bit-field should be set or removed. 1649 * @return The bit-mask with the bit-field changed. 1650 */ 1651 private int changeBitmask(int bitmask, int bitfield, boolean enabled) { 1652 if (enabled) { 1653 return bitmask | bitfield; 1654 } else { 1655 return bitmask & ~bitfield; 1656 } 1657 } 1658 1659 private void updateStatusHints() { 1660 boolean isIncoming = isValidRingingCall(); 1661 if (mIsWifi && (isIncoming || getState() == STATE_ACTIVE)) { 1662 int labelId = isIncoming 1663 ? R.string.status_hint_label_incoming_wifi_call 1664 : R.string.status_hint_label_wifi_call; 1665 1666 Context context = getPhone().getContext(); 1667 setStatusHints(new StatusHints( 1668 context.getString(labelId), 1669 Icon.createWithResource( 1670 context.getResources(), 1671 R.drawable.ic_signal_wifi_4_bar_24dp), 1672 null /* extras */)); 1673 } else { 1674 setStatusHints(null); 1675 } 1676 } 1677 1678 /** 1679 * Register a listener for {@link TelephonyConnection} specific triggers. 1680 * @param l The instance of the listener to add 1681 * @return The connection being listened to 1682 */ 1683 public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) { 1684 mTelephonyListeners.add(l); 1685 // If we already have an original connection, let's call back immediately. 1686 // This would be the case for incoming calls. 1687 if (mOriginalConnection != null) { 1688 fireOnOriginalConnectionConfigured(); 1689 } 1690 return this; 1691 } 1692 1693 /** 1694 * Remove a listener for {@link TelephonyConnection} specific triggers. 1695 * @param l The instance of the listener to remove 1696 * @return The connection being listened to 1697 */ 1698 public final TelephonyConnection removeTelephonyConnectionListener( 1699 TelephonyConnectionListener l) { 1700 if (l != null) { 1701 mTelephonyListeners.remove(l); 1702 } 1703 return this; 1704 } 1705 1706 /** 1707 * Fire a callback to the various listeners for when the original connection is 1708 * set in this {@link TelephonyConnection} 1709 */ 1710 private final void fireOnOriginalConnectionConfigured() { 1711 for (TelephonyConnectionListener l : mTelephonyListeners) { 1712 l.onOriginalConnectionConfigured(this); 1713 } 1714 } 1715 1716 private final void fireOnOriginalConnectionRetryDial() { 1717 for (TelephonyConnectionListener l : mTelephonyListeners) { 1718 l.onOriginalConnectionRetry(this); 1719 } 1720 } 1721 1722 /** 1723 * Handles exiting ECM mode. 1724 */ 1725 protected void handleExitedEcmMode() { 1726 updateConnectionProperties(); 1727 } 1728 1729 /** 1730 * Determines whether the connection supports conference calling. A connection supports 1731 * conference calling if it: 1732 * 1. Is not an emergency call. 1733 * 2. Carrier supports conference calls. 1734 * 3. If call is a video call, carrier supports video conference calls. 1735 * 4. If call is a wifi call and VoWIFI is disabled and carrier supports merging these calls. 1736 */ 1737 private void refreshConferenceSupported() { 1738 boolean isVideoCall = VideoProfile.isVideo(getVideoState()); 1739 Phone phone = getPhone(); 1740 boolean isIms = phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS; 1741 boolean isVoWifiEnabled = false; 1742 if (isIms) { 1743 ImsPhone imsPhone = (ImsPhone) phone; 1744 isVoWifiEnabled = imsPhone.isWifiCallingEnabled(); 1745 } 1746 PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils 1747 .makePstnPhoneAccountHandle(phone.getDefaultPhone()) 1748 : PhoneUtils.makePstnPhoneAccountHandle(phone); 1749 TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry 1750 .getInstance(getPhone().getContext()); 1751 boolean isConferencingSupported = telecomAccountRegistry 1752 .isMergeCallSupported(phoneAccountHandle); 1753 mIsCarrierVideoConferencingSupported = telecomAccountRegistry 1754 .isVideoConferencingSupported(phoneAccountHandle); 1755 boolean isMergeOfWifiCallsAllowedWhenVoWifiOff = telecomAccountRegistry 1756 .isMergeOfWifiCallsAllowedWhenVoWifiOff(phoneAccountHandle); 1757 1758 Log.v(this, "refreshConferenceSupported : isConfSupp=%b, isVidConfSupp=%b, " + 1759 "isMergeOfWifiAllowed=%b, isWifi=%b, isVoWifiEnabled=%b", isConferencingSupported, 1760 mIsCarrierVideoConferencingSupported, isMergeOfWifiCallsAllowedWhenVoWifiOff, 1761 isWifi(), isVoWifiEnabled); 1762 boolean isConferenceSupported = true; 1763 if (mTreatAsEmergencyCall) { 1764 isConferenceSupported = false; 1765 Log.d(this, "refreshConferenceSupported = false; emergency call"); 1766 } else if (!isConferencingSupported) { 1767 isConferenceSupported = false; 1768 Log.d(this, "refreshConferenceSupported = false; carrier doesn't support conf."); 1769 } else if (isVideoCall && !mIsCarrierVideoConferencingSupported) { 1770 isConferenceSupported = false; 1771 Log.d(this, "refreshConferenceSupported = false; video conf not supported."); 1772 } else if (!isMergeOfWifiCallsAllowedWhenVoWifiOff && isWifi() && !isVoWifiEnabled) { 1773 isConferenceSupported = false; 1774 Log.d(this, 1775 "refreshConferenceSupported = false; can't merge wifi calls when voWifi off."); 1776 } else { 1777 Log.d(this, "refreshConferenceSupported = true."); 1778 } 1779 1780 if (isConferenceSupported != isConferenceSupported()) { 1781 setConferenceSupported(isConferenceSupported); 1782 notifyConferenceSupportedChanged(isConferenceSupported); 1783 } 1784 } 1785 /** 1786 * Provides a mapping from extras keys which may be found in the 1787 * {@link com.android.internal.telephony.Connection} to their equivalents defined in 1788 * {@link android.telecom.Connection}. 1789 * 1790 * @return Map containing key mappings. 1791 */ 1792 private static Map<String, String> createExtrasMap() { 1793 Map<String, String> result = new HashMap<String, String>(); 1794 result.put(ImsCallProfile.EXTRA_CHILD_NUMBER, 1795 android.telecom.Connection.EXTRA_CHILD_ADDRESS); 1796 result.put(ImsCallProfile.EXTRA_DISPLAY_TEXT, 1797 android.telecom.Connection.EXTRA_CALL_SUBJECT); 1798 return Collections.unmodifiableMap(result); 1799 } 1800 1801 /** 1802 * Creates a string representation of this {@link TelephonyConnection}. Primarily intended for 1803 * use in log statements. 1804 * 1805 * @return String representation of the connection. 1806 */ 1807 @Override 1808 public String toString() { 1809 StringBuilder sb = new StringBuilder(); 1810 sb.append("[TelephonyConnection objId:"); 1811 sb.append(System.identityHashCode(this)); 1812 sb.append(" telecomCallID:"); 1813 sb.append(getTelecomCallId()); 1814 sb.append(" type:"); 1815 if (isImsConnection()) { 1816 sb.append("ims"); 1817 } else if (this instanceof com.android.services.telephony.GsmConnection) { 1818 sb.append("gsm"); 1819 } else if (this instanceof CdmaConnection) { 1820 sb.append("cdma"); 1821 } 1822 sb.append(" state:"); 1823 sb.append(Connection.stateToString(getState())); 1824 sb.append(" capabilities:"); 1825 sb.append(capabilitiesToString(getConnectionCapabilities())); 1826 sb.append(" properties:"); 1827 sb.append(propertiesToString(getConnectionProperties())); 1828 sb.append(" address:"); 1829 sb.append(Log.pii(getAddress())); 1830 sb.append(" originalConnection:"); 1831 sb.append(mOriginalConnection); 1832 sb.append(" partOfConf:"); 1833 if (getConference() == null) { 1834 sb.append("N"); 1835 } else { 1836 sb.append("Y"); 1837 } 1838 sb.append(" confSupported:"); 1839 sb.append(mIsConferenceSupported ? "Y" : "N"); 1840 sb.append("]"); 1841 return sb.toString(); 1842 } 1843} 1844