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