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