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