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