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