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