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