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.os.PersistableBundle; 27 import android.telecom.CallAudioState; 28 import android.telecom.ConferenceParticipant; 29 import android.telecom.Connection; 30 import android.telecom.PhoneAccount; 31 import android.telecom.PhoneAccountHandle; 32 import android.telecom.StatusHints; 33 import android.telecom.TelecomManager; 34 import android.telecom.VideoProfile; 35 import android.telephony.CarrierConfigManager; 36 import android.telephony.DisconnectCause; 37 import android.telephony.PhoneNumberUtils; 38 import android.telephony.TelephonyManager; 39 import android.util.Pair; 40 41 import com.android.ims.ImsCall; 42 import com.android.ims.ImsCallProfile; 43 import com.android.internal.telephony.Call; 44 import com.android.internal.telephony.CallStateException; 45 import com.android.internal.telephony.Connection.Capability; 46 import com.android.internal.telephony.Connection.PostDialListener; 47 import com.android.internal.telephony.PhoneConstants; 48 import com.android.internal.telephony.gsm.SuppServiceNotification; 49 50 import com.android.internal.telephony.Phone; 51 import com.android.internal.telephony.imsphone.ImsPhone; 52 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker; 53 import com.android.phone.ImsUtil; 54 import com.android.phone.PhoneGlobals; 55 import com.android.phone.PhoneUtils; 56 import com.android.phone.R; 57 58 import java.lang.Override; 59 import java.util.Arrays; 60 import java.util.ArrayList; 61 import java.util.Collections; 62 import java.util.HashMap; 63 import java.util.List; 64 import java.util.Map; 65 import java.util.Objects; 66 import java.util.Set; 67 import java.util.concurrent.ConcurrentHashMap; 68 69 /** 70 * Base class for CDMA and GSM connections. 71 */ 72 abstract class TelephonyConnection extends Connection { 73 private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1; 74 private static final int MSG_RINGBACK_TONE = 2; 75 private static final int MSG_HANDOVER_STATE_CHANGED = 3; 76 private static final int MSG_DISCONNECT = 4; 77 private static final int MSG_MULTIPARTY_STATE_CHANGED = 5; 78 private static final int MSG_CONFERENCE_MERGE_FAILED = 6; 79 private static final int MSG_SUPP_SERVICE_NOTIFY = 7; 80 81 /** 82 * Mappings from {@link com.android.internal.telephony.Connection} extras keys to their 83 * equivalents defined in {@link android.telecom.Connection}. 84 */ 85 private static final Map<String, String> sExtrasMap = createExtrasMap(); 86 87 private static final int MSG_SET_VIDEO_STATE = 8; 88 private static final int MSG_SET_VIDEO_PROVIDER = 9; 89 private static final int MSG_SET_AUDIO_QUALITY = 10; 90 private static final int MSG_SET_CONFERENCE_PARTICIPANTS = 11; 91 private static final int MSG_CONNECTION_EXTRAS_CHANGED = 12; 92 private static final int MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES = 13; 93 private static final int MSG_ON_HOLD_TONE = 14; 94 private static final int MSG_CDMA_VOICE_PRIVACY_ON = 15; 95 private static final int MSG_CDMA_VOICE_PRIVACY_OFF = 16; 96 97 private final Handler mHandler = new Handler() { 98 @Override 99 public void handleMessage(Message msg) { 100 switch (msg.what) { 101 case MSG_PRECISE_CALL_STATE_CHANGED: 102 Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED"); 103 updateState(); 104 break; 105 case MSG_HANDOVER_STATE_CHANGED: 106 Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED"); 107 AsyncResult ar = (AsyncResult) msg.obj; 108 com.android.internal.telephony.Connection connection = 109 (com.android.internal.telephony.Connection) ar.result; 110 if (mOriginalConnection != null) { 111 if (connection != null && 112 ((connection.getAddress() != null && 113 mOriginalConnection.getAddress() != null && 114 mOriginalConnection.getAddress().contains(connection.getAddress())) || 115 connection.getState() == mOriginalConnection.getStateBeforeHandover())) { 116 Log.d(TelephonyConnection.this, 117 "SettingOriginalConnection " + mOriginalConnection.toString() 118 + " with " + connection.toString()); 119 setOriginalConnection(connection); 120 mWasImsConnection = false; 121 } 122 } else { 123 Log.w(TelephonyConnection.this, 124 "MSG_HANDOVER_STATE_CHANGED: mOriginalConnection==null - invalid state (not cleaned up)"); 125 } 126 break; 127 case MSG_RINGBACK_TONE: 128 Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE"); 129 // TODO: This code assumes that there is only one connection in the foreground 130 // call, in other words, it punts on network-mediated conference calling. 131 if (getOriginalConnection() != getForegroundConnection()) { 132 Log.v(TelephonyConnection.this, "handleMessage, original connection is " + 133 "not foreground connection, skipping"); 134 return; 135 } 136 setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result); 137 break; 138 case MSG_DISCONNECT: 139 updateState(); 140 break; 141 case MSG_MULTIPARTY_STATE_CHANGED: 142 boolean isMultiParty = (Boolean) msg.obj; 143 Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N"); 144 mIsMultiParty = isMultiParty; 145 if (isMultiParty) { 146 notifyConferenceStarted(); 147 } 148 break; 149 case MSG_CONFERENCE_MERGE_FAILED: 150 notifyConferenceMergeFailed(); 151 break; 152 case MSG_SUPP_SERVICE_NOTIFY: 153 Log.v(TelephonyConnection.this, "MSG_SUPP_SERVICE_NOTIFY on phoneId : " 154 + getPhone() != null ? Integer.toString(getPhone().getPhoneId()) 155 : "null"); 156 SuppServiceNotification mSsNotification = null; 157 if (msg.obj != null && ((AsyncResult) msg.obj).result != null) { 158 mSsNotification = 159 (SuppServiceNotification)((AsyncResult) msg.obj).result; 160 if (mOriginalConnection != null && mSsNotification.history != null) { 161 Bundle lastForwardedNumber = new Bundle(); 162 Log.v(TelephonyConnection.this, 163 "Updating call history info in extras."); 164 lastForwardedNumber.putStringArrayList( 165 Connection.EXTRA_LAST_FORWARDED_NUMBER, 166 new ArrayList(Arrays.asList(mSsNotification.history))); 167 putExtras(lastForwardedNumber); 168 } 169 } 170 break; 171 172 case MSG_SET_VIDEO_STATE: 173 int videoState = (int) msg.obj; 174 setVideoState(videoState); 175 176 // A change to the video state of the call can influence whether or not it 177 // can be part of a conference, whether another call can be added, and 178 // whether the call should have the HD audio property set. 179 refreshConferenceSupported(); 180 refreshDisableAddCall(); 181 updateConnectionProperties(); 182 break; 183 184 case MSG_SET_VIDEO_PROVIDER: 185 VideoProvider videoProvider = (VideoProvider) msg.obj; 186 setVideoProvider(videoProvider); 187 break; 188 189 case MSG_SET_AUDIO_QUALITY: 190 int audioQuality = (int) msg.obj; 191 setAudioQuality(audioQuality); 192 break; 193 194 case MSG_SET_CONFERENCE_PARTICIPANTS: 195 List<ConferenceParticipant> participants = (List<ConferenceParticipant>) msg.obj; 196 updateConferenceParticipants(participants); 197 break; 198 199 case MSG_CONNECTION_EXTRAS_CHANGED: 200 final Bundle extras = (Bundle) msg.obj; 201 updateExtras(extras); 202 break; 203 204 case MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES: 205 setOriginalConnectionCapabilities(msg.arg1); 206 break; 207 208 case MSG_ON_HOLD_TONE: 209 AsyncResult asyncResult = (AsyncResult) msg.obj; 210 Pair<com.android.internal.telephony.Connection, Boolean> heldInfo = 211 (Pair<com.android.internal.telephony.Connection, Boolean>) 212 asyncResult.result; 213 214 // Determines if the hold tone is starting or stopping. 215 boolean playTone = ((Boolean) (heldInfo.second)).booleanValue(); 216 217 // Determine which connection the hold tone is stopping or starting for 218 com.android.internal.telephony.Connection heldConnection = heldInfo.first; 219 220 // Only start or stop the hold tone if this is the connection which is starting 221 // or stopping the hold tone. 222 if (heldConnection == mOriginalConnection) { 223 // If starting the hold tone, send a connection event to Telecom which will 224 // cause it to play the on hold tone. 225 if (playTone) { 226 sendConnectionEvent(EVENT_ON_HOLD_TONE_START, null); 227 } else { 228 sendConnectionEvent(EVENT_ON_HOLD_TONE_END, null); 229 } 230 } 231 break; 232 233 case MSG_CDMA_VOICE_PRIVACY_ON: 234 Log.d(this, "MSG_CDMA_VOICE_PRIVACY_ON received"); 235 setCdmaVoicePrivacy(true); 236 break; 237 case MSG_CDMA_VOICE_PRIVACY_OFF: 238 Log.d(this, "MSG_CDMA_VOICE_PRIVACY_OFF received"); 239 setCdmaVoicePrivacy(false); 240 break; 241 } 242 } 243 }; 244 245 /** 246 * @return {@code true} if carrier video conferencing is supported, {@code false} otherwise. 247 */ isCarrierVideoConferencingSupported()248 public boolean isCarrierVideoConferencingSupported() { 249 return mIsCarrierVideoConferencingSupported; 250 } 251 252 /** 253 * A listener/callback mechanism that is specific communication from TelephonyConnections 254 * to TelephonyConnectionService (for now). It is more specific that Connection.Listener 255 * because it is only exposed in Telephony. 256 */ 257 public abstract static class TelephonyConnectionListener { onOriginalConnectionConfigured(TelephonyConnection c)258 public void onOriginalConnectionConfigured(TelephonyConnection c) {} onOriginalConnectionRetry(TelephonyConnection c)259 public void onOriginalConnectionRetry(TelephonyConnection c) {} 260 } 261 262 private final PostDialListener mPostDialListener = new PostDialListener() { 263 @Override 264 public void onPostDialWait() { 265 Log.v(TelephonyConnection.this, "onPostDialWait"); 266 if (mOriginalConnection != null) { 267 setPostDialWait(mOriginalConnection.getRemainingPostDialString()); 268 } 269 } 270 271 @Override 272 public void onPostDialChar(char c) { 273 Log.v(TelephonyConnection.this, "onPostDialChar: %s", c); 274 if (mOriginalConnection != null) { 275 setNextPostDialChar(c); 276 } 277 } 278 }; 279 280 /** 281 * Listener for listening to events in the {@link com.android.internal.telephony.Connection}. 282 */ 283 private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener = 284 new com.android.internal.telephony.Connection.ListenerBase() { 285 @Override 286 public void onVideoStateChanged(int videoState) { 287 mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState).sendToTarget(); 288 } 289 290 /* 291 * The {@link com.android.internal.telephony.Connection} has reported a change in 292 * connection capability. 293 * @param capabilities bit mask containing voice or video or both capabilities. 294 */ 295 @Override 296 public void onConnectionCapabilitiesChanged(int capabilities) { 297 mHandler.obtainMessage(MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES, 298 capabilities, 0).sendToTarget(); 299 } 300 301 /** 302 * The {@link com.android.internal.telephony.Connection} has reported a change in the 303 * video call provider. 304 * 305 * @param videoProvider The video call provider. 306 */ 307 @Override 308 public void onVideoProviderChanged(VideoProvider videoProvider) { 309 mHandler.obtainMessage(MSG_SET_VIDEO_PROVIDER, videoProvider).sendToTarget(); 310 } 311 312 /** 313 * Used by {@link com.android.internal.telephony.Connection} to report a change in whether 314 * the call is being made over a wifi network. 315 * 316 * @param isWifi True if call is made over wifi. 317 */ 318 @Override 319 public void onWifiChanged(boolean isWifi) { 320 setWifi(isWifi); 321 } 322 323 /** 324 * Used by the {@link com.android.internal.telephony.Connection} to report a change in the 325 * audio quality for the current call. 326 * 327 * @param audioQuality The audio quality. 328 */ 329 @Override 330 public void onAudioQualityChanged(int audioQuality) { 331 mHandler.obtainMessage(MSG_SET_AUDIO_QUALITY, audioQuality).sendToTarget(); 332 } 333 /** 334 * Handles a change in the state of conference participant(s), as reported by the 335 * {@link com.android.internal.telephony.Connection}. 336 * 337 * @param participants The participant(s) which changed. 338 */ 339 @Override 340 public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) { 341 mHandler.obtainMessage(MSG_SET_CONFERENCE_PARTICIPANTS, participants).sendToTarget(); 342 } 343 344 /* 345 * Handles a change to the multiparty state for this connection. 346 * 347 * @param isMultiParty {@code true} if the call became multiparty, {@code false} 348 * otherwise. 349 */ 350 @Override 351 public void onMultipartyStateChanged(boolean isMultiParty) { 352 handleMultipartyStateChange(isMultiParty); 353 } 354 355 /** 356 * Handles the event that the request to merge calls failed. 357 */ 358 @Override 359 public void onConferenceMergedFailed() { 360 handleConferenceMergeFailed(); 361 } 362 363 @Override 364 public void onExtrasChanged(Bundle extras) { 365 mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, extras).sendToTarget(); 366 } 367 368 /** 369 * Handles the phone exiting ECM mode by updating the connection capabilities. During an 370 * ongoing call, if ECM mode is exited, we will re-enable mute for CDMA calls. 371 */ 372 @Override 373 public void onExitedEcmMode() { 374 handleExitedEcmMode(); 375 } 376 377 /** 378 * Called from {@link ImsPhoneCallTracker} when a request to pull an external call has 379 * failed. 380 * @param externalConnection 381 */ 382 @Override 383 public void onCallPullFailed(com.android.internal.telephony.Connection externalConnection) { 384 if (externalConnection == null) { 385 return; 386 } 387 388 Log.i(this, "onCallPullFailed - pull failed; swapping back to call: %s", 389 externalConnection); 390 391 // Inform the InCallService of the fact that the call pull failed (it may choose to 392 // display a message informing the user of the pull failure). 393 sendConnectionEvent(Connection.EVENT_CALL_PULL_FAILED, null); 394 395 // Swap the ImsPhoneConnection we used to do the pull for the ImsExternalConnection 396 // which originally represented the call. 397 setOriginalConnection(externalConnection); 398 399 // Set our state to active again since we're no longer pulling. 400 setActiveInternal(); 401 } 402 403 /** 404 * Called from {@link ImsPhoneCallTracker} when a handover to WIFI has failed. 405 */ 406 @Override 407 public void onHandoverToWifiFailed() { 408 sendConnectionEvent(TelephonyManager.EVENT_HANDOVER_TO_WIFI_FAILED, null); 409 } 410 411 /** 412 * Informs the {@link android.telecom.ConnectionService} of a connection event raised by the 413 * original connection. 414 * @param event The connection event. 415 * @param extras The extras. 416 */ 417 @Override 418 public void onConnectionEvent(String event, Bundle extras) { 419 sendConnectionEvent(event, extras); 420 } 421 }; 422 423 protected com.android.internal.telephony.Connection mOriginalConnection; 424 private Call.State mConnectionState = Call.State.IDLE; 425 private Bundle mOriginalConnectionExtras = new Bundle(); 426 private boolean mIsStateOverridden = false; 427 private Call.State mOriginalConnectionState = Call.State.IDLE; 428 private Call.State mConnectionOverriddenState = Call.State.IDLE; 429 430 private boolean mWasImsConnection; 431 432 /** 433 * Tracks the multiparty state of the ImsCall so that changes in the bit state can be detected. 434 */ 435 private boolean mIsMultiParty = false; 436 437 /** 438 * The {@link com.android.internal.telephony.Connection} capabilities associated with the 439 * current {@link #mOriginalConnection}. 440 */ 441 private int mOriginalConnectionCapabilities; 442 443 /** 444 * Determines if the {@link TelephonyConnection} is using wifi. 445 * This is used when {@link TelephonyConnection#updateConnectionProperties()} is called to 446 * indicate whether a call has the {@link Connection#PROPERTY_WIFI} property. 447 */ 448 private boolean mIsWifi; 449 450 /** 451 * Determines the audio quality is high for the {@link TelephonyConnection}. 452 * This is used when {@link TelephonyConnection#updateConnectionProperties}} is called to 453 * indicate whether a call has the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property. 454 */ 455 private boolean mHasHighDefAudio; 456 457 /** 458 * Indicates that the connection should be treated as an emergency call because the 459 * number dialed matches an internal list of emergency numbers. Does not guarantee whether 460 * the network will treat the call as an emergency call. 461 */ 462 private boolean mTreatAsEmergencyCall; 463 464 /** 465 * For video calls, indicates whether the outgoing video for the call can be paused using 466 * the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. 467 */ 468 private boolean mIsVideoPauseSupported; 469 470 /** 471 * Indicates whether this connection supports being a part of a conference.. 472 */ 473 private boolean mIsConferenceSupported; 474 475 /** 476 * Indicates whether the carrier supports video conferencing; captures the current state of the 477 * carrier config 478 * {@link android.telephony.CarrierConfigManager#KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL}. 479 */ 480 private boolean mIsCarrierVideoConferencingSupported; 481 482 /** 483 * Indicates whether or not this connection has CDMA Enhanced Voice Privacy enabled. 484 */ 485 private boolean mIsCdmaVoicePrivacyEnabled; 486 487 /** 488 * Indicates whether this call is an outgoing call. 489 */ 490 protected final boolean mIsOutgoing; 491 492 /** 493 * Listeners to our TelephonyConnection specific callbacks 494 */ 495 private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap( 496 new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1)); 497 TelephonyConnection(com.android.internal.telephony.Connection originalConnection, String callId, boolean isOutgoingCall)498 protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection, 499 String callId, boolean isOutgoingCall) { 500 mIsOutgoing = isOutgoingCall; 501 setTelecomCallId(callId); 502 if (originalConnection != null) { 503 setOriginalConnection(originalConnection); 504 } 505 } 506 507 /** 508 * Creates a clone of the current {@link TelephonyConnection}. 509 * 510 * @return The clone. 511 */ cloneConnection()512 public abstract TelephonyConnection cloneConnection(); 513 514 @Override onCallAudioStateChanged(CallAudioState audioState)515 public void onCallAudioStateChanged(CallAudioState audioState) { 516 // TODO: update TTY mode. 517 if (getPhone() != null) { 518 getPhone().setEchoSuppressionEnabled(); 519 } 520 } 521 522 @Override onStateChanged(int state)523 public void onStateChanged(int state) { 524 Log.v(this, "onStateChanged, state: " + Connection.stateToString(state)); 525 updateStatusHints(); 526 } 527 528 @Override onDisconnect()529 public void onDisconnect() { 530 Log.v(this, "onDisconnect"); 531 hangup(android.telephony.DisconnectCause.LOCAL); 532 } 533 534 /** 535 * Notifies this Connection of a request to disconnect a participant of the conference managed 536 * by the connection. 537 * 538 * @param endpoint the {@link Uri} of the participant to disconnect. 539 */ 540 @Override onDisconnectConferenceParticipant(Uri endpoint)541 public void onDisconnectConferenceParticipant(Uri endpoint) { 542 Log.v(this, "onDisconnectConferenceParticipant %s", endpoint); 543 544 if (mOriginalConnection == null) { 545 return; 546 } 547 548 mOriginalConnection.onDisconnectConferenceParticipant(endpoint); 549 } 550 551 @Override onSeparate()552 public void onSeparate() { 553 Log.v(this, "onSeparate"); 554 if (mOriginalConnection != null) { 555 try { 556 mOriginalConnection.separate(); 557 } catch (CallStateException e) { 558 Log.e(this, e, "Call to Connection.separate failed with exception"); 559 } 560 } 561 } 562 563 @Override onAbort()564 public void onAbort() { 565 Log.v(this, "onAbort"); 566 hangup(android.telephony.DisconnectCause.LOCAL); 567 } 568 569 @Override onHold()570 public void onHold() { 571 performHold(); 572 } 573 574 @Override onUnhold()575 public void onUnhold() { 576 performUnhold(); 577 } 578 579 @Override onAnswer(int videoState)580 public void onAnswer(int videoState) { 581 Log.v(this, "onAnswer"); 582 if (isValidRingingCall() && getPhone() != null) { 583 try { 584 getPhone().acceptCall(videoState); 585 } catch (CallStateException e) { 586 Log.e(this, e, "Failed to accept call."); 587 } 588 } 589 } 590 591 @Override onReject()592 public void onReject() { 593 Log.v(this, "onReject"); 594 if (isValidRingingCall()) { 595 hangup(android.telephony.DisconnectCause.INCOMING_REJECTED); 596 } 597 super.onReject(); 598 } 599 600 @Override onPostDialContinue(boolean proceed)601 public void onPostDialContinue(boolean proceed) { 602 Log.v(this, "onPostDialContinue, proceed: " + proceed); 603 if (mOriginalConnection != null) { 604 if (proceed) { 605 mOriginalConnection.proceedAfterWaitChar(); 606 } else { 607 mOriginalConnection.cancelPostDial(); 608 } 609 } 610 } 611 612 /** 613 * Handles requests to pull an external call. 614 */ 615 @Override onPullExternalCall()616 public void onPullExternalCall() { 617 if ((getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) != 618 Connection.PROPERTY_IS_EXTERNAL_CALL) { 619 Log.w(this, "onPullExternalCall - cannot pull non-external call"); 620 return; 621 } 622 623 if (mOriginalConnection != null) { 624 mOriginalConnection.pullExternalCall(); 625 } 626 } 627 performHold()628 public void performHold() { 629 Log.v(this, "performHold"); 630 // TODO: Can dialing calls be put on hold as well since they take up the 631 // foreground call slot? 632 if (Call.State.ACTIVE == mConnectionState) { 633 Log.v(this, "Holding active call"); 634 try { 635 Phone phone = mOriginalConnection.getCall().getPhone(); 636 Call ringingCall = phone.getRingingCall(); 637 638 // Although the method says switchHoldingAndActive, it eventually calls a RIL method 639 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put 640 // a call on hold while a call-waiting call exists, it'll end up accepting the 641 // call-waiting call, which is bad if that was not the user's intention. We are 642 // cheating here and simply skipping it because we know any attempt to hold a call 643 // while a call-waiting call is happening is likely a request from Telecom prior to 644 // accepting the call-waiting call. 645 // TODO: Investigate a better solution. It would be great here if we 646 // could "fake" hold by silencing the audio and microphone streams for this call 647 // instead of actually putting it on hold. 648 if (ringingCall.getState() != Call.State.WAITING) { 649 phone.switchHoldingAndActive(); 650 } 651 652 // TODO: Cdma calls are slightly different. 653 } catch (CallStateException e) { 654 Log.e(this, e, "Exception occurred while trying to put call on hold."); 655 } 656 } else { 657 Log.w(this, "Cannot put a call that is not currently active on hold."); 658 } 659 } 660 performUnhold()661 public void performUnhold() { 662 Log.v(this, "performUnhold"); 663 if (Call.State.HOLDING == mConnectionState) { 664 try { 665 // Here's the deal--Telephony hold/unhold is weird because whenever there exists 666 // more than one call, one of them must always be active. In other words, if you 667 // have an active call and holding call, and you put the active call on hold, it 668 // will automatically activate the holding call. This is weird with how Telecom 669 // sends its commands. When a user opts to "unhold" a background call, telecom 670 // issues hold commands to all active calls, and then the unhold command to the 671 // background call. This means that we get two commands...each of which reduces to 672 // switchHoldingAndActive(). The result is that they simply cancel each other out. 673 // To fix this so that it works well with telecom we add a minor hack. If we 674 // have one telephony call, everything works as normally expected. But if we have 675 // two or more calls, we will ignore all requests to "unhold" knowing that the hold 676 // requests already do what we want. If you've read up to this point, I'm very sorry 677 // that we are doing this. I didn't think of a better solution that wouldn't also 678 // make the Telecom APIs very ugly. 679 680 if (!hasMultipleTopLevelCalls()) { 681 mOriginalConnection.getCall().getPhone().switchHoldingAndActive(); 682 } else { 683 Log.i(this, "Skipping unhold command for %s", this); 684 } 685 } catch (CallStateException e) { 686 Log.e(this, e, "Exception occurred while trying to release call from hold."); 687 } 688 } else { 689 Log.w(this, "Cannot release a call that is not already on hold from hold."); 690 } 691 } 692 performConference(Connection otherConnection)693 public void performConference(Connection otherConnection) { 694 Log.d(this, "performConference - %s", this); 695 if (getPhone() != null) { 696 try { 697 // We dont use the "other" connection because there is no concept of that in the 698 // implementation of calls inside telephony. Basically, you can "conference" and it 699 // will conference with the background call. We know that otherConnection is the 700 // background call because it would never have called setConferenceableConnections() 701 // otherwise. 702 getPhone().conference(); 703 } catch (CallStateException e) { 704 Log.e(this, e, "Failed to conference call."); 705 } 706 } 707 } 708 709 /** 710 * Builds connection capabilities common to all TelephonyConnections. Namely, apply IMS-based 711 * capabilities. 712 */ buildConnectionCapabilities()713 protected int buildConnectionCapabilities() { 714 int callCapabilities = 0; 715 if (mOriginalConnection != null && mOriginalConnection.isIncoming()) { 716 callCapabilities |= CAPABILITY_SPEED_UP_MT_AUDIO; 717 } 718 if (!shouldTreatAsEmergencyCall() && isImsConnection() && canHoldImsCalls()) { 719 callCapabilities |= CAPABILITY_SUPPORT_HOLD; 720 if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) { 721 callCapabilities |= CAPABILITY_HOLD; 722 } 723 } 724 725 return callCapabilities; 726 } 727 updateConnectionCapabilities()728 protected final void updateConnectionCapabilities() { 729 int newCapabilities = buildConnectionCapabilities(); 730 731 newCapabilities = applyOriginalConnectionCapabilities(newCapabilities); 732 newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PAUSE_VIDEO, 733 mIsVideoPauseSupported && isVideoCapable()); 734 newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PULL_CALL, 735 isExternalConnection() && isPullable()); 736 newCapabilities = applyConferenceTerminationCapabilities(newCapabilities); 737 738 if (getConnectionCapabilities() != newCapabilities) { 739 setConnectionCapabilities(newCapabilities); 740 } 741 } 742 buildConnectionProperties()743 protected int buildConnectionProperties() { 744 int connectionProperties = 0; 745 746 // If the phone is in ECM mode, mark the call to indicate that the callback number should be 747 // shown. 748 Phone phone = getPhone(); 749 if (phone != null && phone.isInEcm()) { 750 connectionProperties |= PROPERTY_EMERGENCY_CALLBACK_MODE; 751 } 752 753 return connectionProperties; 754 } 755 756 /** 757 * Updates the properties of the connection. 758 */ updateConnectionProperties()759 protected final void updateConnectionProperties() { 760 int newProperties = buildConnectionProperties(); 761 762 newProperties = changeBitmask(newProperties, PROPERTY_HIGH_DEF_AUDIO, 763 hasHighDefAudioProperty()); 764 newProperties = changeBitmask(newProperties, PROPERTY_WIFI, mIsWifi); 765 newProperties = changeBitmask(newProperties, PROPERTY_IS_EXTERNAL_CALL, 766 isExternalConnection()); 767 newProperties = changeBitmask(newProperties, PROPERTY_HAS_CDMA_VOICE_PRIVACY, 768 mIsCdmaVoicePrivacyEnabled); 769 770 if (getConnectionProperties() != newProperties) { 771 setConnectionProperties(newProperties); 772 } 773 } 774 updateAddress()775 protected final void updateAddress() { 776 updateConnectionCapabilities(); 777 updateConnectionProperties(); 778 if (mOriginalConnection != null) { 779 Uri address = getAddressFromNumber(mOriginalConnection.getAddress()); 780 int presentation = mOriginalConnection.getNumberPresentation(); 781 if (!Objects.equals(address, getAddress()) || 782 presentation != getAddressPresentation()) { 783 Log.v(this, "updateAddress, address changed"); 784 if ((getConnectionProperties() & PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0) { 785 address = null; 786 } 787 setAddress(address, presentation); 788 } 789 790 String name = filterCnapName(mOriginalConnection.getCnapName()); 791 int namePresentation = mOriginalConnection.getCnapNamePresentation(); 792 if (!Objects.equals(name, getCallerDisplayName()) || 793 namePresentation != getCallerDisplayNamePresentation()) { 794 Log.v(this, "updateAddress, caller display name changed"); 795 setCallerDisplayName(name, namePresentation); 796 } 797 798 if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) { 799 mTreatAsEmergencyCall = true; 800 } 801 802 // Changing the address of the connection can change whether it is an emergency call or 803 // not, which can impact whether it can be part of a conference. 804 refreshConferenceSupported(); 805 } 806 } 807 onRemovedFromCallService()808 void onRemovedFromCallService() { 809 // Subclass can override this to do cleanup. 810 } 811 setOriginalConnection(com.android.internal.telephony.Connection originalConnection)812 void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) { 813 Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); 814 clearOriginalConnection(); 815 mOriginalConnectionExtras.clear(); 816 mOriginalConnection = originalConnection; 817 mOriginalConnection.setTelecomCallId(getTelecomCallId()); 818 getPhone().registerForPreciseCallStateChanged( 819 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null); 820 getPhone().registerForHandoverStateChanged( 821 mHandler, MSG_HANDOVER_STATE_CHANGED, null); 822 getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null); 823 getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null); 824 getPhone().registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null); 825 getPhone().registerForOnHoldTone(mHandler, MSG_ON_HOLD_TONE, null); 826 getPhone().registerForInCallVoicePrivacyOn(mHandler, MSG_CDMA_VOICE_PRIVACY_ON, null); 827 getPhone().registerForInCallVoicePrivacyOff(mHandler, MSG_CDMA_VOICE_PRIVACY_OFF, null); 828 mOriginalConnection.addPostDialListener(mPostDialListener); 829 mOriginalConnection.addListener(mOriginalConnectionListener); 830 831 // Set video state and capabilities 832 setVideoState(mOriginalConnection.getVideoState()); 833 setOriginalConnectionCapabilities(mOriginalConnection.getConnectionCapabilities()); 834 setWifi(mOriginalConnection.isWifi()); 835 setAudioModeIsVoip(mOriginalConnection.getAudioModeIsVoip()); 836 setVideoProvider(mOriginalConnection.getVideoProvider()); 837 setAudioQuality(mOriginalConnection.getAudioQuality()); 838 setTechnologyTypeExtra(); 839 840 // Post update of extras to the handler; extras are updated via the handler to ensure thread 841 // safety. The Extras Bundle is cloned in case the original extras are modified while they 842 // are being added to mOriginalConnectionExtras in updateExtras. 843 Bundle connExtras = mOriginalConnection.getConnectionExtras(); 844 mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, connExtras == null ? null : 845 new Bundle(connExtras)).sendToTarget(); 846 847 if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) { 848 mTreatAsEmergencyCall = true; 849 } 850 851 if (isImsConnection()) { 852 mWasImsConnection = true; 853 } 854 mIsMultiParty = mOriginalConnection.isMultiparty(); 855 856 Bundle extrasToPut = new Bundle(); 857 List<String> extrasToRemove = new ArrayList<>(); 858 if (mOriginalConnection.isActiveCallDisconnectedOnAnswer()) { 859 extrasToPut.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true); 860 } else { 861 extrasToRemove.add(Connection.EXTRA_ANSWERING_DROPS_FG_CALL); 862 } 863 864 if (shouldSetDisableAddCallExtra()) { 865 extrasToPut.putBoolean(Connection.EXTRA_DISABLE_ADD_CALL, true); 866 } else { 867 extrasToRemove.add(Connection.EXTRA_DISABLE_ADD_CALL); 868 } 869 putExtras(extrasToPut); 870 removeExtras(extrasToRemove); 871 872 // updateState can set mOriginalConnection to null if its state is DISCONNECTED, so this 873 // should be executed *after* the above setters have run. 874 updateState(); 875 if (mOriginalConnection == null) { 876 Log.w(this, "original Connection was nulled out as part of setOriginalConnection. " + 877 originalConnection); 878 } 879 880 fireOnOriginalConnectionConfigured(); 881 } 882 883 /** 884 * Filters the CNAP name to not include a list of names that are unhelpful to the user for 885 * Caller ID purposes. 886 */ filterCnapName(final String cnapName)887 private String filterCnapName(final String cnapName) { 888 if (cnapName == null) { 889 return null; 890 } 891 PersistableBundle carrierConfig = getCarrierConfig(); 892 String[] filteredCnapNames = null; 893 if (carrierConfig != null) { 894 filteredCnapNames = carrierConfig.getStringArray( 895 CarrierConfigManager.KEY_FILTERED_CNAP_NAMES_STRING_ARRAY); 896 } 897 if (filteredCnapNames != null) { 898 long cnapNameMatches = Arrays.asList(filteredCnapNames) 899 .stream() 900 .filter(filteredCnapName -> filteredCnapName.equals(cnapName.toUpperCase())) 901 .count(); 902 if (cnapNameMatches > 0) { 903 Log.i(this, "filterCnapName: Filtered CNAP Name: " + cnapName); 904 return ""; 905 } 906 } 907 return cnapName; 908 } 909 910 /** 911 * Sets the EXTRA_CALL_TECHNOLOGY_TYPE extra on the connection to report back to Telecom. 912 */ setTechnologyTypeExtra()913 private void setTechnologyTypeExtra() { 914 if (getPhone() != null) { 915 putExtra(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, getPhone().getPhoneType()); 916 } 917 } 918 refreshDisableAddCall()919 private void refreshDisableAddCall() { 920 if (shouldSetDisableAddCallExtra()) { 921 putExtra(Connection.EXTRA_DISABLE_ADD_CALL, true); 922 } else { 923 removeExtras(Connection.EXTRA_DISABLE_ADD_CALL); 924 } 925 } 926 shouldSetDisableAddCallExtra()927 private boolean shouldSetDisableAddCallExtra() { 928 boolean carrierShouldAllowAddCall = mOriginalConnection.shouldAllowAddCallDuringVideoCall(); 929 if (carrierShouldAllowAddCall) { 930 return false; 931 } 932 Phone phone = getPhone(); 933 if (phone == null) { 934 return false; 935 } 936 boolean isCurrentVideoCall = false; 937 boolean wasVideoCall = false; 938 boolean isVowifiEnabled = false; 939 if (phone instanceof ImsPhone) { 940 ImsPhone imsPhone = (ImsPhone) phone; 941 if (imsPhone.getForegroundCall() != null 942 && imsPhone.getForegroundCall().getImsCall() != null) { 943 ImsCall call = imsPhone.getForegroundCall().getImsCall(); 944 isCurrentVideoCall = call.isVideoCall(); 945 wasVideoCall = call.wasVideoCall(); 946 } 947 948 isVowifiEnabled = ImsUtil.isWfcEnabled(phone.getContext()); 949 } 950 951 if (isCurrentVideoCall) { 952 return true; 953 } else if (wasVideoCall && mIsWifi && !isVowifiEnabled) { 954 return true; 955 } 956 return false; 957 } 958 hasHighDefAudioProperty()959 private boolean hasHighDefAudioProperty() { 960 if (!mHasHighDefAudio) { 961 return false; 962 } 963 964 boolean isVideoCall = VideoProfile.isVideo(getVideoState()); 965 966 PersistableBundle b = getCarrierConfig(); 967 boolean canWifiCallsBeHdAudio = 968 b != null && b.getBoolean(CarrierConfigManager.KEY_WIFI_CALLS_CAN_BE_HD_AUDIO); 969 boolean canVideoCallsBeHdAudio = 970 b != null && b.getBoolean(CarrierConfigManager.KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO); 971 boolean shouldDisplayHdAudio = 972 b != null && b.getBoolean(CarrierConfigManager.KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL); 973 974 if (!shouldDisplayHdAudio) { 975 return false; 976 } 977 978 if (isVideoCall && !canVideoCallsBeHdAudio) { 979 return false; 980 } 981 982 if (mIsWifi && !canWifiCallsBeHdAudio) { 983 return false; 984 } 985 986 return true; 987 } 988 canHoldImsCalls()989 private boolean canHoldImsCalls() { 990 PersistableBundle b = getCarrierConfig(); 991 // Return true if the CarrierConfig is unavailable 992 return !doesDeviceRespectHoldCarrierConfig() || b == null || 993 b.getBoolean(CarrierConfigManager.KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL); 994 } 995 getCarrierConfig()996 private PersistableBundle getCarrierConfig() { 997 Phone phone = getPhone(); 998 if (phone == null) { 999 return null; 1000 } 1001 return PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId()); 1002 } 1003 1004 /** 1005 * Determines if the device will respect the value of the 1006 * {@link CarrierConfigManager#KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL} configuration option. 1007 * 1008 * @return {@code false} if the device always supports holding IMS calls, {@code true} if it 1009 * will use {@link CarrierConfigManager#KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL} to determine if 1010 * hold is supported. 1011 */ doesDeviceRespectHoldCarrierConfig()1012 private boolean doesDeviceRespectHoldCarrierConfig() { 1013 Phone phone = getPhone(); 1014 if (phone == null) { 1015 return true; 1016 } 1017 return phone.getContext().getResources().getBoolean( 1018 com.android.internal.R.bool.config_device_respects_hold_carrier_config); 1019 } 1020 1021 /** 1022 * Whether the connection should be treated as an emergency. 1023 * @return {@code true} if the connection should be treated as an emergency call based 1024 * on the number dialed, {@code false} otherwise. 1025 */ shouldTreatAsEmergencyCall()1026 protected boolean shouldTreatAsEmergencyCall() { 1027 return mTreatAsEmergencyCall; 1028 } 1029 1030 /** 1031 * Un-sets the underlying radio connection. 1032 */ clearOriginalConnection()1033 void clearOriginalConnection() { 1034 if (mOriginalConnection != null) { 1035 if (getPhone() != null) { 1036 getPhone().unregisterForPreciseCallStateChanged(mHandler); 1037 getPhone().unregisterForRingbackTone(mHandler); 1038 getPhone().unregisterForHandoverStateChanged(mHandler); 1039 getPhone().unregisterForDisconnect(mHandler); 1040 getPhone().unregisterForSuppServiceNotification(mHandler); 1041 getPhone().unregisterForOnHoldTone(mHandler); 1042 getPhone().unregisterForInCallVoicePrivacyOn(mHandler); 1043 getPhone().unregisterForInCallVoicePrivacyOff(mHandler); 1044 } 1045 mOriginalConnection.removePostDialListener(mPostDialListener); 1046 mOriginalConnection.removeListener(mOriginalConnectionListener); 1047 mOriginalConnection = null; 1048 } 1049 } 1050 hangup(int telephonyDisconnectCode)1051 protected void hangup(int telephonyDisconnectCode) { 1052 if (mOriginalConnection != null) { 1053 try { 1054 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to 1055 // connection.hangup(). Without this change, the party originating the call will not 1056 // get sent to voicemail if the user opts to reject the call. 1057 if (isValidRingingCall()) { 1058 Call call = getCall(); 1059 if (call != null) { 1060 call.hangup(); 1061 } else { 1062 Log.w(this, "Attempting to hangup a connection without backing call."); 1063 } 1064 } else { 1065 // We still prefer to call connection.hangup() for non-ringing calls in order 1066 // to support hanging-up specific calls within a conference call. If we invoked 1067 // call.hangup() while in a conference, we would end up hanging up the entire 1068 // conference call instead of the specific connection. 1069 mOriginalConnection.hangup(); 1070 } 1071 } catch (CallStateException e) { 1072 Log.e(this, e, "Call to Connection.hangup failed with exception"); 1073 } 1074 } else { 1075 if (getState() == STATE_DISCONNECTED) { 1076 Log.i(this, "hangup called on an already disconnected call!"); 1077 close(); 1078 } else { 1079 // There are a few cases where mOriginalConnection has not been set yet. For 1080 // example, when the radio has to be turned on to make an emergency call, 1081 // mOriginalConnection could not be set for many seconds. 1082 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 1083 android.telephony.DisconnectCause.LOCAL, 1084 "Local Disconnect before connection established.")); 1085 close(); 1086 } 1087 } 1088 } 1089 getOriginalConnection()1090 com.android.internal.telephony.Connection getOriginalConnection() { 1091 return mOriginalConnection; 1092 } 1093 getCall()1094 protected Call getCall() { 1095 if (mOriginalConnection != null) { 1096 return mOriginalConnection.getCall(); 1097 } 1098 return null; 1099 } 1100 getPhone()1101 Phone getPhone() { 1102 Call call = getCall(); 1103 if (call != null) { 1104 return call.getPhone(); 1105 } 1106 return null; 1107 } 1108 hasMultipleTopLevelCalls()1109 private boolean hasMultipleTopLevelCalls() { 1110 int numCalls = 0; 1111 Phone phone = getPhone(); 1112 if (phone != null) { 1113 if (!phone.getRingingCall().isIdle()) { 1114 numCalls++; 1115 } 1116 if (!phone.getForegroundCall().isIdle()) { 1117 numCalls++; 1118 } 1119 if (!phone.getBackgroundCall().isIdle()) { 1120 numCalls++; 1121 } 1122 } 1123 return numCalls > 1; 1124 } 1125 getForegroundConnection()1126 private com.android.internal.telephony.Connection getForegroundConnection() { 1127 if (getPhone() != null) { 1128 return getPhone().getForegroundCall().getEarliestConnection(); 1129 } 1130 return null; 1131 } 1132 1133 /** 1134 * Checks for and returns the list of conference participants 1135 * associated with this connection. 1136 */ getConferenceParticipants()1137 public List<ConferenceParticipant> getConferenceParticipants() { 1138 if (mOriginalConnection == null) { 1139 Log.v(this, "Null mOriginalConnection, cannot get conf participants."); 1140 return null; 1141 } 1142 return mOriginalConnection.getConferenceParticipants(); 1143 } 1144 1145 /** 1146 * Checks to see the original connection corresponds to an active incoming call. Returns false 1147 * if there is no such actual call, or if the associated call is not incoming (See 1148 * {@link Call.State#isRinging}). 1149 */ isValidRingingCall()1150 private boolean isValidRingingCall() { 1151 if (getPhone() == null) { 1152 Log.v(this, "isValidRingingCall, phone is null"); 1153 return false; 1154 } 1155 1156 Call ringingCall = getPhone().getRingingCall(); 1157 if (!ringingCall.getState().isRinging()) { 1158 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 1159 return false; 1160 } 1161 1162 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 1163 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 1164 return false; 1165 } 1166 1167 Log.v(this, "isValidRingingCall, returning true"); 1168 return true; 1169 } 1170 1171 // Make sure the extras being passed into this method is a COPY of the original extras Bundle. 1172 // We do not want the extras to be cleared or modified during mOriginalConnectionExtras.putAll 1173 // below. updateExtras(Bundle extras)1174 protected void updateExtras(Bundle extras) { 1175 if (mOriginalConnection != null) { 1176 if (extras != null) { 1177 // Check if extras have changed and need updating. 1178 if (!areBundlesEqual(mOriginalConnectionExtras, extras)) { 1179 if (Log.DEBUG) { 1180 Log.d(TelephonyConnection.this, "Updating extras:"); 1181 for (String key : extras.keySet()) { 1182 Object value = extras.get(key); 1183 if (value instanceof String) { 1184 Log.d(this, "updateExtras Key=" + Log.pii(key) + 1185 " value=" + Log.pii((String)value)); 1186 } 1187 } 1188 } 1189 mOriginalConnectionExtras.clear(); 1190 1191 mOriginalConnectionExtras.putAll(extras); 1192 1193 // Remap any string extras that have a remapping defined. 1194 for (String key : mOriginalConnectionExtras.keySet()) { 1195 if (sExtrasMap.containsKey(key)) { 1196 String newKey = sExtrasMap.get(key); 1197 mOriginalConnectionExtras.putString(newKey, extras.getString(key)); 1198 mOriginalConnectionExtras.remove(key); 1199 } 1200 } 1201 1202 // Ensure extras are propagated to Telecom. 1203 putExtras(mOriginalConnectionExtras); 1204 } else { 1205 Log.d(this, "Extras update not required"); 1206 } 1207 } else { 1208 Log.d(this, "updateExtras extras: " + Log.pii(extras)); 1209 } 1210 } 1211 } 1212 areBundlesEqual(Bundle extras, Bundle newExtras)1213 private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) { 1214 if (extras == null || newExtras == null) { 1215 return extras == newExtras; 1216 } 1217 1218 if (extras.size() != newExtras.size()) { 1219 return false; 1220 } 1221 1222 for(String key : extras.keySet()) { 1223 if (key != null) { 1224 final Object value = extras.get(key); 1225 final Object newValue = newExtras.get(key); 1226 if (!Objects.equals(value, newValue)) { 1227 return false; 1228 } 1229 } 1230 } 1231 return true; 1232 } 1233 setStateOverride(Call.State state)1234 void setStateOverride(Call.State state) { 1235 mIsStateOverridden = true; 1236 mConnectionOverriddenState = state; 1237 // Need to keep track of the original connection's state before override. 1238 mOriginalConnectionState = mOriginalConnection.getState(); 1239 updateStateInternal(); 1240 } 1241 resetStateOverride()1242 void resetStateOverride() { 1243 mIsStateOverridden = false; 1244 updateStateInternal(); 1245 } 1246 updateStateInternal()1247 void updateStateInternal() { 1248 if (mOriginalConnection == null) { 1249 return; 1250 } 1251 Call.State newState; 1252 // If the state is overridden and the state of the original connection hasn't changed since, 1253 // then we continue in the overridden state, else we go to the original connection's state. 1254 if (mIsStateOverridden && mOriginalConnectionState == mOriginalConnection.getState()) { 1255 newState = mConnectionOverriddenState; 1256 } else { 1257 newState = mOriginalConnection.getState(); 1258 } 1259 Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState, this); 1260 1261 if (mConnectionState != newState) { 1262 mConnectionState = newState; 1263 switch (newState) { 1264 case IDLE: 1265 break; 1266 case ACTIVE: 1267 setActiveInternal(); 1268 break; 1269 case HOLDING: 1270 setOnHold(); 1271 break; 1272 case DIALING: 1273 case ALERTING: 1274 if (mOriginalConnection != null && mOriginalConnection.isPulledCall()) { 1275 setPulling(); 1276 } else { 1277 setDialing(); 1278 } 1279 break; 1280 case INCOMING: 1281 case WAITING: 1282 setRinging(); 1283 break; 1284 case DISCONNECTED: 1285 // We can get into a situation where the radio wants us to redial the same 1286 // emergency call on the other available slot. This will not set the state to 1287 // disconnected and will instead tell the TelephonyConnectionService to create 1288 // a new originalConnection using the new Slot. 1289 if (mOriginalConnection.getDisconnectCause() == 1290 DisconnectCause.DIALED_ON_WRONG_SLOT) { 1291 fireOnOriginalConnectionRetryDial(); 1292 } else { 1293 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 1294 mOriginalConnection.getDisconnectCause(), 1295 mOriginalConnection.getVendorDisconnectCause())); 1296 close(); 1297 } 1298 break; 1299 case DISCONNECTING: 1300 break; 1301 } 1302 } 1303 } 1304 updateState()1305 void updateState() { 1306 if (mOriginalConnection == null) { 1307 return; 1308 } 1309 1310 updateStateInternal(); 1311 updateStatusHints(); 1312 updateConnectionCapabilities(); 1313 updateConnectionProperties(); 1314 updateAddress(); 1315 updateMultiparty(); 1316 } 1317 1318 /** 1319 * Checks for changes to the multiparty bit. If a conference has started, informs listeners. 1320 */ updateMultiparty()1321 private void updateMultiparty() { 1322 if (mOriginalConnection == null) { 1323 return; 1324 } 1325 1326 if (mIsMultiParty != mOriginalConnection.isMultiparty()) { 1327 mIsMultiParty = mOriginalConnection.isMultiparty(); 1328 1329 if (mIsMultiParty) { 1330 notifyConferenceStarted(); 1331 } 1332 } 1333 } 1334 1335 /** 1336 * Handles a failure when merging calls into a conference. 1337 * {@link com.android.internal.telephony.Connection.Listener#onConferenceMergedFailed()} 1338 * listener. 1339 */ handleConferenceMergeFailed()1340 private void handleConferenceMergeFailed(){ 1341 mHandler.obtainMessage(MSG_CONFERENCE_MERGE_FAILED).sendToTarget(); 1342 } 1343 1344 /** 1345 * Handles requests to update the multiparty state received via the 1346 * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)} 1347 * listener. 1348 * <p> 1349 * Note: We post this to the mHandler to ensure that if a conference must be created as a 1350 * result of the multiparty state change, the conference creation happens on the correct 1351 * thread. This ensures that the thread check in 1352 * {@link com.android.internal.telephony.Phone#checkCorrectThread(android.os.Handler)} 1353 * does not fire. 1354 * 1355 * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise. 1356 */ handleMultipartyStateChange(boolean isMultiParty)1357 private void handleMultipartyStateChange(boolean isMultiParty) { 1358 Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N"); 1359 mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget(); 1360 } 1361 setActiveInternal()1362 private void setActiveInternal() { 1363 if (getState() == STATE_ACTIVE) { 1364 Log.w(this, "Should not be called if this is already ACTIVE"); 1365 return; 1366 } 1367 1368 // When we set a call to active, we need to make sure that there are no other active 1369 // calls. However, the ordering of state updates to connections can be non-deterministic 1370 // since all connections register for state changes on the phone independently. 1371 // To "optimize", we check here to see if there already exists any active calls. If so, 1372 // we issue an update for those calls first to make sure we only have one top-level 1373 // active call. 1374 if (getConnectionService() != null) { 1375 for (Connection current : getConnectionService().getAllConnections()) { 1376 if (current != this && current instanceof TelephonyConnection) { 1377 TelephonyConnection other = (TelephonyConnection) current; 1378 if (other.getState() == STATE_ACTIVE) { 1379 other.updateState(); 1380 } 1381 } 1382 } 1383 } 1384 setActive(); 1385 } 1386 close()1387 private void close() { 1388 Log.v(this, "close"); 1389 clearOriginalConnection(); 1390 destroy(); 1391 } 1392 1393 /** 1394 * Determines if the current connection is video capable. 1395 * 1396 * A connection is deemed to be video capable if the original connection capabilities state that 1397 * both local and remote video is supported. 1398 * 1399 * @return {@code true} if the connection is video capable, {@code false} otherwise. 1400 */ isVideoCapable()1401 private boolean isVideoCapable() { 1402 return can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL) 1403 && can(mOriginalConnectionCapabilities, 1404 Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 1405 } 1406 1407 /** 1408 * Determines if the current connection is an external connection. 1409 * 1410 * A connection is deemed to be external if the original connection capabilities state that it 1411 * is. 1412 * 1413 * @return {@code true} if the connection is external, {@code false} otherwise. 1414 */ isExternalConnection()1415 private boolean isExternalConnection() { 1416 return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION) 1417 && can(mOriginalConnectionCapabilities, 1418 Capability.IS_EXTERNAL_CONNECTION); 1419 } 1420 1421 /** 1422 * Determines if the current connection is pullable. 1423 * 1424 * A connection is deemed to be pullable if the original connection capabilities state that it 1425 * is. 1426 * 1427 * @return {@code true} if the connection is pullable, {@code false} otherwise. 1428 */ isPullable()1429 private boolean isPullable() { 1430 return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION) 1431 && can(mOriginalConnectionCapabilities, Capability.IS_PULLABLE); 1432 } 1433 1434 /** 1435 * Sets whether or not CDMA enhanced call privacy is enabled for this connection. 1436 */ setCdmaVoicePrivacy(boolean isEnabled)1437 private void setCdmaVoicePrivacy(boolean isEnabled) { 1438 if(mIsCdmaVoicePrivacyEnabled != isEnabled) { 1439 mIsCdmaVoicePrivacyEnabled = isEnabled; 1440 updateConnectionProperties(); 1441 } 1442 } 1443 1444 /** 1445 * Applies capabilities specific to conferences termination to the 1446 * {@code ConnectionCapabilities} bit-mask. 1447 * 1448 * @param capabilities The {@code ConnectionCapabilities} bit-mask. 1449 * @return The capabilities with the IMS conference capabilities applied. 1450 */ applyConferenceTerminationCapabilities(int capabilities)1451 private int applyConferenceTerminationCapabilities(int capabilities) { 1452 int currentCapabilities = capabilities; 1453 1454 // An IMS call cannot be individually disconnected or separated from its parent conference. 1455 // If the call was IMS, even if it hands over to GMS, these capabilities are not supported. 1456 if (!mWasImsConnection) { 1457 currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE; 1458 currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE; 1459 } 1460 1461 return currentCapabilities; 1462 } 1463 1464 /** 1465 * Stores the new original connection capabilities, and applies them to the current connection, 1466 * notifying any listeners as necessary. 1467 * 1468 * @param connectionCapabilities The original connection capabilties. 1469 */ setOriginalConnectionCapabilities(int connectionCapabilities)1470 public void setOriginalConnectionCapabilities(int connectionCapabilities) { 1471 mOriginalConnectionCapabilities = connectionCapabilities; 1472 updateConnectionCapabilities(); 1473 updateConnectionProperties(); 1474 } 1475 1476 /** 1477 * Called to apply the capabilities present in the {@link #mOriginalConnection} to this 1478 * {@link Connection}. Provides a mapping between the capabilities present in the original 1479 * connection (see {@link com.android.internal.telephony.Connection.Capability}) and those in 1480 * this {@link Connection}. 1481 * 1482 * @param capabilities The capabilities bitmask from the {@link Connection}. 1483 * @return the capabilities bitmask with the original connection capabilities remapped and 1484 * applied. 1485 */ applyOriginalConnectionCapabilities(int capabilities)1486 public int applyOriginalConnectionCapabilities(int capabilities) { 1487 // We only support downgrading to audio if both the remote and local side support 1488 // downgrading to audio. 1489 boolean supportsDowngradeToAudio = can(mOriginalConnectionCapabilities, 1490 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL | 1491 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE); 1492 capabilities = changeBitmask(capabilities, 1493 CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO, !supportsDowngradeToAudio); 1494 1495 capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, 1496 can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL)); 1497 1498 capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, 1499 can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL)); 1500 1501 return capabilities; 1502 } 1503 1504 /** 1505 * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset 1506 * the {@link Connection#PROPERTY_WIFI} property. 1507 */ setWifi(boolean isWifi)1508 public void setWifi(boolean isWifi) { 1509 mIsWifi = isWifi; 1510 updateConnectionProperties(); 1511 updateStatusHints(); 1512 refreshDisableAddCall(); 1513 } 1514 1515 /** 1516 * Whether the call is using wifi. 1517 */ isWifi()1518 boolean isWifi() { 1519 return mIsWifi; 1520 } 1521 1522 /** 1523 * @return {@code true} if this is an outgoing call, {@code false} otherwise. 1524 */ isOutgoingCall()1525 boolean isOutgoingCall() { 1526 return mIsOutgoing; 1527 } 1528 1529 /** 1530 * Sets the current call audio quality. Used during rebuild of the properties 1531 * to set or unset the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property. 1532 * 1533 * @param audioQuality The audio quality. 1534 */ setAudioQuality(int audioQuality)1535 public void setAudioQuality(int audioQuality) { 1536 mHasHighDefAudio = audioQuality == 1537 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION; 1538 updateConnectionProperties(); 1539 } 1540 resetStateForConference()1541 void resetStateForConference() { 1542 if (getState() == Connection.STATE_HOLDING) { 1543 resetStateOverride(); 1544 } 1545 } 1546 setHoldingForConference()1547 boolean setHoldingForConference() { 1548 if (getState() == Connection.STATE_ACTIVE) { 1549 setStateOverride(Call.State.HOLDING); 1550 return true; 1551 } 1552 return false; 1553 } 1554 1555 /** 1556 * For video calls, sets whether this connection supports pausing the outgoing video for the 1557 * call using the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. 1558 * 1559 * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise. 1560 */ setVideoPauseSupported(boolean isVideoPauseSupported)1561 public void setVideoPauseSupported(boolean isVideoPauseSupported) { 1562 mIsVideoPauseSupported = isVideoPauseSupported; 1563 } 1564 1565 /** 1566 * @return {@code true} if this connection supports pausing the outgoing video using the 1567 * {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. 1568 */ getVideoPauseSupported()1569 public boolean getVideoPauseSupported() { 1570 return mIsVideoPauseSupported; 1571 } 1572 1573 /** 1574 * Sets whether this connection supports conference calling. 1575 * @param isConferenceSupported {@code true} if conference calling is supported by this 1576 * connection, {@code false} otherwise. 1577 */ setConferenceSupported(boolean isConferenceSupported)1578 public void setConferenceSupported(boolean isConferenceSupported) { 1579 mIsConferenceSupported = isConferenceSupported; 1580 } 1581 1582 /** 1583 * @return {@code true} if this connection supports merging calls into a conference. 1584 */ isConferenceSupported()1585 public boolean isConferenceSupported() { 1586 return mIsConferenceSupported; 1587 } 1588 1589 /** 1590 * Whether the original connection is an IMS connection. 1591 * @return {@code True} if the original connection is an IMS connection, {@code false} 1592 * otherwise. 1593 */ isImsConnection()1594 protected boolean isImsConnection() { 1595 com.android.internal.telephony.Connection originalConnection = getOriginalConnection(); 1596 return originalConnection != null && 1597 originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS; 1598 } 1599 1600 /** 1601 * Whether the original connection was ever an IMS connection, either before or now. 1602 * @return {@code True} if the original connection was ever an IMS connection, {@code false} 1603 * otherwise. 1604 */ wasImsConnection()1605 public boolean wasImsConnection() { 1606 return mWasImsConnection; 1607 } 1608 getAddressFromNumber(String number)1609 private static Uri getAddressFromNumber(String number) { 1610 // Address can be null for blocked calls. 1611 if (number == null) { 1612 number = ""; 1613 } 1614 return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 1615 } 1616 1617 /** 1618 * Changes a capabilities bit-mask to add or remove a capability. 1619 * 1620 * @param bitmask The bit-mask. 1621 * @param bitfield The bit-field to change. 1622 * @param enabled Whether the bit-field should be set or removed. 1623 * @return The bit-mask with the bit-field changed. 1624 */ changeBitmask(int bitmask, int bitfield, boolean enabled)1625 private int changeBitmask(int bitmask, int bitfield, boolean enabled) { 1626 if (enabled) { 1627 return bitmask | bitfield; 1628 } else { 1629 return bitmask & ~bitfield; 1630 } 1631 } 1632 updateStatusHints()1633 private void updateStatusHints() { 1634 boolean isIncoming = isValidRingingCall(); 1635 if (mIsWifi && (isIncoming || getState() == STATE_ACTIVE)) { 1636 int labelId = isIncoming 1637 ? R.string.status_hint_label_incoming_wifi_call 1638 : R.string.status_hint_label_wifi_call; 1639 1640 Context context = getPhone().getContext(); 1641 setStatusHints(new StatusHints( 1642 context.getString(labelId), 1643 Icon.createWithResource( 1644 context.getResources(), 1645 R.drawable.ic_signal_wifi_4_bar_24dp), 1646 null /* extras */)); 1647 } else { 1648 setStatusHints(null); 1649 } 1650 } 1651 1652 /** 1653 * Register a listener for {@link TelephonyConnection} specific triggers. 1654 * @param l The instance of the listener to add 1655 * @return The connection being listened to 1656 */ addTelephonyConnectionListener(TelephonyConnectionListener l)1657 public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) { 1658 mTelephonyListeners.add(l); 1659 // If we already have an original connection, let's call back immediately. 1660 // This would be the case for incoming calls. 1661 if (mOriginalConnection != null) { 1662 fireOnOriginalConnectionConfigured(); 1663 } 1664 return this; 1665 } 1666 1667 /** 1668 * Remove a listener for {@link TelephonyConnection} specific triggers. 1669 * @param l The instance of the listener to remove 1670 * @return The connection being listened to 1671 */ removeTelephonyConnectionListener( TelephonyConnectionListener l)1672 public final TelephonyConnection removeTelephonyConnectionListener( 1673 TelephonyConnectionListener l) { 1674 if (l != null) { 1675 mTelephonyListeners.remove(l); 1676 } 1677 return this; 1678 } 1679 1680 /** 1681 * Fire a callback to the various listeners for when the original connection is 1682 * set in this {@link TelephonyConnection} 1683 */ fireOnOriginalConnectionConfigured()1684 private final void fireOnOriginalConnectionConfigured() { 1685 for (TelephonyConnectionListener l : mTelephonyListeners) { 1686 l.onOriginalConnectionConfigured(this); 1687 } 1688 } 1689 fireOnOriginalConnectionRetryDial()1690 private final void fireOnOriginalConnectionRetryDial() { 1691 for (TelephonyConnectionListener l : mTelephonyListeners) { 1692 l.onOriginalConnectionRetry(this); 1693 } 1694 } 1695 1696 /** 1697 * Handles exiting ECM mode. 1698 */ handleExitedEcmMode()1699 protected void handleExitedEcmMode() { 1700 updateConnectionProperties(); 1701 } 1702 1703 /** 1704 * Determines whether the connection supports conference calling. A connection supports 1705 * conference calling if it: 1706 * 1. Is not an emergency call. 1707 * 2. Carrier supports conference calls. 1708 * 3. If call is a video call, carrier supports video conference calls. 1709 * 4. If call is a wifi call and VoWIFI is disabled and carrier supports merging these calls. 1710 */ refreshConferenceSupported()1711 private void refreshConferenceSupported() { 1712 boolean isVideoCall = VideoProfile.isVideo(getVideoState()); 1713 Phone phone = getPhone(); 1714 boolean isIms = phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS; 1715 boolean isVoWifiEnabled = false; 1716 if (isIms) { 1717 ImsPhone imsPhone = (ImsPhone) phone; 1718 isVoWifiEnabled = ImsUtil.isWfcEnabled(phone.getContext()); 1719 } 1720 PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils 1721 .makePstnPhoneAccountHandle(phone.getDefaultPhone()) 1722 : PhoneUtils.makePstnPhoneAccountHandle(phone); 1723 TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry 1724 .getInstance(getPhone().getContext()); 1725 boolean isConferencingSupported = telecomAccountRegistry 1726 .isMergeCallSupported(phoneAccountHandle); 1727 mIsCarrierVideoConferencingSupported = telecomAccountRegistry 1728 .isVideoConferencingSupported(phoneAccountHandle); 1729 boolean isMergeOfWifiCallsAllowedWhenVoWifiOff = telecomAccountRegistry 1730 .isMergeOfWifiCallsAllowedWhenVoWifiOff(phoneAccountHandle); 1731 1732 Log.v(this, "refreshConferenceSupported : isConfSupp=%b, isVidConfSupp=%b, " + 1733 "isMergeOfWifiAllowed=%b, isWifi=%b, isVoWifiEnabled=%b", isConferencingSupported, 1734 mIsCarrierVideoConferencingSupported, isMergeOfWifiCallsAllowedWhenVoWifiOff, 1735 isWifi(), isVoWifiEnabled); 1736 boolean isConferenceSupported = true; 1737 if (mTreatAsEmergencyCall) { 1738 isConferenceSupported = false; 1739 Log.d(this, "refreshConferenceSupported = false; emergency call"); 1740 } else if (!isConferencingSupported) { 1741 isConferenceSupported = false; 1742 Log.d(this, "refreshConferenceSupported = false; carrier doesn't support conf."); 1743 } else if (isVideoCall && !mIsCarrierVideoConferencingSupported) { 1744 isConferenceSupported = false; 1745 Log.d(this, "refreshConferenceSupported = false; video conf not supported."); 1746 } else if (!isMergeOfWifiCallsAllowedWhenVoWifiOff && isWifi() && !isVoWifiEnabled) { 1747 isConferenceSupported = false; 1748 Log.d(this, 1749 "refreshConferenceSupported = false; can't merge wifi calls when voWifi off."); 1750 } else { 1751 Log.d(this, "refreshConferenceSupported = true."); 1752 } 1753 1754 if (isConferenceSupported != isConferenceSupported()) { 1755 setConferenceSupported(isConferenceSupported); 1756 notifyConferenceSupportedChanged(isConferenceSupported); 1757 } 1758 } 1759 /** 1760 * Provides a mapping from extras keys which may be found in the 1761 * {@link com.android.internal.telephony.Connection} to their equivalents defined in 1762 * {@link android.telecom.Connection}. 1763 * 1764 * @return Map containing key mappings. 1765 */ createExtrasMap()1766 private static Map<String, String> createExtrasMap() { 1767 Map<String, String> result = new HashMap<String, String>(); 1768 result.put(ImsCallProfile.EXTRA_CHILD_NUMBER, 1769 android.telecom.Connection.EXTRA_CHILD_ADDRESS); 1770 result.put(ImsCallProfile.EXTRA_DISPLAY_TEXT, 1771 android.telecom.Connection.EXTRA_CALL_SUBJECT); 1772 return Collections.unmodifiableMap(result); 1773 } 1774 1775 /** 1776 * Creates a string representation of this {@link TelephonyConnection}. Primarily intended for 1777 * use in log statements. 1778 * 1779 * @return String representation of the connection. 1780 */ 1781 @Override toString()1782 public String toString() { 1783 StringBuilder sb = new StringBuilder(); 1784 sb.append("[TelephonyConnection objId:"); 1785 sb.append(System.identityHashCode(this)); 1786 sb.append(" telecomCallID:"); 1787 sb.append(getTelecomCallId()); 1788 sb.append(" type:"); 1789 if (isImsConnection()) { 1790 sb.append("ims"); 1791 } else if (this instanceof com.android.services.telephony.GsmConnection) { 1792 sb.append("gsm"); 1793 } else if (this instanceof CdmaConnection) { 1794 sb.append("cdma"); 1795 } 1796 sb.append(" state:"); 1797 sb.append(Connection.stateToString(getState())); 1798 sb.append(" capabilities:"); 1799 sb.append(capabilitiesToString(getConnectionCapabilities())); 1800 sb.append(" properties:"); 1801 sb.append(propertiesToString(getConnectionProperties())); 1802 sb.append(" address:"); 1803 sb.append(Log.pii(getAddress())); 1804 sb.append(" originalConnection:"); 1805 sb.append(mOriginalConnection); 1806 sb.append(" partOfConf:"); 1807 if (getConference() == null) { 1808 sb.append("N"); 1809 } else { 1810 sb.append("Y"); 1811 } 1812 sb.append(" confSupported:"); 1813 sb.append(mIsConferenceSupported ? "Y" : "N"); 1814 sb.append("]"); 1815 return sb.toString(); 1816 } 1817 } 1818