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