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