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