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