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