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