TelephonyConnection.java revision 5046bc73a9bb72c6f6f9f913def0c71d551da62d
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 (isImsConnection()) { 597 if (mOriginalConnection.isIncoming()) { 598 callCapabilities |= CAPABILITY_SPEED_UP_MT_AUDIO; 599 } 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 if (mOriginalConnection.getConnectionExtras() == null) { 741 Bundle b = new Bundle(); 742 b.putInt(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, getPhone().getPhoneType()); 743 mOriginalConnection.setConnectionExtras(b); 744 } else { 745 mOriginalConnection.getConnectionExtras().putInt( 746 TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, getPhone().getPhoneType()); 747 } 748 } 749 } 750 751 /** 752 * Whether the connection should be treated as an emergency. 753 * @return {@code true} if the connection should be treated as an emergency call based 754 * on the number dialed, {@code false} otherwise. 755 */ 756 protected boolean shouldTreatAsEmergencyCall() { 757 return mTreatAsEmergencyCall; 758 } 759 760 /** 761 * Un-sets the underlying radio connection. 762 */ 763 void clearOriginalConnection() { 764 if (mOriginalConnection != null) { 765 if (getPhone() != null) { 766 getPhone().unregisterForPreciseCallStateChanged(mHandler); 767 getPhone().unregisterForRingbackTone(mHandler); 768 getPhone().unregisterForHandoverStateChanged(mHandler); 769 getPhone().unregisterForDisconnect(mHandler); 770 getPhone().unregisterForSuppServiceNotification(mHandler); 771 getPhone().unregisterForOnHoldTone(mHandler); 772 } 773 mOriginalConnection.removePostDialListener(mPostDialListener); 774 mOriginalConnection.removeListener(mOriginalConnectionListener); 775 mOriginalConnection = null; 776 } 777 } 778 779 protected void hangup(int telephonyDisconnectCode) { 780 if (mOriginalConnection != null) { 781 try { 782 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to 783 // connection.hangup(). Without this change, the party originating the call will not 784 // get sent to voicemail if the user opts to reject the call. 785 if (isValidRingingCall()) { 786 Call call = getCall(); 787 if (call != null) { 788 call.hangup(); 789 } else { 790 Log.w(this, "Attempting to hangup a connection without backing call."); 791 } 792 } else { 793 // We still prefer to call connection.hangup() for non-ringing calls in order 794 // to support hanging-up specific calls within a conference call. If we invoked 795 // call.hangup() while in a conference, we would end up hanging up the entire 796 // conference call instead of the specific connection. 797 mOriginalConnection.hangup(); 798 } 799 } catch (CallStateException e) { 800 Log.e(this, e, "Call to Connection.hangup failed with exception"); 801 } 802 } 803 } 804 805 com.android.internal.telephony.Connection getOriginalConnection() { 806 return mOriginalConnection; 807 } 808 809 protected Call getCall() { 810 if (mOriginalConnection != null) { 811 return mOriginalConnection.getCall(); 812 } 813 return null; 814 } 815 816 Phone getPhone() { 817 Call call = getCall(); 818 if (call != null) { 819 return call.getPhone(); 820 } 821 return null; 822 } 823 824 private boolean hasMultipleTopLevelCalls() { 825 int numCalls = 0; 826 Phone phone = getPhone(); 827 if (phone != null) { 828 if (!phone.getRingingCall().isIdle()) { 829 numCalls++; 830 } 831 if (!phone.getForegroundCall().isIdle()) { 832 numCalls++; 833 } 834 if (!phone.getBackgroundCall().isIdle()) { 835 numCalls++; 836 } 837 } 838 return numCalls > 1; 839 } 840 841 private com.android.internal.telephony.Connection getForegroundConnection() { 842 if (getPhone() != null) { 843 return getPhone().getForegroundCall().getEarliestConnection(); 844 } 845 return null; 846 } 847 848 /** 849 * Checks for and returns the list of conference participants 850 * associated with this connection. 851 */ 852 public List<ConferenceParticipant> getConferenceParticipants() { 853 if (mOriginalConnection == null) { 854 Log.v(this, "Null mOriginalConnection, cannot get conf participants."); 855 return null; 856 } 857 return mOriginalConnection.getConferenceParticipants(); 858 } 859 860 /** 861 * Checks to see the original connection corresponds to an active incoming call. Returns false 862 * if there is no such actual call, or if the associated call is not incoming (See 863 * {@link Call.State#isRinging}). 864 */ 865 private boolean isValidRingingCall() { 866 if (getPhone() == null) { 867 Log.v(this, "isValidRingingCall, phone is null"); 868 return false; 869 } 870 871 Call ringingCall = getPhone().getRingingCall(); 872 if (!ringingCall.getState().isRinging()) { 873 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 874 return false; 875 } 876 877 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 878 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 879 return false; 880 } 881 882 Log.v(this, "isValidRingingCall, returning true"); 883 return true; 884 } 885 886 protected void updateExtras(Bundle extras) { 887 if (mOriginalConnection != null) { 888 if (extras != null) { 889 // Check if extras have changed and need updating. 890 if (!areBundlesEqual(mOriginalConnectionExtras, extras)) { 891 if (Log.DEBUG) { 892 Log.d(TelephonyConnection.this, "Updating extras:"); 893 for (String key : extras.keySet()) { 894 Object value = extras.get(key); 895 if (value instanceof String) { 896 Log.d(this, "updateExtras Key=" + Log.pii(key) + 897 " value=" + Log.pii((String)value)); 898 } 899 } 900 } 901 mOriginalConnectionExtras.clear(); 902 903 mOriginalConnectionExtras.putAll(extras); 904 905 // Remap any string extras that have a remapping defined. 906 for (String key : mOriginalConnectionExtras.keySet()) { 907 if (sExtrasMap.containsKey(key)) { 908 String newKey = sExtrasMap.get(key); 909 mOriginalConnectionExtras.putString(newKey, extras.getString(key)); 910 mOriginalConnectionExtras.remove(key); 911 } 912 } 913 914 // Ensure extras are propagated to Telecom. 915 putExtras(mOriginalConnectionExtras); 916 } else { 917 Log.d(this, "Extras update not required"); 918 } 919 } else { 920 Log.d(this, "updateExtras extras: " + Log.pii(extras)); 921 } 922 } 923 } 924 925 private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) { 926 if (extras == null || newExtras == null) { 927 return extras == newExtras; 928 } 929 930 if (extras.size() != newExtras.size()) { 931 return false; 932 } 933 934 for(String key : extras.keySet()) { 935 if (key != null) { 936 final Object value = extras.get(key); 937 final Object newValue = newExtras.get(key); 938 if (!Objects.equals(value, newValue)) { 939 return false; 940 } 941 } 942 } 943 return true; 944 } 945 946 void setStateOverride(Call.State state) { 947 mIsStateOverridden = true; 948 mConnectionOverriddenState = state; 949 // Need to keep track of the original connection's state before override. 950 mOriginalConnectionState = mOriginalConnection.getState(); 951 updateStateInternal(); 952 } 953 954 void resetStateOverride() { 955 mIsStateOverridden = false; 956 updateStateInternal(); 957 } 958 959 void updateStateInternal() { 960 if (mOriginalConnection == null) { 961 return; 962 } 963 Call.State newState; 964 // If the state is overridden and the state of the original connection hasn't changed since, 965 // then we continue in the overridden state, else we go to the original connection's state. 966 if (mIsStateOverridden && mOriginalConnectionState == mOriginalConnection.getState()) { 967 newState = mConnectionOverriddenState; 968 } else { 969 newState = mOriginalConnection.getState(); 970 } 971 Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState, this); 972 973 if (mConnectionState != newState) { 974 mConnectionState = newState; 975 switch (newState) { 976 case IDLE: 977 break; 978 case ACTIVE: 979 setActiveInternal(); 980 break; 981 case HOLDING: 982 setOnHold(); 983 break; 984 case DIALING: 985 case ALERTING: 986 setDialing(); 987 break; 988 case INCOMING: 989 case WAITING: 990 setRinging(); 991 break; 992 case DISCONNECTED: 993 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 994 mOriginalConnection.getDisconnectCause(), 995 mOriginalConnection.getVendorDisconnectCause())); 996 close(); 997 break; 998 case DISCONNECTING: 999 break; 1000 } 1001 } 1002 } 1003 1004 void updateState() { 1005 if (mOriginalConnection == null) { 1006 return; 1007 } 1008 1009 updateStateInternal(); 1010 updateStatusHints(); 1011 updateConnectionCapabilities(); 1012 updateConnectionProperties(); 1013 updateAddress(); 1014 updateMultiparty(); 1015 } 1016 1017 /** 1018 * Checks for changes to the multiparty bit. If a conference has started, informs listeners. 1019 */ 1020 private void updateMultiparty() { 1021 if (mOriginalConnection == null) { 1022 return; 1023 } 1024 1025 if (mIsMultiParty != mOriginalConnection.isMultiparty()) { 1026 mIsMultiParty = mOriginalConnection.isMultiparty(); 1027 1028 if (mIsMultiParty) { 1029 notifyConferenceStarted(); 1030 } 1031 } 1032 } 1033 1034 /** 1035 * Handles a failure when merging calls into a conference. 1036 * {@link com.android.internal.telephony.Connection.Listener#onConferenceMergedFailed()} 1037 * listener. 1038 */ 1039 private void handleConferenceMergeFailed(){ 1040 mHandler.obtainMessage(MSG_CONFERENCE_MERGE_FAILED).sendToTarget(); 1041 } 1042 1043 /** 1044 * Handles requests to update the multiparty state received via the 1045 * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)} 1046 * listener. 1047 * <p> 1048 * Note: We post this to the mHandler to ensure that if a conference must be created as a 1049 * result of the multiparty state change, the conference creation happens on the correct 1050 * thread. This ensures that the thread check in 1051 * {@link com.android.internal.telephony.Phone#checkCorrectThread(android.os.Handler)} 1052 * does not fire. 1053 * 1054 * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise. 1055 */ 1056 private void handleMultipartyStateChange(boolean isMultiParty) { 1057 Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N"); 1058 mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget(); 1059 } 1060 1061 private void setActiveInternal() { 1062 if (getState() == STATE_ACTIVE) { 1063 Log.w(this, "Should not be called if this is already ACTIVE"); 1064 return; 1065 } 1066 1067 // When we set a call to active, we need to make sure that there are no other active 1068 // calls. However, the ordering of state updates to connections can be non-deterministic 1069 // since all connections register for state changes on the phone independently. 1070 // To "optimize", we check here to see if there already exists any active calls. If so, 1071 // we issue an update for those calls first to make sure we only have one top-level 1072 // active call. 1073 if (getConnectionService() != null) { 1074 for (Connection current : getConnectionService().getAllConnections()) { 1075 if (current != this && current instanceof TelephonyConnection) { 1076 TelephonyConnection other = (TelephonyConnection) current; 1077 if (other.getState() == STATE_ACTIVE) { 1078 other.updateState(); 1079 } 1080 } 1081 } 1082 } 1083 setActive(); 1084 } 1085 1086 private void close() { 1087 Log.v(this, "close"); 1088 clearOriginalConnection(); 1089 destroy(); 1090 } 1091 1092 /** 1093 * Determines if the current connection is video capable. 1094 * 1095 * A connection is deemed to be video capable if the original connection capabilities state that 1096 * both local and remote video is supported. 1097 * 1098 * @return {@code true} if the connection is video capable, {@code false} otherwise. 1099 */ 1100 private boolean isVideoCapable() { 1101 return can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL) 1102 && can(mOriginalConnectionCapabilities, 1103 Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 1104 } 1105 1106 /** 1107 * Determines if the current connection is an external connection. 1108 * 1109 * A connection is deemed to be external if the original connection capabilities state that it 1110 * is. 1111 * 1112 * @return {@code true} if the connection is external, {@code false} otherwise. 1113 */ 1114 private boolean isExternalConnection() { 1115 return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION) 1116 && can(mOriginalConnectionCapabilities, 1117 Capability.IS_EXTERNAL_CONNECTION); 1118 } 1119 1120 /** 1121 * Determines if the current connection is pullable. 1122 * 1123 * A connection is deemed to be pullable if the original connection capabilities state that it 1124 * is. 1125 * 1126 * @return {@code true} if the connection is pullable, {@code false} otherwise. 1127 */ 1128 private boolean isPullable() { 1129 return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION) 1130 && can(mOriginalConnectionCapabilities, Capability.IS_PULLABLE); 1131 } 1132 1133 /** 1134 * Applies capabilities specific to conferences termination to the 1135 * {@code ConnectionCapabilities} bit-mask. 1136 * 1137 * @param capabilities The {@code ConnectionCapabilities} bit-mask. 1138 * @return The capabilities with the IMS conference capabilities applied. 1139 */ 1140 private int applyConferenceTerminationCapabilities(int capabilities) { 1141 int currentCapabilities = capabilities; 1142 1143 // An IMS call cannot be individually disconnected or separated from its parent conference. 1144 // If the call was IMS, even if it hands over to GMS, these capabilities are not supported. 1145 if (!mWasImsConnection) { 1146 currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE; 1147 currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE; 1148 } 1149 1150 return currentCapabilities; 1151 } 1152 1153 /** 1154 * Stores the new original connection capabilities, and applies them to the current connection, 1155 * notifying any listeners as necessary. 1156 * 1157 * @param connectionCapabilities The original connection capabilties. 1158 */ 1159 public void setOriginalConnectionCapabilities(int connectionCapabilities) { 1160 mOriginalConnectionCapabilities = connectionCapabilities; 1161 updateConnectionCapabilities(); 1162 updateConnectionProperties(); 1163 } 1164 1165 /** 1166 * Called to apply the capabilities present in the {@link #mOriginalConnection} to this 1167 * {@link Connection}. Provides a mapping between the capabilities present in the original 1168 * connection (see {@link com.android.internal.telephony.Connection.Capability}) and those in 1169 * this {@link Connection}. 1170 * 1171 * @param capabilities The capabilities bitmask from the {@link Connection}. 1172 * @return the capabilities bitmask with the original connection capabilities remapped and 1173 * applied. 1174 */ 1175 public int applyOriginalConnectionCapabilities(int capabilities) { 1176 // We only support downgrading to audio if both the remote and local side support 1177 // downgrading to audio. 1178 boolean supportsDowngradeToAudio = can(mOriginalConnectionCapabilities, 1179 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL | 1180 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE); 1181 capabilities = changeBitmask(capabilities, 1182 CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO, !supportsDowngradeToAudio); 1183 1184 capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, 1185 can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL)); 1186 1187 capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, 1188 can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL)); 1189 1190 return capabilities; 1191 } 1192 1193 /** 1194 * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset 1195 * the {@link Connection#PROPERTY_WIFI} property. 1196 */ 1197 public void setWifi(boolean isWifi) { 1198 mIsWifi = isWifi; 1199 updateConnectionProperties(); 1200 updateStatusHints(); 1201 } 1202 1203 /** 1204 * Whether the call is using wifi. 1205 */ 1206 boolean isWifi() { 1207 return mIsWifi; 1208 } 1209 1210 /** 1211 * Sets the current call audio quality. Used during rebuild of the properties 1212 * to set or unset the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property. 1213 * 1214 * @param audioQuality The audio quality. 1215 */ 1216 public void setAudioQuality(int audioQuality) { 1217 mHasHighDefAudio = audioQuality == 1218 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION; 1219 updateConnectionProperties(); 1220 } 1221 1222 void resetStateForConference() { 1223 if (getState() == Connection.STATE_HOLDING) { 1224 resetStateOverride(); 1225 } 1226 } 1227 1228 boolean setHoldingForConference() { 1229 if (getState() == Connection.STATE_ACTIVE) { 1230 setStateOverride(Call.State.HOLDING); 1231 return true; 1232 } 1233 return false; 1234 } 1235 1236 /** 1237 * For video calls, sets whether this connection supports pausing the outgoing video for the 1238 * call using the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. 1239 * 1240 * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise. 1241 */ 1242 public void setVideoPauseSupported(boolean isVideoPauseSupported) { 1243 mIsVideoPauseSupported = isVideoPauseSupported; 1244 } 1245 1246 /** 1247 * Sets whether this connection supports conference calling. 1248 * @param isConferenceSupported {@code true} if conference calling is supported by this 1249 * connection, {@code false} otherwise. 1250 */ 1251 public void setConferenceSupported(boolean isConferenceSupported) { 1252 mIsConferenceSupported = isConferenceSupported; 1253 } 1254 1255 /** 1256 * @return {@code true} if this connection supports merging calls into a conference. 1257 */ 1258 public boolean isConferenceSupported() { 1259 return mIsConferenceSupported; 1260 } 1261 1262 /** 1263 * Whether the original connection is an IMS connection. 1264 * @return {@code True} if the original connection is an IMS connection, {@code false} 1265 * otherwise. 1266 */ 1267 protected boolean isImsConnection() { 1268 com.android.internal.telephony.Connection originalConnection = getOriginalConnection(); 1269 return originalConnection != null && 1270 originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS; 1271 } 1272 1273 /** 1274 * Whether the original connection was ever an IMS connection, either before or now. 1275 * @return {@code True} if the original connection was ever an IMS connection, {@code false} 1276 * otherwise. 1277 */ 1278 public boolean wasImsConnection() { 1279 return mWasImsConnection; 1280 } 1281 1282 private static Uri getAddressFromNumber(String number) { 1283 // Address can be null for blocked calls. 1284 if (number == null) { 1285 number = ""; 1286 } 1287 return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 1288 } 1289 1290 /** 1291 * Changes a capabilities bit-mask to add or remove a capability. 1292 * 1293 * @param bitmask The bit-mask. 1294 * @param bitfield The bit-field to change. 1295 * @param enabled Whether the bit-field should be set or removed. 1296 * @return The bit-mask with the bit-field changed. 1297 */ 1298 private int changeBitmask(int bitmask, int bitfield, boolean enabled) { 1299 if (enabled) { 1300 return bitmask | bitfield; 1301 } else { 1302 return bitmask & ~bitfield; 1303 } 1304 } 1305 1306 private void updateStatusHints() { 1307 boolean isIncoming = isValidRingingCall(); 1308 if (mIsWifi && (isIncoming || getState() == STATE_ACTIVE)) { 1309 int labelId = isIncoming 1310 ? R.string.status_hint_label_incoming_wifi_call 1311 : R.string.status_hint_label_wifi_call; 1312 1313 Context context = getPhone().getContext(); 1314 setStatusHints(new StatusHints( 1315 context.getString(labelId), 1316 Icon.createWithResource( 1317 context.getResources(), 1318 R.drawable.ic_signal_wifi_4_bar_24dp), 1319 null /* extras */)); 1320 } else { 1321 setStatusHints(null); 1322 } 1323 } 1324 1325 /** 1326 * Register a listener for {@link TelephonyConnection} specific triggers. 1327 * @param l The instance of the listener to add 1328 * @return The connection being listened to 1329 */ 1330 public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) { 1331 mTelephonyListeners.add(l); 1332 // If we already have an original connection, let's call back immediately. 1333 // This would be the case for incoming calls. 1334 if (mOriginalConnection != null) { 1335 fireOnOriginalConnectionConfigured(); 1336 } 1337 return this; 1338 } 1339 1340 /** 1341 * Remove a listener for {@link TelephonyConnection} specific triggers. 1342 * @param l The instance of the listener to remove 1343 * @return The connection being listened to 1344 */ 1345 public final TelephonyConnection removeTelephonyConnectionListener( 1346 TelephonyConnectionListener l) { 1347 if (l != null) { 1348 mTelephonyListeners.remove(l); 1349 } 1350 return this; 1351 } 1352 1353 /** 1354 * Fire a callback to the various listeners for when the original connection is 1355 * set in this {@link TelephonyConnection} 1356 */ 1357 private final void fireOnOriginalConnectionConfigured() { 1358 for (TelephonyConnectionListener l : mTelephonyListeners) { 1359 l.onOriginalConnectionConfigured(this); 1360 } 1361 } 1362 1363 /** 1364 * Handles exiting ECM mode. 1365 */ 1366 protected void handleExitedEcmMode() { 1367 updateConnectionProperties(); 1368 } 1369 1370 /** 1371 * Provides a mapping from extras keys which may be found in the 1372 * {@link com.android.internal.telephony.Connection} to their equivalents defined in 1373 * {@link android.telecom.Connection}. 1374 * 1375 * @return Map containing key mappings. 1376 */ 1377 private static Map<String, String> createExtrasMap() { 1378 Map<String, String> result = new HashMap<String, String>(); 1379 result.put(ImsCallProfile.EXTRA_CHILD_NUMBER, 1380 android.telecom.Connection.EXTRA_CHILD_ADDRESS); 1381 result.put(ImsCallProfile.EXTRA_DISPLAY_TEXT, 1382 android.telecom.Connection.EXTRA_CALL_SUBJECT); 1383 return Collections.unmodifiableMap(result); 1384 } 1385 1386 /** 1387 * Creates a string representation of this {@link TelephonyConnection}. Primarily intended for 1388 * use in log statements. 1389 * 1390 * @return String representation of the connection. 1391 */ 1392 @Override 1393 public String toString() { 1394 StringBuilder sb = new StringBuilder(); 1395 sb.append("[TelephonyConnection objId:"); 1396 sb.append(System.identityHashCode(this)); 1397 sb.append(" telecomCallID:"); 1398 sb.append(getTelecomCallId()); 1399 sb.append(" type:"); 1400 if (isImsConnection()) { 1401 sb.append("ims"); 1402 } else if (this instanceof com.android.services.telephony.GsmConnection) { 1403 sb.append("gsm"); 1404 } else if (this instanceof CdmaConnection) { 1405 sb.append("cdma"); 1406 } 1407 sb.append(" state:"); 1408 sb.append(Connection.stateToString(getState())); 1409 sb.append(" capabilities:"); 1410 sb.append(capabilitiesToString(getConnectionCapabilities())); 1411 sb.append(" properties:"); 1412 sb.append(propertiesToString(getConnectionProperties())); 1413 sb.append(" address:"); 1414 sb.append(Log.pii(getAddress())); 1415 sb.append(" originalConnection:"); 1416 sb.append(mOriginalConnection); 1417 sb.append(" partOfConf:"); 1418 if (getConference() == null) { 1419 sb.append("N"); 1420 } else { 1421 sb.append("Y"); 1422 } 1423 sb.append("]"); 1424 return sb.toString(); 1425 } 1426} 1427