ImsPhoneCallTracker.java revision 27e0378194d55fdcb23f4a3a85dc620a234b5855
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.internal.telephony.imsphone; 18 19import java.io.FileDescriptor; 20import java.io.PrintWriter; 21import java.util.ArrayList; 22import java.util.List; 23 24import android.app.PendingIntent; 25import android.content.BroadcastReceiver; 26import android.content.Context; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.SharedPreferences; 30import android.os.AsyncResult; 31import android.os.Bundle; 32import android.os.Handler; 33import android.os.Message; 34import android.os.PersistableBundle; 35import android.os.Registrant; 36import android.os.RegistrantList; 37import android.os.RemoteException; 38import android.os.SystemProperties; 39import android.provider.Settings; 40import android.telephony.CarrierConfigManager; 41import android.text.TextUtils; 42import android.preference.PreferenceManager; 43import android.telecom.ConferenceParticipant; 44import android.telecom.VideoProfile; 45import android.telephony.DisconnectCause; 46import android.telephony.PhoneNumberUtils; 47import android.telephony.Rlog; 48import android.telephony.ServiceState; 49import android.telephony.SubscriptionManager; 50 51import com.android.ims.ImsCall; 52import com.android.ims.ImsCallProfile; 53import com.android.ims.ImsConfig; 54import com.android.ims.ImsConnectionStateListener; 55import com.android.ims.ImsEcbm; 56import com.android.ims.ImsException; 57import com.android.ims.ImsManager; 58import com.android.ims.ImsReasonInfo; 59import com.android.ims.ImsServiceClass; 60import com.android.ims.ImsSuppServiceNotification; 61import com.android.ims.ImsUtInterface; 62import com.android.ims.internal.IImsVideoCallProvider; 63import com.android.ims.internal.ImsVideoCallProviderWrapper; 64import com.android.internal.telephony.Call; 65import com.android.internal.telephony.CallStateException; 66import com.android.internal.telephony.CallTracker; 67import com.android.internal.telephony.CommandException; 68import com.android.internal.telephony.CommandsInterface; 69import com.android.internal.telephony.Connection; 70import com.android.internal.telephony.Phone; 71import com.android.internal.telephony.PhoneConstants; 72import com.android.internal.telephony.TelephonyEventLog; 73import com.android.internal.telephony.TelephonyProperties; 74import com.android.internal.telephony.gsm.SuppServiceNotification; 75 76/** 77 * {@hide} 78 */ 79public final class ImsPhoneCallTracker extends CallTracker { 80 static final String LOG_TAG = "ImsPhoneCallTracker"; 81 82 private static final boolean DBG = true; 83 84 // When true, dumps the state of ImsPhoneCallTracker after changes to foreground and background 85 // calls. This is helpful for debugging. 86 private static final boolean VERBOSE_STATE_LOGGING = false; /* stopship if true */ 87 88 //Indices map to ImsConfig.FeatureConstants 89 private boolean[] mImsFeatureEnabled = {false, false, false, false, false, false}; 90 private final String[] mImsFeatureStrings = {"VoLTE", "ViLTE", "VoWiFi", "ViWiFi", 91 "UTLTE", "UTWiFi"}; 92 93 private TelephonyEventLog mEventLog; 94 95 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 96 @Override 97 public void onReceive(Context context, Intent intent) { 98 if (intent.getAction().equals(ImsManager.ACTION_IMS_INCOMING_CALL)) { 99 if (DBG) log("onReceive : incoming call intent"); 100 101 if (mImsManager == null) return; 102 103 if (mServiceId < 0) return; 104 105 try { 106 // Network initiated USSD will be treated by mImsUssdListener 107 boolean isUssd = intent.getBooleanExtra(ImsManager.EXTRA_USSD, false); 108 if (isUssd) { 109 if (DBG) log("onReceive : USSD"); 110 mUssdSession = mImsManager.takeCall(mServiceId, intent, mImsUssdListener); 111 if (mUssdSession != null) { 112 mUssdSession.accept(ImsCallProfile.CALL_TYPE_VOICE); 113 } 114 return; 115 } 116 117 boolean isUnknown = intent.getBooleanExtra(ImsManager.EXTRA_IS_UNKNOWN_CALL, 118 false); 119 if (DBG) { 120 log("onReceive : isUnknown = " + isUnknown + 121 " fg = " + mForegroundCall.getState() + 122 " bg = " + mBackgroundCall.getState()); 123 } 124 125 // Normal MT/Unknown call 126 ImsCall imsCall = mImsManager.takeCall(mServiceId, intent, mImsCallListener); 127 ImsPhoneConnection conn = new ImsPhoneConnection(mPhone, imsCall, 128 ImsPhoneCallTracker.this, 129 (isUnknown? mForegroundCall: mRingingCall), isUnknown); 130 addConnection(conn); 131 132 setVideoCallProvider(conn, imsCall); 133 134 mEventLog.writeOnImsCallReceive(imsCall.getSession()); 135 136 if (isUnknown) { 137 mPhone.notifyUnknownConnection(conn); 138 } else { 139 if ((mForegroundCall.getState() != ImsPhoneCall.State.IDLE) || 140 (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE)) { 141 conn.update(imsCall, ImsPhoneCall.State.WAITING); 142 } 143 144 mPhone.notifyNewRingingConnection(conn); 145 mPhone.notifyIncomingRing(); 146 } 147 148 updatePhoneState(); 149 mPhone.notifyPreciseCallStateChanged(); 150 } catch (ImsException e) { 151 loge("onReceive : exception " + e); 152 } catch (RemoteException e) { 153 } 154 } else if (intent.getAction().equals( 155 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) { 156 int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, 157 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 158 if (subId == mPhone.getSubId()) { 159 mAllowEmergencyVideoCalls = isEmergencyVtCallAllowed(subId); 160 log("onReceive : Updating mAllowEmergencyVideoCalls = " + 161 mAllowEmergencyVideoCalls); 162 } 163 } 164 } 165 }; 166 167 //***** Constants 168 169 static final int MAX_CONNECTIONS = 7; 170 static final int MAX_CONNECTIONS_PER_CALL = 5; 171 172 private static final int EVENT_HANGUP_PENDINGMO = 18; 173 private static final int EVENT_RESUME_BACKGROUND = 19; 174 private static final int EVENT_DIAL_PENDINGMO = 20; 175 176 private static final int TIMEOUT_HANGUP_PENDINGMO = 500; 177 178 //***** Instance Variables 179 private ArrayList<ImsPhoneConnection> mConnections = new ArrayList<ImsPhoneConnection>(); 180 private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList(); 181 private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList(); 182 183 final ImsPhoneCall mRingingCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_RINGING); 184 final ImsPhoneCall mForegroundCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_FOREGROUND); 185 final ImsPhoneCall mBackgroundCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_BACKGROUND); 186 final ImsPhoneCall mHandoverCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_HANDOVER); 187 188 private ImsPhoneConnection mPendingMO; 189 private int mClirMode = CommandsInterface.CLIR_DEFAULT; 190 private Object mSyncHold = new Object(); 191 192 private ImsCall mUssdSession = null; 193 private Message mPendingUssd = null; 194 195 ImsPhone mPhone; 196 197 private boolean mDesiredMute = false; // false = mute off 198 private boolean mOnHoldToneStarted = false; 199 200 PhoneConstants.State mState = PhoneConstants.State.IDLE; 201 202 private ImsManager mImsManager; 203 private int mServiceId = -1; 204 205 private Call.SrvccState mSrvccState = Call.SrvccState.NONE; 206 207 private boolean mIsInEmergencyCall = false; 208 209 private int pendingCallClirMode; 210 private int mPendingCallVideoState; 211 private Bundle mPendingIntentExtras; 212 private boolean pendingCallInEcm = false; 213 private boolean mSwitchingFgAndBgCalls = false; 214 private ImsCall mCallExpectedToResume = null; 215 private boolean mAllowEmergencyVideoCalls = false; 216 217 //***** Events 218 219 220 //***** Constructors 221 222 ImsPhoneCallTracker(ImsPhone phone) { 223 this.mPhone = phone; 224 225 mEventLog = TelephonyEventLog.getInstance(mPhone.getContext(), mPhone.getPhoneId()); 226 227 IntentFilter intentfilter = new IntentFilter(); 228 intentfilter.addAction(ImsManager.ACTION_IMS_INCOMING_CALL); 229 intentfilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); 230 mPhone.getContext().registerReceiver(mReceiver, intentfilter); 231 mAllowEmergencyVideoCalls = isEmergencyVtCallAllowed(mPhone.getSubId()); 232 233 Thread t = new Thread() { 234 public void run() { 235 getImsService(); 236 } 237 }; 238 t.start(); 239 } 240 241 private PendingIntent createIncomingCallPendingIntent() { 242 Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL); 243 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 244 return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent, 245 PendingIntent.FLAG_UPDATE_CURRENT); 246 } 247 248 private void getImsService() { 249 if (DBG) log("getImsService"); 250 mImsManager = ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId()); 251 try { 252 mServiceId = mImsManager.open(ImsServiceClass.MMTEL, 253 createIncomingCallPendingIntent(), 254 mImsConnectionStateListener); 255 256 // Get the ECBM interface and set IMSPhone's listener object for notifications 257 getEcbmInterface().setEcbmStateListener(mPhone.mImsEcbmStateListener); 258 if (mPhone.isInEcm()) { 259 // Call exit ECBM which will invoke onECBMExited 260 mPhone.exitEmergencyCallbackMode(); 261 } 262 int mPreferredTtyMode = Settings.Secure.getInt( 263 mPhone.getContext().getContentResolver(), 264 Settings.Secure.PREFERRED_TTY_MODE, 265 Phone.TTY_MODE_OFF); 266 mImsManager.setUiTTYMode(mPhone.getContext(), mServiceId, mPreferredTtyMode, null); 267 268 } catch (ImsException e) { 269 loge("getImsService: " + e); 270 //Leave mImsManager as null, then CallStateException will be thrown when dialing 271 mImsManager = null; 272 } 273 } 274 275 public void dispose() { 276 if (DBG) log("dispose"); 277 mRingingCall.dispose(); 278 mBackgroundCall.dispose(); 279 mForegroundCall.dispose(); 280 mHandoverCall.dispose(); 281 282 clearDisconnected(); 283 mPhone.getContext().unregisterReceiver(mReceiver); 284 } 285 286 @Override 287 protected void finalize() { 288 log("ImsPhoneCallTracker finalized"); 289 } 290 291 //***** Instance Methods 292 293 //***** Public Methods 294 @Override 295 public void registerForVoiceCallStarted(Handler h, int what, Object obj) { 296 Registrant r = new Registrant(h, what, obj); 297 mVoiceCallStartedRegistrants.add(r); 298 } 299 300 @Override 301 public void unregisterForVoiceCallStarted(Handler h) { 302 mVoiceCallStartedRegistrants.remove(h); 303 } 304 305 @Override 306 public void registerForVoiceCallEnded(Handler h, int what, Object obj) { 307 Registrant r = new Registrant(h, what, obj); 308 mVoiceCallEndedRegistrants.add(r); 309 } 310 311 @Override 312 public void unregisterForVoiceCallEnded(Handler h) { 313 mVoiceCallEndedRegistrants.remove(h); 314 } 315 316 Connection 317 dial(String dialString, int videoState, Bundle intentExtras) throws CallStateException { 318 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext()); 319 int oirMode = sp.getInt(Phone.CLIR_KEY, CommandsInterface.CLIR_DEFAULT); 320 return dial(dialString, oirMode, videoState, intentExtras); 321 } 322 323 /** 324 * oirMode is one of the CLIR_ constants 325 */ 326 synchronized Connection 327 dial(String dialString, int clirMode, int videoState, Bundle intentExtras) 328 throws CallStateException { 329 boolean isPhoneInEcmMode = SystemProperties.getBoolean( 330 TelephonyProperties.PROPERTY_INECM_MODE, false); 331 boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(dialString); 332 333 if (DBG) log("dial clirMode=" + clirMode); 334 335 // note that this triggers call state changed notif 336 clearDisconnected(); 337 338 if (mImsManager == null) { 339 throw new CallStateException("service not available"); 340 } 341 342 if (!canDial()) { 343 throw new CallStateException("cannot dial in current state"); 344 } 345 346 if (isPhoneInEcmMode && isEmergencyNumber) { 347 handleEcmTimer(ImsPhone.CANCEL_ECM_TIMER); 348 } 349 350 // If the call is to an emergency number and the carrier does not support video emergency 351 // calls, dial as an audio-only call. 352 if (isEmergencyNumber && VideoProfile.isVideo(videoState) && 353 !mAllowEmergencyVideoCalls) { 354 loge("dial: carrier does not support video emergency calls; downgrade to audio-only"); 355 videoState = VideoProfile.STATE_AUDIO_ONLY; 356 } 357 358 boolean holdBeforeDial = false; 359 360 // The new call must be assigned to the foreground call. 361 // That call must be idle, so place anything that's 362 // there on hold 363 if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) { 364 if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) { 365 //we should have failed in !canDial() above before we get here 366 throw new CallStateException("cannot dial in current state"); 367 } 368 // foreground call is empty for the newly dialed connection 369 holdBeforeDial = true; 370 // Cache the video state for pending MO call. 371 mPendingCallVideoState = videoState; 372 mPendingIntentExtras = intentExtras; 373 switchWaitingOrHoldingAndActive(); 374 } 375 376 ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE; 377 ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE; 378 379 mClirMode = clirMode; 380 381 synchronized (mSyncHold) { 382 if (holdBeforeDial) { 383 fgState = mForegroundCall.getState(); 384 bgState = mBackgroundCall.getState(); 385 386 //holding foreground call failed 387 if (fgState == ImsPhoneCall.State.ACTIVE) { 388 throw new CallStateException("cannot dial in current state"); 389 } 390 391 //holding foreground call succeeded 392 if (bgState == ImsPhoneCall.State.HOLDING) { 393 holdBeforeDial = false; 394 } 395 } 396 397 mPendingMO = new ImsPhoneConnection(mPhone, 398 checkForTestEmergencyNumber(dialString), this, mForegroundCall, 399 isEmergencyNumber); 400 } 401 addConnection(mPendingMO); 402 403 if (!holdBeforeDial) { 404 if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) { 405 dialInternal(mPendingMO, clirMode, videoState, intentExtras); 406 } else { 407 try { 408 getEcbmInterface().exitEmergencyCallbackMode(); 409 } catch (ImsException e) { 410 e.printStackTrace(); 411 throw new CallStateException("service not available"); 412 } 413 mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null); 414 pendingCallClirMode = clirMode; 415 mPendingCallVideoState = videoState; 416 pendingCallInEcm = true; 417 } 418 } 419 420 updatePhoneState(); 421 mPhone.notifyPreciseCallStateChanged(); 422 423 return mPendingMO; 424 } 425 426 /** 427 * Determines if the carrier associated with the specified SubId supports making video emergency 428 * calls. 429 * 430 * @param subId The sub id. 431 * @return {@code true} if video emergency calls are supported, {@code false} otherwise. 432 */ 433 private boolean isEmergencyVtCallAllowed(int subId) { 434 CarrierConfigManager carrierConfigManager = (CarrierConfigManager) 435 mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 436 if (carrierConfigManager == null) { 437 loge("isEmergencyVideoCallsSupported: No carrier config service found."); 438 return false; 439 } 440 441 PersistableBundle carrierConfig = carrierConfigManager.getConfigForSubId(subId); 442 if (carrierConfig == null) { 443 loge("isEmergencyVideoCallsSupported: Empty carrier config."); 444 return false; 445 } 446 447 return carrierConfig.getBoolean(CarrierConfigManager.BOOL_ALLOW_EMERGENCY_VIDEO_CALLS); 448 } 449 450 private void handleEcmTimer(int action) { 451 mPhone.handleTimerInEmergencyCallbackMode(action); 452 switch (action) { 453 case ImsPhone.CANCEL_ECM_TIMER: 454 break; 455 case ImsPhone.RESTART_ECM_TIMER: 456 break; 457 default: 458 log("handleEcmTimer, unsupported action " + action); 459 } 460 } 461 462 private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState, 463 Bundle intentExtras) { 464 465 if (conn == null) { 466 return; 467 } 468 469 if (conn.getAddress()== null || conn.getAddress().length() == 0 470 || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) { 471 // Phone number is invalid 472 conn.setDisconnectCause(DisconnectCause.INVALID_NUMBER); 473 sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO); 474 return; 475 } 476 477 // Always unmute when initiating a new call 478 setMute(false); 479 int serviceType = PhoneNumberUtils.isEmergencyNumber(conn.getAddress()) ? 480 ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL; 481 int callType = ImsCallProfile.getCallTypeFromVideoState(videoState); 482 //TODO(vt): Is this sufficient? At what point do we know the video state of the call? 483 conn.setVideoState(videoState); 484 485 try { 486 String[] callees = new String[] { conn.getAddress() }; 487 ImsCallProfile profile = mImsManager.createCallProfile(mServiceId, 488 serviceType, callType); 489 profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode); 490 491 // Translate call subject intent-extra from Telecom-specific extra key to the 492 // ImsCallProfile key. 493 if (intentExtras != null) { 494 if (intentExtras.containsKey(android.telecom.TelecomManager.EXTRA_CALL_SUBJECT)) { 495 intentExtras.putString(ImsCallProfile.EXTRA_DISPLAY_TEXT, 496 cleanseInstantLetteringMessage(intentExtras.getString( 497 android.telecom.TelecomManager.EXTRA_CALL_SUBJECT)) 498 ); 499 } 500 501 // Pack the OEM-specific call extras. 502 profile.mCallExtras.putBundle(ImsCallProfile.EXTRA_OEM_EXTRAS, intentExtras); 503 504 // NOTE: Extras to be sent over the network are packed into the 505 // intentExtras individually, with uniquely defined keys. 506 // These key-value pairs are processed by IMS Service before 507 // being sent to the lower layers/to the network. 508 } 509 510 ImsCall imsCall = mImsManager.makeCall(mServiceId, profile, 511 callees, mImsCallListener); 512 conn.setImsCall(imsCall); 513 514 mEventLog.writeOnImsCallStart(imsCall.getSession(), callees[0]); 515 516 setVideoCallProvider(conn, imsCall); 517 } catch (ImsException e) { 518 loge("dialInternal : " + e); 519 conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED); 520 sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO); 521 } catch (RemoteException e) { 522 } 523 } 524 525 /** 526 * Accepts a call with the specified video state. The video state is the video state that the 527 * user has agreed upon in the InCall UI. 528 * 529 * @param videoState The video State 530 * @throws CallStateException 531 */ 532 void acceptCall (int videoState) throws CallStateException { 533 if (DBG) log("acceptCall"); 534 535 if (mForegroundCall.getState().isAlive() 536 && mBackgroundCall.getState().isAlive()) { 537 throw new CallStateException("cannot accept call"); 538 } 539 540 if ((mRingingCall.getState() == ImsPhoneCall.State.WAITING) 541 && mForegroundCall.getState().isAlive()) { 542 setMute(false); 543 // Cache video state for pending MT call. 544 mPendingCallVideoState = videoState; 545 switchWaitingOrHoldingAndActive(); 546 } else if (mRingingCall.getState().isRinging()) { 547 if (DBG) log("acceptCall: incoming..."); 548 // Always unmute when answering a new call 549 setMute(false); 550 try { 551 ImsCall imsCall = mRingingCall.getImsCall(); 552 if (imsCall != null) { 553 imsCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState)); 554 mEventLog.writeOnImsCallAccept(imsCall.getSession()); 555 } else { 556 throw new CallStateException("no valid ims call"); 557 } 558 } catch (ImsException e) { 559 throw new CallStateException("cannot accept call"); 560 } 561 } else { 562 throw new CallStateException("phone not ringing"); 563 } 564 } 565 566 void 567 rejectCall () throws CallStateException { 568 if (DBG) log("rejectCall"); 569 570 if (mRingingCall.getState().isRinging()) { 571 hangup(mRingingCall); 572 } else { 573 throw new CallStateException("phone not ringing"); 574 } 575 } 576 577 578 private void switchAfterConferenceSuccess() { 579 if (DBG) log("switchAfterConferenceSuccess fg =" + mForegroundCall.getState() + 580 ", bg = " + mBackgroundCall.getState()); 581 582 if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) { 583 log("switchAfterConferenceSuccess"); 584 mForegroundCall.switchWith(mBackgroundCall); 585 } 586 } 587 588 void 589 switchWaitingOrHoldingAndActive() throws CallStateException { 590 if (DBG) log("switchWaitingOrHoldingAndActive"); 591 592 if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) { 593 throw new CallStateException("cannot be in the incoming state"); 594 } 595 596 if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) { 597 ImsCall imsCall = mForegroundCall.getImsCall(); 598 if (imsCall == null) { 599 throw new CallStateException("no ims call"); 600 } 601 602 // Swap the ImsCalls pointed to by the foreground and background ImsPhoneCalls. 603 // If hold or resume later fails, we will swap them back. 604 mSwitchingFgAndBgCalls = true; 605 mCallExpectedToResume = mBackgroundCall.getImsCall(); 606 mForegroundCall.switchWith(mBackgroundCall); 607 608 // Hold the foreground call; once the foreground call is held, the background call will 609 // be resumed. 610 try { 611 imsCall.hold(); 612 mEventLog.writeOnImsCallHold(imsCall.getSession()); 613 614 // If there is no background call to resume, then don't expect there to be a switch. 615 if (mCallExpectedToResume == null) { 616 mSwitchingFgAndBgCalls = false; 617 } 618 } catch (ImsException e) { 619 mForegroundCall.switchWith(mBackgroundCall); 620 throw new CallStateException(e.getMessage()); 621 } 622 } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) { 623 resumeWaitingOrHolding(); 624 } 625 } 626 627 void 628 conference() { 629 if (DBG) log("conference"); 630 631 ImsCall fgImsCall = mForegroundCall.getImsCall(); 632 if (fgImsCall == null) { 633 log("conference no foreground ims call"); 634 return; 635 } 636 637 ImsCall bgImsCall = mBackgroundCall.getImsCall(); 638 if (bgImsCall == null) { 639 log("conference no background ims call"); 640 return; 641 } 642 643 // Keep track of the connect time of the earliest call so that it can be set on the 644 // {@code ImsConference} when it is created. 645 long foregroundConnectTime = mForegroundCall.getEarliestConnectTime(); 646 long backgroundConnectTime = mBackgroundCall.getEarliestConnectTime(); 647 long conferenceConnectTime; 648 if (foregroundConnectTime > 0 && backgroundConnectTime > 0) { 649 conferenceConnectTime = Math.min(mForegroundCall.getEarliestConnectTime(), 650 mBackgroundCall.getEarliestConnectTime()); 651 log("conference - using connect time = " + conferenceConnectTime); 652 } else if (foregroundConnectTime > 0) { 653 log("conference - bg call connect time is 0; using fg = " + foregroundConnectTime); 654 conferenceConnectTime = foregroundConnectTime; 655 } else { 656 log("conference - fg call connect time is 0; using bg = " + backgroundConnectTime); 657 conferenceConnectTime = backgroundConnectTime; 658 } 659 660 ImsPhoneConnection foregroundConnection = mForegroundCall.getFirstConnection(); 661 if (foregroundConnection != null) { 662 foregroundConnection.setConferenceConnectTime(conferenceConnectTime); 663 } 664 665 try { 666 fgImsCall.merge(bgImsCall); 667 } catch (ImsException e) { 668 log("conference " + e.getMessage()); 669 } 670 } 671 672 void 673 explicitCallTransfer() { 674 //TODO : implement 675 } 676 677 void 678 clearDisconnected() { 679 if (DBG) log("clearDisconnected"); 680 681 internalClearDisconnected(); 682 683 updatePhoneState(); 684 mPhone.notifyPreciseCallStateChanged(); 685 } 686 687 boolean 688 canConference() { 689 return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE 690 && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING 691 && !mBackgroundCall.isFull() 692 && !mForegroundCall.isFull(); 693 } 694 695 boolean 696 canDial() { 697 boolean ret; 698 int serviceState = mPhone.getServiceState().getState(); 699 String disableCall = SystemProperties.get( 700 TelephonyProperties.PROPERTY_DISABLE_CALL, "false"); 701 702 ret = (serviceState != ServiceState.STATE_POWER_OFF) 703 && mPendingMO == null 704 && !mRingingCall.isRinging() 705 && !disableCall.equals("true") 706 && (!mForegroundCall.getState().isAlive() 707 || !mBackgroundCall.getState().isAlive()); 708 709 return ret; 710 } 711 712 boolean 713 canTransfer() { 714 return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE 715 && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING; 716 } 717 718 //***** Private Instance Methods 719 720 private void 721 internalClearDisconnected() { 722 mRingingCall.clearDisconnected(); 723 mForegroundCall.clearDisconnected(); 724 mBackgroundCall.clearDisconnected(); 725 mHandoverCall.clearDisconnected(); 726 } 727 728 private void 729 updatePhoneState() { 730 PhoneConstants.State oldState = mState; 731 732 if (mRingingCall.isRinging()) { 733 mState = PhoneConstants.State.RINGING; 734 } else if (mPendingMO != null || 735 !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) { 736 mState = PhoneConstants.State.OFFHOOK; 737 } else { 738 mState = PhoneConstants.State.IDLE; 739 } 740 741 if (mState == PhoneConstants.State.IDLE && oldState != mState) { 742 mVoiceCallEndedRegistrants.notifyRegistrants( 743 new AsyncResult(null, null, null)); 744 } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) { 745 mVoiceCallStartedRegistrants.notifyRegistrants ( 746 new AsyncResult(null, null, null)); 747 } 748 749 if (DBG) log("updatePhoneState oldState=" + oldState + ", newState=" + mState); 750 751 if (mState != oldState) { 752 mPhone.notifyPhoneStateChanged(); 753 mEventLog.writeImsPhoneState(mState); 754 } 755 } 756 757 private void 758 handleRadioNotAvailable() { 759 // handlePollCalls will clear out its 760 // call list when it gets the CommandException 761 // error result from this 762 pollCallsWhenSafe(); 763 } 764 765 private void 766 dumpState() { 767 List l; 768 769 log("Phone State:" + mState); 770 771 log("Ringing call: " + mRingingCall.toString()); 772 773 l = mRingingCall.getConnections(); 774 for (int i = 0, s = l.size(); i < s; i++) { 775 log(l.get(i).toString()); 776 } 777 778 log("Foreground call: " + mForegroundCall.toString()); 779 780 l = mForegroundCall.getConnections(); 781 for (int i = 0, s = l.size(); i < s; i++) { 782 log(l.get(i).toString()); 783 } 784 785 log("Background call: " + mBackgroundCall.toString()); 786 787 l = mBackgroundCall.getConnections(); 788 for (int i = 0, s = l.size(); i < s; i++) { 789 log(l.get(i).toString()); 790 } 791 792 } 793 794 //***** Called from ImsPhone 795 796 void setUiTTYMode(int uiTtyMode, Message onComplete) { 797 try { 798 mImsManager.setUiTTYMode(mPhone.getContext(), mServiceId, uiTtyMode, onComplete); 799 } catch (ImsException e) { 800 loge("setTTYMode : " + e); 801 mPhone.sendErrorResponse(onComplete, e); 802 } 803 } 804 805 /*package*/ void setMute(boolean mute) { 806 mDesiredMute = mute; 807 mForegroundCall.setMute(mute); 808 } 809 810 /*package*/ boolean getMute() { 811 return mDesiredMute; 812 } 813 814 /* package */ void sendDtmf(char c, Message result) { 815 if (DBG) log("sendDtmf"); 816 817 ImsCall imscall = mForegroundCall.getImsCall(); 818 if (imscall != null) { 819 imscall.sendDtmf(c, result); 820 } 821 } 822 823 /*package*/ void 824 startDtmf(char c) { 825 if (DBG) log("startDtmf"); 826 827 ImsCall imscall = mForegroundCall.getImsCall(); 828 if (imscall != null) { 829 imscall.startDtmf(c); 830 } else { 831 loge("startDtmf : no foreground call"); 832 } 833 } 834 835 /*package*/ void 836 stopDtmf() { 837 if (DBG) log("stopDtmf"); 838 839 ImsCall imscall = mForegroundCall.getImsCall(); 840 if (imscall != null) { 841 imscall.stopDtmf(); 842 } else { 843 loge("stopDtmf : no foreground call"); 844 } 845 } 846 847 //***** Called from ImsPhoneConnection 848 849 /*package*/ void 850 hangup (ImsPhoneConnection conn) throws CallStateException { 851 if (DBG) log("hangup connection"); 852 853 if (conn.getOwner() != this) { 854 throw new CallStateException ("ImsPhoneConnection " + conn 855 + "does not belong to ImsPhoneCallTracker " + this); 856 } 857 858 hangup(conn.getCall()); 859 } 860 861 //***** Called from ImsPhoneCall 862 863 /* package */ void 864 hangup (ImsPhoneCall call) throws CallStateException { 865 if (DBG) log("hangup call"); 866 867 if (call.getConnections().size() == 0) { 868 throw new CallStateException("no connections"); 869 } 870 871 ImsCall imsCall = call.getImsCall(); 872 boolean rejectCall = false; 873 874 if (call == mRingingCall) { 875 if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming"); 876 rejectCall = true; 877 } else if (call == mForegroundCall) { 878 if (call.isDialingOrAlerting()) { 879 if (Phone.DEBUG_PHONE) { 880 log("(foregnd) hangup dialing or alerting..."); 881 } 882 } else { 883 if (Phone.DEBUG_PHONE) { 884 log("(foregnd) hangup foreground"); 885 } 886 //held call will be resumed by onCallTerminated 887 } 888 } else if (call == mBackgroundCall) { 889 if (Phone.DEBUG_PHONE) { 890 log("(backgnd) hangup waiting or background"); 891 } 892 } else { 893 throw new CallStateException ("ImsPhoneCall " + call + 894 "does not belong to ImsPhoneCallTracker " + this); 895 } 896 897 call.onHangupLocal(); 898 899 try { 900 if (imsCall != null) { 901 if (rejectCall) { 902 imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE); 903 mEventLog.writeOnImsCallReject(imsCall.getSession()); 904 } else { 905 imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED); 906 mEventLog.writeOnImsCallTerminate(imsCall.getSession()); 907 } 908 } else if (mPendingMO != null && call == mForegroundCall) { 909 // is holding a foreground call 910 mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED); 911 mPendingMO.onDisconnect(); 912 removeConnection(mPendingMO); 913 mPendingMO = null; 914 updatePhoneState(); 915 removeMessages(EVENT_DIAL_PENDINGMO); 916 } 917 } catch (ImsException e) { 918 throw new CallStateException(e.getMessage()); 919 } 920 921 mPhone.notifyPreciseCallStateChanged(); 922 } 923 924 void callEndCleanupHandOverCallIfAny() { 925 if (mHandoverCall.mConnections.size() > 0) { 926 if (DBG) log("callEndCleanupHandOverCallIfAny, mHandoverCall.mConnections=" 927 + mHandoverCall.mConnections); 928 mHandoverCall.mConnections.clear(); 929 mState = PhoneConstants.State.IDLE; 930 } 931 } 932 933 /* package */ 934 void resumeWaitingOrHolding() throws CallStateException { 935 if (DBG) log("resumeWaitingOrHolding"); 936 937 try { 938 if (mForegroundCall.getState().isAlive()) { 939 //resume foreground call after holding background call 940 //they were switched before holding 941 ImsCall imsCall = mForegroundCall.getImsCall(); 942 if (imsCall != null) { 943 imsCall.resume(); 944 mEventLog.writeOnImsCallResume(imsCall.getSession()); 945 } 946 } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) { 947 //accept waiting call after holding background call 948 ImsCall imsCall = mRingingCall.getImsCall(); 949 if (imsCall != null) { 950 imsCall.accept( 951 ImsCallProfile.getCallTypeFromVideoState(mPendingCallVideoState)); 952 mEventLog.writeOnImsCallAccept(imsCall.getSession()); 953 } 954 } else { 955 //Just resume background call. 956 //To distinguish resuming call with swapping calls 957 //we do not switch calls.here 958 //ImsPhoneConnection.update will chnage the parent when completed 959 ImsCall imsCall = mBackgroundCall.getImsCall(); 960 if (imsCall != null) { 961 imsCall.resume(); 962 mEventLog.writeOnImsCallResume(imsCall.getSession()); 963 } 964 } 965 } catch (ImsException e) { 966 throw new CallStateException(e.getMessage()); 967 } 968 } 969 970 /* package */ 971 void sendUSSD (String ussdString, Message response) { 972 if (DBG) log("sendUSSD"); 973 974 try { 975 if (mUssdSession != null) { 976 mUssdSession.sendUssd(ussdString); 977 AsyncResult.forMessage(response, null, null); 978 response.sendToTarget(); 979 return; 980 } 981 982 String[] callees = new String[] { ussdString }; 983 ImsCallProfile profile = mImsManager.createCallProfile(mServiceId, 984 ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE); 985 profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING, 986 ImsCallProfile.DIALSTRING_USSD); 987 988 mUssdSession = mImsManager.makeCall(mServiceId, profile, 989 callees, mImsUssdListener); 990 } catch (ImsException e) { 991 loge("sendUSSD : " + e); 992 mPhone.sendErrorResponse(response, e); 993 } 994 } 995 996 /* package */ 997 void cancelUSSD() { 998 if (mUssdSession == null) return; 999 1000 try { 1001 mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED); 1002 } catch (ImsException e) { 1003 } 1004 1005 } 1006 1007 private synchronized ImsPhoneConnection findConnection(final ImsCall imsCall) { 1008 for (ImsPhoneConnection conn : mConnections) { 1009 if (conn.getImsCall() == imsCall) { 1010 return conn; 1011 } 1012 } 1013 return null; 1014 } 1015 1016 private synchronized void removeConnection(ImsPhoneConnection conn) { 1017 mConnections.remove(conn); 1018 // If not emergency call is remaining, notify emergency call registrants 1019 if (mIsInEmergencyCall) { 1020 boolean isEmergencyCallInList = false; 1021 // if no emergency calls pending, set this to false 1022 for (ImsPhoneConnection imsPhoneConnection : mConnections) { 1023 if (imsPhoneConnection != null && imsPhoneConnection.isEmergency() == true) { 1024 isEmergencyCallInList = true; 1025 break; 1026 } 1027 } 1028 1029 if (!isEmergencyCallInList) { 1030 mIsInEmergencyCall = false; 1031 mPhone.sendEmergencyCallStateChange(false); 1032 } 1033 } 1034 } 1035 1036 private synchronized void addConnection(ImsPhoneConnection conn) { 1037 mConnections.add(conn); 1038 if (conn.isEmergency()) { 1039 mIsInEmergencyCall = true; 1040 mPhone.sendEmergencyCallStateChange(true); 1041 } 1042 } 1043 1044 private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) { 1045 if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause); 1046 // This method is called on onCallUpdate() where there is not necessarily a call state 1047 // change. In these situations, we'll ignore the state related updates and only process 1048 // the change in media capabilities (as expected). The default is to not ignore state 1049 // changes so we do not change existing behavior. 1050 processCallStateChange(imsCall, state, cause, false /* do not ignore state update */); 1051 } 1052 1053 private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause, 1054 boolean ignoreState) { 1055 if (DBG) { 1056 log("processCallStateChange state=" + state + " cause=" + cause 1057 + " ignoreState=" + ignoreState); 1058 } 1059 1060 if (imsCall == null) return; 1061 1062 boolean changed = false; 1063 ImsPhoneConnection conn = findConnection(imsCall); 1064 1065 if (conn == null) { 1066 // TODO : what should be done? 1067 return; 1068 } 1069 1070 // processCallStateChange is triggered for onCallUpdated as well. 1071 // onCallUpdated should not modify the state of the call 1072 // It should modify only other capabilities of call through updateMediaCapabilities 1073 // State updates will be triggered through individual callbacks 1074 // i.e. onCallHeld, onCallResume, etc and conn.update will be responsible for the update 1075 if (ignoreState) { 1076 conn.updateAddressDisplay(imsCall); 1077 conn.updateMediaCapabilities(imsCall); 1078 conn.updateExtras(imsCall); 1079 return; 1080 } 1081 1082 changed = conn.update(imsCall, state); 1083 if (state == ImsPhoneCall.State.DISCONNECTED) { 1084 changed = conn.onDisconnect(cause) || changed; 1085 //detach the disconnected connections 1086 conn.getCall().detach(conn); 1087 removeConnection(conn); 1088 } 1089 1090 if (changed) { 1091 if (conn.getCall() == mHandoverCall) return; 1092 updatePhoneState(); 1093 mPhone.notifyPreciseCallStateChanged(); 1094 } 1095 } 1096 1097 private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) { 1098 int cause = DisconnectCause.ERROR_UNSPECIFIED; 1099 1100 //int type = reasonInfo.getReasonType(); 1101 int code = reasonInfo.getCode(); 1102 switch (code) { 1103 case ImsReasonInfo.CODE_SIP_BAD_ADDRESS: 1104 case ImsReasonInfo.CODE_SIP_NOT_REACHABLE: 1105 return DisconnectCause.NUMBER_UNREACHABLE; 1106 1107 case ImsReasonInfo.CODE_SIP_BUSY: 1108 return DisconnectCause.BUSY; 1109 1110 case ImsReasonInfo.CODE_USER_TERMINATED: 1111 return DisconnectCause.LOCAL; 1112 1113 case ImsReasonInfo.CODE_LOCAL_CALL_DECLINE: 1114 return DisconnectCause.INCOMING_REJECTED; 1115 1116 case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE: 1117 return DisconnectCause.NORMAL; 1118 1119 case ImsReasonInfo.CODE_SIP_REDIRECTED: 1120 case ImsReasonInfo.CODE_SIP_BAD_REQUEST: 1121 case ImsReasonInfo.CODE_SIP_FORBIDDEN: 1122 case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE: 1123 case ImsReasonInfo.CODE_SIP_USER_REJECTED: 1124 case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR: 1125 return DisconnectCause.SERVER_ERROR; 1126 1127 case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE: 1128 case ImsReasonInfo.CODE_SIP_NOT_FOUND: 1129 case ImsReasonInfo.CODE_SIP_SERVER_ERROR: 1130 return DisconnectCause.SERVER_UNREACHABLE; 1131 1132 case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING: 1133 case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED: 1134 case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN: 1135 case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE: 1136 case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED: 1137 case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE: 1138 case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE: 1139 case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING: 1140 return DisconnectCause.OUT_OF_SERVICE; 1141 1142 case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT: 1143 case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING: 1144 case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER: 1145 case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE: 1146 return DisconnectCause.TIMED_OUT; 1147 1148 case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY: 1149 case ImsReasonInfo.CODE_LOCAL_POWER_OFF: 1150 return DisconnectCause.POWER_OFF; 1151 1152 case ImsReasonInfo.CODE_FDN_BLOCKED: 1153 return DisconnectCause.FDN_BLOCKED; 1154 default: 1155 } 1156 1157 return cause; 1158 } 1159 1160 /** 1161 * Listen to the IMS call state change 1162 */ 1163 private ImsCall.Listener mImsCallListener = new ImsCall.Listener() { 1164 @Override 1165 public void onCallProgressing(ImsCall imsCall) { 1166 if (DBG) log("onCallProgressing"); 1167 1168 mPendingMO = null; 1169 processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING, 1170 DisconnectCause.NOT_DISCONNECTED); 1171 mEventLog.writeOnImsCallProgressing(imsCall.getCallSession()); 1172 } 1173 1174 @Override 1175 public void onCallStarted(ImsCall imsCall) { 1176 if (DBG) log("onCallStarted"); 1177 1178 mPendingMO = null; 1179 processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE, 1180 DisconnectCause.NOT_DISCONNECTED); 1181 mEventLog.writeOnImsCallStarted(imsCall.getCallSession()); 1182 } 1183 1184 @Override 1185 public void onCallUpdated(ImsCall imsCall) { 1186 if (DBG) log("onCallUpdated"); 1187 if (imsCall == null) { 1188 return; 1189 } 1190 ImsPhoneConnection conn = findConnection(imsCall); 1191 if (conn != null) { 1192 processCallStateChange(imsCall, conn.getCall().mState, 1193 DisconnectCause.NOT_DISCONNECTED, true /*ignore state update*/); 1194 mEventLog.writeImsCallState(imsCall.getCallSession(), conn.getCall().mState); 1195 } 1196 } 1197 1198 /** 1199 * onCallStartFailed will be invoked when: 1200 * case 1) Dialing fails 1201 * case 2) Ringing call is disconnected by local or remote user 1202 */ 1203 @Override 1204 public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) { 1205 if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode()); 1206 1207 if (mPendingMO != null) { 1208 // To initiate dialing circuit-switched call 1209 if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED 1210 && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE 1211 && mRingingCall.getState() == ImsPhoneCall.State.IDLE) { 1212 mForegroundCall.detach(mPendingMO); 1213 removeConnection(mPendingMO); 1214 mPendingMO.finalize(); 1215 mPendingMO = null; 1216 mPhone.initiateSilentRedial(); 1217 return; 1218 } else { 1219 mPendingMO = null; 1220 int cause = getDisconnectCauseFromReasonInfo(reasonInfo); 1221 processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause); 1222 } 1223 mEventLog.writeOnImsCallStartFailed(imsCall.getCallSession(), reasonInfo); 1224 } 1225 } 1226 1227 @Override 1228 public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) { 1229 if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode()); 1230 1231 ImsPhoneCall.State oldState = mForegroundCall.getState(); 1232 int cause = getDisconnectCauseFromReasonInfo(reasonInfo); 1233 ImsPhoneConnection conn = findConnection(imsCall); 1234 if (DBG) log("cause = " + cause + " conn = " + conn); 1235 1236 if (conn != null && conn.isIncoming() && conn.getConnectTime() == 0) { 1237 // Missed 1238 if (cause == DisconnectCause.NORMAL) { 1239 cause = DisconnectCause.INCOMING_MISSED; 1240 } else { 1241 cause = DisconnectCause.INCOMING_REJECTED; 1242 } 1243 if (DBG) log("Incoming connection of 0 connect time detected - translated cause = " 1244 + cause); 1245 1246 } 1247 1248 if (cause == DisconnectCause.NORMAL && conn != null && conn.getImsCall().isMerged()) { 1249 // Call was terminated while it is merged instead of a remote disconnect. 1250 cause = DisconnectCause.IMS_MERGED_SUCCESSFULLY; 1251 } 1252 1253 processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause); 1254 if (mForegroundCall.getState() != ImsPhoneCall.State.ACTIVE) { 1255 if (mRingingCall.getState().isRinging()) { 1256 // Drop pending MO. We should address incoming call first 1257 mPendingMO = null; 1258 } else if (mPendingMO != null) { 1259 sendEmptyMessage(EVENT_DIAL_PENDINGMO); 1260 } 1261 } 1262 1263 if (mSwitchingFgAndBgCalls) { 1264 if (DBG) { 1265 log("onCallTerminated: Call terminated in the midst of Switching " + 1266 "Fg and Bg calls."); 1267 } 1268 // If we are the in midst of swapping FG and BG calls and the call that was 1269 // terminated was the one that we expected to resume, we need to swap the FG and 1270 // BG calls back. 1271 if (imsCall == mCallExpectedToResume) { 1272 if (DBG) { 1273 log("onCallTerminated: switching " + mForegroundCall + " with " 1274 + mBackgroundCall); 1275 } 1276 mForegroundCall.switchWith(mBackgroundCall); 1277 } 1278 // This call terminated in the midst of a switch after the other call was held, so 1279 // resume it back to ACTIVE state since the switch failed. 1280 if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) { 1281 sendEmptyMessage(EVENT_RESUME_BACKGROUND); 1282 mSwitchingFgAndBgCalls = false; 1283 mCallExpectedToResume = null; 1284 } 1285 } 1286 1287 mEventLog.writeOnImsCallTerminated(imsCall.getCallSession(), reasonInfo); 1288 } 1289 1290 @Override 1291 public void onCallHeld(ImsCall imsCall) { 1292 if (DBG) { 1293 if (mForegroundCall.getImsCall() == imsCall) { 1294 log("onCallHeld (fg) " + imsCall); 1295 } else if (mBackgroundCall.getImsCall() == imsCall) { 1296 log("onCallHeld (bg) " + imsCall); 1297 } 1298 } 1299 1300 synchronized (mSyncHold) { 1301 ImsPhoneCall.State oldState = mBackgroundCall.getState(); 1302 processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING, 1303 DisconnectCause.NOT_DISCONNECTED); 1304 1305 // Note: If we're performing a switchWaitingOrHoldingAndActive, the call to 1306 // processCallStateChange above may have caused the mBackgroundCall and 1307 // mForegroundCall references below to change meaning. Watch out for this if you 1308 // are reading through this code. 1309 if (oldState == ImsPhoneCall.State.ACTIVE) { 1310 // Note: This case comes up when we have just held a call in response to a 1311 // switchWaitingOrHoldingAndActive. We now need to resume the background call. 1312 // The EVENT_RESUME_BACKGROUND causes resumeWaitingOrHolding to be called. 1313 if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) 1314 || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) { 1315 sendEmptyMessage(EVENT_RESUME_BACKGROUND); 1316 } else { 1317 //when multiple connections belong to background call, 1318 //only the first callback reaches here 1319 //otherwise the oldState is already HOLDING 1320 if (mPendingMO != null) { 1321 sendEmptyMessage(EVENT_DIAL_PENDINGMO); 1322 } 1323 1324 // In this case there will be no call resumed, so we can assume that we 1325 // are done switching fg and bg calls now. 1326 // This may happen if there is no BG call and we are holding a call so that 1327 // we can dial another one. 1328 mSwitchingFgAndBgCalls = false; 1329 } 1330 } else if (oldState == ImsPhoneCall.State.IDLE && mSwitchingFgAndBgCalls) { 1331 // The other call terminated in the midst of a switch before this call was held, 1332 // so resume the foreground call back to ACTIVE state since the switch failed. 1333 if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) { 1334 sendEmptyMessage(EVENT_RESUME_BACKGROUND); 1335 mSwitchingFgAndBgCalls = false; 1336 mCallExpectedToResume = null; 1337 } 1338 } 1339 } 1340 mEventLog.writeOnImsCallHeld(imsCall.getCallSession()); 1341 } 1342 1343 @Override 1344 public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) { 1345 if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode()); 1346 1347 synchronized (mSyncHold) { 1348 ImsPhoneCall.State bgState = mBackgroundCall.getState(); 1349 if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) { 1350 // disconnected while processing hold 1351 if (mPendingMO != null) { 1352 sendEmptyMessage(EVENT_DIAL_PENDINGMO); 1353 } 1354 } else if (bgState == ImsPhoneCall.State.ACTIVE) { 1355 mForegroundCall.switchWith(mBackgroundCall); 1356 1357 if (mPendingMO != null) { 1358 mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED); 1359 sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO); 1360 } 1361 } 1362 } 1363 mEventLog.writeOnImsCallHoldFailed(imsCall.getCallSession(), reasonInfo); 1364 } 1365 1366 @Override 1367 public void onCallResumed(ImsCall imsCall) { 1368 if (DBG) log("onCallResumed"); 1369 1370 // If we are the in midst of swapping FG and BG calls and the call we end up resuming 1371 // is not the one we expected, we likely had a resume failure and we need to swap the 1372 // FG and BG calls back. 1373 if (mSwitchingFgAndBgCalls && imsCall != mCallExpectedToResume) { 1374 if (DBG) { 1375 log("onCallResumed : switching " + mForegroundCall + " with " 1376 + mBackgroundCall); 1377 } 1378 mForegroundCall.switchWith(mBackgroundCall); 1379 mSwitchingFgAndBgCalls = false; 1380 mCallExpectedToResume = null; 1381 } 1382 processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE, 1383 DisconnectCause.NOT_DISCONNECTED); 1384 mEventLog.writeOnImsCallResumed(imsCall.getCallSession()); 1385 } 1386 1387 @Override 1388 public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) { 1389 // If we are in the midst of swapping the FG and BG calls and we got a resume fail, we 1390 // need to swap back the FG and BG calls. 1391 if (mSwitchingFgAndBgCalls && imsCall == mCallExpectedToResume) { 1392 if (DBG) { 1393 log("onCallResumeFailed : switching " + mForegroundCall + " with " 1394 + mBackgroundCall); 1395 } 1396 mForegroundCall.switchWith(mBackgroundCall); 1397 mCallExpectedToResume = null; 1398 mSwitchingFgAndBgCalls = false; 1399 } 1400 mPhone.notifySuppServiceFailed(Phone.SuppService.RESUME); 1401 mEventLog.writeOnImsCallResumeFailed(imsCall.getCallSession(), reasonInfo); 1402 } 1403 1404 @Override 1405 public void onCallResumeReceived(ImsCall imsCall) { 1406 if (DBG) log("onCallResumeReceived"); 1407 1408 if (mOnHoldToneStarted) { 1409 mPhone.stopOnHoldTone(); 1410 mOnHoldToneStarted = false; 1411 } 1412 1413 SuppServiceNotification supp = new SuppServiceNotification(); 1414 // Type of notification: 0 = MO; 1 = MT 1415 // Refer SuppServiceNotification class documentation. 1416 supp.notificationType = 1; 1417 supp.code = SuppServiceNotification.MT_CODE_CALL_RETRIEVED; 1418 mPhone.notifySuppSvcNotification(supp); 1419 mEventLog.writeOnImsCallResumeReceived(imsCall.getCallSession()); 1420 } 1421 1422 @Override 1423 public void onCallHoldReceived(ImsCall imsCall) { 1424 if (DBG) log("onCallHoldReceived"); 1425 1426 ImsPhoneConnection conn = findConnection(imsCall); 1427 if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) { 1428 if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) { 1429 mPhone.startOnHoldTone(); 1430 mOnHoldToneStarted = true; 1431 } 1432 } 1433 1434 SuppServiceNotification supp = new SuppServiceNotification(); 1435 // Type of notification: 0 = MO; 1 = MT 1436 // Refer SuppServiceNotification class documentation. 1437 supp.notificationType = 1; 1438 supp.code = SuppServiceNotification.MT_CODE_CALL_ON_HOLD; 1439 mPhone.notifySuppSvcNotification(supp); 1440 mEventLog.writeOnImsCallHoldReceived(imsCall.getCallSession()); 1441 } 1442 1443 @Override 1444 public void onCallSuppServiceReceived(ImsCall call, 1445 ImsSuppServiceNotification suppServiceInfo) { 1446 if (DBG) log("onCallSuppServiceReceived: suppServiceInfo=" + suppServiceInfo); 1447 1448 SuppServiceNotification supp = new SuppServiceNotification(); 1449 supp.notificationType = suppServiceInfo.notificationType; 1450 supp.code = suppServiceInfo.code; 1451 supp.index = suppServiceInfo.index; 1452 supp.number = suppServiceInfo.number; 1453 supp.history = suppServiceInfo.history; 1454 1455 mPhone.notifySuppSvcNotification(supp); 1456 } 1457 1458 @Override 1459 public void onCallMerged(final ImsCall call, final ImsCall peerCall, boolean swapCalls) { 1460 if (DBG) log("onCallMerged"); 1461 1462 ImsPhoneCall foregroundImsPhoneCall = findConnection(call).getCall(); 1463 ImsPhoneConnection peerConnection = findConnection(peerCall); 1464 ImsPhoneCall peerImsPhoneCall = peerConnection == null ? null 1465 : peerConnection.getCall(); 1466 1467 if (swapCalls) { 1468 switchAfterConferenceSuccess(); 1469 } 1470 foregroundImsPhoneCall.merge(peerImsPhoneCall, ImsPhoneCall.State.ACTIVE); 1471 1472 // TODO Temporary code. Remove the try-catch block from the runnable once thread 1473 // synchronization is fixed. 1474 Runnable r = new Runnable() { 1475 @Override 1476 public void run() { 1477 try { 1478 final ImsPhoneConnection conn = findConnection(call); 1479 log("onCallMerged: ImsPhoneConnection=" + conn); 1480 log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider()); 1481 setVideoCallProvider(conn, call); 1482 log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider()); 1483 } catch (Exception e) { 1484 loge("onCallMerged: exception " + e); 1485 } 1486 } 1487 }; 1488 1489 ImsPhoneCallTracker.this.post(r); 1490 1491 // After merge complete, update foreground as Active 1492 // and background call as Held, if background call exists 1493 processCallStateChange(mForegroundCall.getImsCall(), ImsPhoneCall.State.ACTIVE, 1494 DisconnectCause.NOT_DISCONNECTED); 1495 if (peerConnection != null) { 1496 processCallStateChange(mBackgroundCall.getImsCall(), ImsPhoneCall.State.HOLDING, 1497 DisconnectCause.NOT_DISCONNECTED); 1498 } 1499 1500 // Check if the merge was requested by an existing conference call. In that 1501 // case, no further action is required. 1502 if (!call.isMergeRequestedByConf()) { 1503 log("onCallMerged :: calling onMultipartyStateChanged()"); 1504 onMultipartyStateChanged(call, true); 1505 } else { 1506 log("onCallMerged :: Merge requested by existing conference."); 1507 // Reset the flag. 1508 call.resetIsMergeRequestedByConf(false); 1509 } 1510 logState(); 1511 } 1512 1513 @Override 1514 public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) { 1515 if (DBG) log("onCallMergeFailed reasonInfo=" + reasonInfo); 1516 1517 // TODO: the call to notifySuppServiceFailed throws up the "merge failed" dialog 1518 // We should move this into the InCallService so that it is handled appropriately 1519 // based on the user facing UI. 1520 mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE); 1521 1522 // Start plumbing this even through Telecom so other components can take 1523 // appropriate action. 1524 ImsPhoneConnection conn = findConnection(call); 1525 if (conn != null) { 1526 conn.onConferenceMergeFailed(); 1527 } 1528 } 1529 1530 /** 1531 * Called when the state of IMS conference participant(s) has changed. 1532 * 1533 * @param call the call object that carries out the IMS call. 1534 * @param participants the participant(s) and their new state information. 1535 */ 1536 @Override 1537 public void onConferenceParticipantsStateChanged(ImsCall call, 1538 List<ConferenceParticipant> participants) { 1539 if (DBG) log("onConferenceParticipantsStateChanged"); 1540 1541 ImsPhoneConnection conn = findConnection(call); 1542 if (conn != null) { 1543 conn.updateConferenceParticipants(participants); 1544 } 1545 } 1546 1547 @Override 1548 public void onCallSessionTtyModeReceived(ImsCall call, int mode) { 1549 mPhone.onTtyModeReceived(mode); 1550 } 1551 1552 @Override 1553 public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech, 1554 ImsReasonInfo reasonInfo) { 1555 if (DBG) { 1556 log("onCallHandover :: srcAccessTech=" + srcAccessTech + ", targetAccessTech=" + 1557 targetAccessTech + ", reasonInfo=" + reasonInfo); 1558 } 1559 mEventLog.writeOnImsCallHandover(imsCall.getCallSession(), 1560 srcAccessTech, targetAccessTech, reasonInfo); 1561 } 1562 1563 @Override 1564 public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech, 1565 ImsReasonInfo reasonInfo) { 1566 if (DBG) { 1567 log("onCallHandoverFailed :: srcAccessTech=" + srcAccessTech + 1568 ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + reasonInfo); 1569 } 1570 mEventLog.writeOnImsCallHandoverFailed(imsCall.getCallSession(), 1571 srcAccessTech, targetAccessTech, reasonInfo); 1572 } 1573 1574 /** 1575 * Handles a change to the multiparty state for an {@code ImsCall}. Notifies the associated 1576 * {@link ImsPhoneConnection} of the change. 1577 * 1578 * @param imsCall The IMS call. 1579 * @param isMultiParty {@code true} if the call became multiparty, {@code false} 1580 * otherwise. 1581 */ 1582 @Override 1583 public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) { 1584 if (DBG) log("onMultipartyStateChanged to " + (isMultiParty ? "Y" : "N")); 1585 1586 ImsPhoneConnection conn = findConnection(imsCall); 1587 if (conn != null) { 1588 conn.updateMultipartyState(isMultiParty); 1589 } 1590 } 1591 }; 1592 1593 /** 1594 * Listen to the IMS call state change 1595 */ 1596 private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() { 1597 @Override 1598 public void onCallStarted(ImsCall imsCall) { 1599 if (DBG) log("mImsUssdListener onCallStarted"); 1600 1601 if (imsCall == mUssdSession) { 1602 if (mPendingUssd != null) { 1603 AsyncResult.forMessage(mPendingUssd); 1604 mPendingUssd.sendToTarget(); 1605 mPendingUssd = null; 1606 } 1607 } 1608 } 1609 1610 @Override 1611 public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) { 1612 if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode()); 1613 1614 onCallTerminated(imsCall, reasonInfo); 1615 } 1616 1617 @Override 1618 public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) { 1619 if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode()); 1620 1621 if (imsCall == mUssdSession) { 1622 mUssdSession = null; 1623 if (mPendingUssd != null) { 1624 CommandException ex = 1625 new CommandException(CommandException.Error.GENERIC_FAILURE); 1626 AsyncResult.forMessage(mPendingUssd, null, ex); 1627 mPendingUssd.sendToTarget(); 1628 mPendingUssd = null; 1629 } 1630 } 1631 imsCall.close(); 1632 } 1633 1634 @Override 1635 public void onCallUssdMessageReceived(ImsCall call, 1636 int mode, String ussdMessage) { 1637 if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode); 1638 1639 int ussdMode = -1; 1640 1641 switch(mode) { 1642 case ImsCall.USSD_MODE_REQUEST: 1643 ussdMode = CommandsInterface.USSD_MODE_REQUEST; 1644 break; 1645 1646 case ImsCall.USSD_MODE_NOTIFY: 1647 ussdMode = CommandsInterface.USSD_MODE_NOTIFY; 1648 break; 1649 } 1650 1651 mPhone.onIncomingUSSD(ussdMode, ussdMessage); 1652 } 1653 }; 1654 1655 /** 1656 * Listen to the IMS service state change 1657 * 1658 */ 1659 private ImsConnectionStateListener mImsConnectionStateListener = 1660 new ImsConnectionStateListener() { 1661 @Override 1662 public void onImsConnected() { 1663 if (DBG) log("onImsConnected"); 1664 mPhone.setServiceState(ServiceState.STATE_IN_SERVICE); 1665 mPhone.setImsRegistered(true); 1666 mEventLog.writeOnImsConnectionState( 1667 TelephonyEventLog.IMS_CONNECTION_STATE_CONNECTED, null); 1668 } 1669 1670 @Override 1671 public void onImsDisconnected(ImsReasonInfo imsReasonInfo) { 1672 if (DBG) log("onImsDisconnected imsReasonInfo=" + imsReasonInfo); 1673 mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE); 1674 mPhone.setImsRegistered(false); 1675 mPhone.processDisconnectReason(imsReasonInfo); 1676 mEventLog.writeOnImsConnectionState( 1677 TelephonyEventLog.IMS_CONNECTION_STATE_DISCONNECTED, imsReasonInfo); 1678 } 1679 1680 @Override 1681 public void onImsProgressing() { 1682 if (DBG) log("onImsProgressing"); 1683 mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE); 1684 mPhone.setImsRegistered(false); 1685 mEventLog.writeOnImsConnectionState( 1686 TelephonyEventLog.IMS_CONNECTION_STATE_PROGRESSING, null); 1687 } 1688 1689 @Override 1690 public void onImsResumed() { 1691 if (DBG) log("onImsResumed"); 1692 mPhone.setServiceState(ServiceState.STATE_IN_SERVICE); 1693 mEventLog.writeOnImsConnectionState( 1694 TelephonyEventLog.IMS_CONNECTION_STATE_RESUMED, null); 1695 } 1696 1697 @Override 1698 public void onImsSuspended() { 1699 if (DBG) log("onImsSuspended"); 1700 mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE); 1701 mEventLog.writeOnImsConnectionState( 1702 TelephonyEventLog.IMS_CONNECTION_STATE_SUSPENDED, null); 1703 } 1704 1705 @Override 1706 public void onFeatureCapabilityChanged(int serviceClass, 1707 int[] enabledFeatures, int[] disabledFeatures) { 1708 if (serviceClass == ImsServiceClass.MMTEL) { 1709 boolean tmpIsVideoCallEnabled = isVideoCallEnabled(); 1710 // Check enabledFeatures to determine capabilities. We ignore disabledFeatures. 1711 StringBuilder sb; 1712 if (DBG) { 1713 sb = new StringBuilder(120); 1714 sb.append("onFeatureCapabilityChanged: "); 1715 } 1716 for (int i = ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE; 1717 i <= ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI && 1718 i < enabledFeatures.length; i++) { 1719 if (enabledFeatures[i] == i) { 1720 // If the feature is set to its own integer value it is enabled. 1721 if (DBG) { 1722 sb.append(mImsFeatureStrings[i]); 1723 sb.append(":true "); 1724 } 1725 1726 mImsFeatureEnabled[i] = true; 1727 } else if (enabledFeatures[i] 1728 == ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) { 1729 // FEATURE_TYPE_UNKNOWN indicates that a feature is disabled. 1730 if (DBG) { 1731 sb.append(mImsFeatureStrings[i]); 1732 sb.append(":false "); 1733 } 1734 1735 mImsFeatureEnabled[i] = false; 1736 } else { 1737 // Feature has unknown state; it is not its own value or -1. 1738 if (DBG) { 1739 loge("onFeatureCapabilityChanged(" + i + ", " + mImsFeatureStrings[i] 1740 + "): unexpectedValue=" + enabledFeatures[i]); 1741 } 1742 } 1743 } 1744 if (DBG) { 1745 log(sb.toString()); 1746 } 1747 if (tmpIsVideoCallEnabled != isVideoCallEnabled()) { 1748 mPhone.notifyForVideoCapabilityChanged(isVideoCallEnabled()); 1749 } 1750 1751 // TODO: Use the ImsCallSession or ImsCallProfile to tell the initial Wifi state and 1752 // {@link ImsCallSession.Listener#callSessionHandover} to listen for changes to 1753 // wifi capability caused by a handover. 1754 if (DBG) log("onFeatureCapabilityChanged: isVolteEnabled=" + isVolteEnabled() 1755 + ", isVideoCallEnabled=" + isVideoCallEnabled() 1756 + ", isVowifiEnabled=" + isVowifiEnabled() 1757 + ", isUtEnabled=" + isUtEnabled()); 1758 for (ImsPhoneConnection connection : mConnections) { 1759 connection.updateWifiState(); 1760 } 1761 1762 mPhone.onFeatureCapabilityChanged(); 1763 1764 mEventLog.writeOnImsCapabilities(mImsFeatureEnabled); 1765 } 1766 } 1767 1768 @Override 1769 public void onVoiceMessageCountChanged(int count) { 1770 if (DBG) log("onVoiceMessageCountChanged :: count=" + count); 1771 mPhone.mDefaultPhone.setVoiceMessageCount(count); 1772 } 1773 }; 1774 1775 /* package */ 1776 ImsUtInterface getUtInterface() throws ImsException { 1777 if (mImsManager == null) { 1778 throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED); 1779 } 1780 1781 ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId); 1782 return ut; 1783 } 1784 1785 private void transferHandoverConnections(ImsPhoneCall call) { 1786 if (call.mConnections != null) { 1787 for (Connection c : call.mConnections) { 1788 c.mPreHandoverState = call.mState; 1789 log ("Connection state before handover is " + c.getStateBeforeHandover()); 1790 } 1791 } 1792 if (mHandoverCall.mConnections == null ) { 1793 mHandoverCall.mConnections = call.mConnections; 1794 } else { // Multi-call SRVCC 1795 mHandoverCall.mConnections.addAll(call.mConnections); 1796 } 1797 if (mHandoverCall.mConnections != null) { 1798 if (call.getImsCall() != null) { 1799 call.getImsCall().close(); 1800 } 1801 for (Connection c : mHandoverCall.mConnections) { 1802 ((ImsPhoneConnection)c).changeParent(mHandoverCall); 1803 ((ImsPhoneConnection)c).releaseWakeLock(); 1804 } 1805 } 1806 if (call.getState().isAlive()) { 1807 log ("Call is alive and state is " + call.mState); 1808 mHandoverCall.mState = call.mState; 1809 } 1810 call.mConnections.clear(); 1811 call.mState = ImsPhoneCall.State.IDLE; 1812 } 1813 1814 /* package */ 1815 void notifySrvccState(Call.SrvccState state) { 1816 if (DBG) log("notifySrvccState state=" + state); 1817 1818 mSrvccState = state; 1819 1820 if (mSrvccState == Call.SrvccState.COMPLETED) { 1821 transferHandoverConnections(mForegroundCall); 1822 transferHandoverConnections(mBackgroundCall); 1823 transferHandoverConnections(mRingingCall); 1824 } 1825 } 1826 1827 //****** Overridden from Handler 1828 1829 @Override 1830 public void 1831 handleMessage (Message msg) { 1832 AsyncResult ar; 1833 if (DBG) log("handleMessage what=" + msg.what); 1834 1835 switch (msg.what) { 1836 case EVENT_HANGUP_PENDINGMO: 1837 if (mPendingMO != null) { 1838 mPendingMO.onDisconnect(); 1839 removeConnection(mPendingMO); 1840 mPendingMO = null; 1841 } 1842 mPendingIntentExtras = null; 1843 updatePhoneState(); 1844 mPhone.notifyPreciseCallStateChanged(); 1845 break; 1846 case EVENT_RESUME_BACKGROUND: 1847 try { 1848 resumeWaitingOrHolding(); 1849 } catch (CallStateException e) { 1850 if (Phone.DEBUG_PHONE) { 1851 loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e); 1852 } 1853 } 1854 break; 1855 case EVENT_DIAL_PENDINGMO: 1856 dialInternal(mPendingMO, mClirMode, mPendingCallVideoState, mPendingIntentExtras); 1857 mPendingIntentExtras = null; 1858 break; 1859 1860 case EVENT_EXIT_ECM_RESPONSE_CDMA: 1861 // no matter the result, we still do the same here 1862 if (pendingCallInEcm) { 1863 dialInternal(mPendingMO, pendingCallClirMode, 1864 mPendingCallVideoState, mPendingIntentExtras); 1865 mPendingIntentExtras = null; 1866 pendingCallInEcm = false; 1867 } 1868 mPhone.unsetOnEcbModeExitResponse(this); 1869 break; 1870 } 1871 } 1872 1873 @Override 1874 protected void log(String msg) { 1875 Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg); 1876 } 1877 1878 protected void loge(String msg) { 1879 Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg); 1880 } 1881 1882 /** 1883 * Logs the current state of the ImsPhoneCallTracker. Useful for debugging issues with 1884 * call tracking. 1885 */ 1886 /* package */ 1887 void logState() { 1888 if (!VERBOSE_STATE_LOGGING) { 1889 return; 1890 } 1891 1892 StringBuilder sb = new StringBuilder(); 1893 sb.append("Current IMS PhoneCall State:\n"); 1894 sb.append(" Foreground: "); 1895 sb.append(mForegroundCall); 1896 sb.append("\n"); 1897 sb.append(" Background: "); 1898 sb.append(mBackgroundCall); 1899 sb.append("\n"); 1900 sb.append(" Ringing: "); 1901 sb.append(mRingingCall); 1902 sb.append("\n"); 1903 sb.append(" Handover: "); 1904 sb.append(mHandoverCall); 1905 sb.append("\n"); 1906 Rlog.v(LOG_TAG, sb.toString()); 1907 } 1908 1909 @Override 1910 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1911 pw.println("ImsPhoneCallTracker extends:"); 1912 super.dump(fd, pw, args); 1913 pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants); 1914 pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants); 1915 pw.println(" mRingingCall=" + mRingingCall); 1916 pw.println(" mForegroundCall=" + mForegroundCall); 1917 pw.println(" mBackgroundCall=" + mBackgroundCall); 1918 pw.println(" mHandoverCall=" + mHandoverCall); 1919 pw.println(" mPendingMO=" + mPendingMO); 1920 //pw.println(" mHangupPendingMO=" + mHangupPendingMO); 1921 pw.println(" mPhone=" + mPhone); 1922 pw.println(" mDesiredMute=" + mDesiredMute); 1923 pw.println(" mState=" + mState); 1924 for (int i = 0; i < mImsFeatureEnabled.length; i++) { 1925 pw.println(" " + mImsFeatureStrings[i] + ": " 1926 + ((mImsFeatureEnabled[i]) ? "enabled" : "disabled")); 1927 } 1928 pw.flush(); 1929 pw.println("++++++++++++++++++++++++++++++++"); 1930 1931 try { 1932 if (mImsManager != null) { 1933 mImsManager.dump(fd, pw, args); 1934 } 1935 } catch (Exception e) { 1936 e.printStackTrace(); 1937 } 1938 1939 if (mConnections != null && mConnections.size() > 0) { 1940 pw.println("mConnections:"); 1941 for (int i = 0; i < mConnections.size(); i++) { 1942 pw.println(" [" + i + "]: " + mConnections.get(i)); 1943 } 1944 } 1945 } 1946 1947 @Override 1948 protected void handlePollCalls(AsyncResult ar) { 1949 } 1950 1951 /* package */ 1952 ImsEcbm getEcbmInterface() throws ImsException { 1953 if (mImsManager == null) { 1954 throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED); 1955 } 1956 1957 ImsEcbm ecbm = mImsManager.getEcbmInterface(mServiceId); 1958 return ecbm; 1959 } 1960 1961 public boolean isInEmergencyCall() { 1962 return mIsInEmergencyCall; 1963 } 1964 1965 public boolean isVolteEnabled() { 1966 return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE]; 1967 } 1968 1969 public boolean isVowifiEnabled() { 1970 return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI]; 1971 } 1972 1973 public boolean isVideoCallEnabled() { 1974 return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE] 1975 || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI]); 1976 } 1977 1978 @Override 1979 public PhoneConstants.State getState() { 1980 return mState; 1981 } 1982 1983 private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall) 1984 throws RemoteException { 1985 IImsVideoCallProvider imsVideoCallProvider = 1986 imsCall.getCallSession().getVideoCallProvider(); 1987 if (imsVideoCallProvider != null) { 1988 ImsVideoCallProviderWrapper imsVideoCallProviderWrapper = 1989 new ImsVideoCallProviderWrapper(imsVideoCallProvider); 1990 conn.setVideoProvider(imsVideoCallProviderWrapper); 1991 } 1992 } 1993 1994 public boolean isUtEnabled() { 1995 return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_LTE] 1996 || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI]); 1997 } 1998 1999 /** 2000 * Given a call subject, removes any characters considered by the current carrier to be 2001 * invalid, as well as escaping (using \) any characters which the carrier requires to be 2002 * escaped. 2003 * 2004 * @param callSubject The call subject. 2005 * @return The call subject with invalid characters removed and escaping applied as required. 2006 */ 2007 private String cleanseInstantLetteringMessage(String callSubject) { 2008 if (TextUtils.isEmpty(callSubject)) { 2009 return callSubject; 2010 } 2011 2012 // Get the carrier config for the current sub. 2013 CarrierConfigManager configMgr = (CarrierConfigManager) 2014 mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 2015 // Bail if we can't find the carrier config service. 2016 if (configMgr == null) { 2017 return callSubject; 2018 } 2019 2020 PersistableBundle carrierConfig = configMgr.getConfigForSubId(mPhone.getSubId()); 2021 // Bail if no carrier config found. 2022 if (carrierConfig == null) { 2023 return callSubject; 2024 } 2025 2026 // Try to replace invalid characters 2027 String invalidCharacters = carrierConfig.getString( 2028 CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING); 2029 if (!TextUtils.isEmpty(invalidCharacters)) { 2030 callSubject = callSubject.replaceAll(invalidCharacters, ""); 2031 } 2032 2033 // Try to escape characters which need to be escaped. 2034 String escapedCharacters = carrierConfig.getString( 2035 CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING); 2036 if (!TextUtils.isEmpty(escapedCharacters)) { 2037 callSubject = escapeChars(escapedCharacters, callSubject); 2038 } 2039 return callSubject; 2040 } 2041 2042 /** 2043 * Given a source string, return a string where a set of characters are escaped using the 2044 * backslash character. 2045 * 2046 * @param toEscape The characters to escape with a backslash. 2047 * @param source The source string. 2048 * @return The source string with characters escaped. 2049 */ 2050 private String escapeChars(String toEscape, String source) { 2051 StringBuilder escaped = new StringBuilder(); 2052 for (char c : source.toCharArray()) { 2053 if (toEscape.contains(Character.toString(c))) { 2054 escaped.append("\\"); 2055 } 2056 escaped.append(c); 2057 } 2058 2059 return escaped.toString(); 2060 } 2061} 2062