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