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