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