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