ImsPhoneCallTracker.java revision 50a6eae6fb10462b1edb12cc30cb9701ca18a212
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 android.app.PendingIntent;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.SharedPreferences;
25import android.net.ConnectivityManager;
26import android.net.NetworkInfo;
27import android.net.Uri;
28import android.os.AsyncResult;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.Message;
32import android.os.PersistableBundle;
33import android.os.Registrant;
34import android.os.RegistrantList;
35import android.os.RemoteException;
36import android.os.SystemProperties;
37import android.preference.PreferenceManager;
38import android.provider.Settings;
39import android.telecom.ConferenceParticipant;
40import android.telecom.VideoProfile;
41import android.telephony.CarrierConfigManager;
42import android.telephony.DisconnectCause;
43import android.telephony.PreciseDisconnectCause;
44import android.telephony.PhoneNumberUtils;
45import android.telephony.Rlog;
46import android.telephony.ServiceState;
47import android.telephony.SubscriptionManager;
48import android.telephony.TelephonyManager;
49import android.telephony.ims.ImsServiceProxy;
50import android.telephony.ims.feature.ImsFeature;
51import android.text.TextUtils;
52import android.util.SparseIntArray;
53import android.util.ArrayMap;
54import android.util.Log;
55import android.util.Pair;
56
57import com.android.ims.ImsCall;
58import com.android.ims.ImsCallProfile;
59import com.android.ims.ImsConfig;
60import com.android.ims.ImsConfigListener;
61import com.android.ims.ImsConnectionStateListener;
62import com.android.ims.ImsEcbm;
63import com.android.ims.ImsException;
64import com.android.ims.ImsManager;
65import com.android.ims.ImsMultiEndpoint;
66import com.android.ims.ImsReasonInfo;
67import com.android.ims.ImsServiceClass;
68import com.android.ims.ImsSuppServiceNotification;
69import com.android.ims.ImsUtInterface;
70import com.android.ims.internal.IImsVideoCallProvider;
71import com.android.ims.internal.ImsVideoCallProviderWrapper;
72import com.android.internal.annotations.VisibleForTesting;
73import com.android.internal.telephony.Call;
74import com.android.internal.telephony.CallStateException;
75import com.android.internal.telephony.CallTracker;
76import com.android.internal.telephony.CommandException;
77import com.android.internal.telephony.CommandsInterface;
78import com.android.internal.telephony.Connection;
79import com.android.internal.telephony.Phone;
80import com.android.internal.telephony.PhoneConstants;
81import com.android.internal.telephony.TelephonyProperties;
82import com.android.internal.telephony.TelephonyProto.ImsConnectionState;
83import com.android.internal.telephony.TelephonyProto.TelephonyCallSession;
84import com.android.internal.telephony.TelephonyProto.TelephonyCallSession.Event.ImsCommand;
85import com.android.internal.telephony.dataconnection.DataEnabledSettings;
86import com.android.internal.telephony.gsm.SuppServiceNotification;
87import com.android.internal.telephony.metrics.TelephonyMetrics;
88
89import java.io.FileDescriptor;
90import java.io.PrintWriter;
91import java.util.ArrayList;
92import java.util.HashMap;
93import java.util.List;
94import java.util.Map;
95import java.util.regex.Pattern;
96
97/**
98 * {@hide}
99 */
100public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
101    static final String LOG_TAG = "ImsPhoneCallTracker";
102    static final String VERBOSE_STATE_TAG = "IPCTState";
103
104    public interface PhoneStateListener {
105        void onPhoneStateChanged(PhoneConstants.State oldState, PhoneConstants.State newState);
106    }
107
108    private static final boolean DBG = true;
109
110    // When true, dumps the state of ImsPhoneCallTracker after changes to foreground and background
111    // calls.  This is helpful for debugging.  It is also possible to enable this at runtime by
112    // setting the IPCTState log tag to VERBOSE.
113    private static final boolean FORCE_VERBOSE_STATE_LOGGING = false; /* stopship if true */
114    private static final boolean VERBOSE_STATE_LOGGING = FORCE_VERBOSE_STATE_LOGGING ||
115            Rlog.isLoggable(VERBOSE_STATE_TAG, Log.VERBOSE);
116
117    //Indices map to ImsConfig.FeatureConstants
118    private boolean[] mImsFeatureEnabled = {false, false, false, false, false, false};
119    private final String[] mImsFeatureStrings = {"VoLTE", "ViLTE", "VoWiFi", "ViWiFi",
120            "UTLTE", "UTWiFi"};
121
122    private TelephonyMetrics mMetrics;
123
124    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
125        @Override
126        public void onReceive(Context context, Intent intent) {
127            if (intent.getAction().equals(ImsManager.ACTION_IMS_INCOMING_CALL)) {
128                if (DBG) log("onReceive : incoming call intent");
129
130                if (mImsManager == null) return;
131
132                if (mServiceId < 0) return;
133
134                try {
135                    // Network initiated USSD will be treated by mImsUssdListener
136                    boolean isUssd = intent.getBooleanExtra(ImsManager.EXTRA_USSD, false);
137                    if (isUssd) {
138                        if (DBG) log("onReceive : USSD");
139                        mUssdSession = mImsManager.takeCall(mServiceId, intent, mImsUssdListener);
140                        if (mUssdSession != null) {
141                            mUssdSession.accept(ImsCallProfile.CALL_TYPE_VOICE);
142                        }
143                        return;
144                    }
145
146                    boolean isUnknown = intent.getBooleanExtra(ImsManager.EXTRA_IS_UNKNOWN_CALL,
147                            false);
148                    if (DBG) {
149                        log("onReceive : isUnknown = " + isUnknown +
150                                " fg = " + mForegroundCall.getState() +
151                                " bg = " + mBackgroundCall.getState());
152                    }
153
154                    // Normal MT/Unknown call
155                    ImsCall imsCall = mImsManager.takeCall(mServiceId, intent, mImsCallListener);
156                    ImsPhoneConnection conn = new ImsPhoneConnection(mPhone, imsCall,
157                            ImsPhoneCallTracker.this,
158                            (isUnknown? mForegroundCall: mRingingCall), isUnknown);
159
160                    // If there is an active call.
161                    if (mForegroundCall.hasConnections()) {
162                        ImsCall activeCall = mForegroundCall.getFirstConnection().getImsCall();
163                        boolean answeringWillDisconnect =
164                                shouldDisconnectActiveCallOnAnswer(activeCall, imsCall);
165                        conn.setActiveCallDisconnectedOnAnswer(answeringWillDisconnect);
166                    }
167                    conn.setAllowAddCallDuringVideoCall(mAllowAddCallDuringVideoCall);
168                    addConnection(conn);
169
170                    setVideoCallProvider(conn, imsCall);
171
172                    TelephonyMetrics.getInstance().writeOnImsCallReceive(mPhone.getPhoneId(),
173                            imsCall.getSession());
174
175                    if (isUnknown) {
176                        mPhone.notifyUnknownConnection(conn);
177                    } else {
178                        if ((mForegroundCall.getState() != ImsPhoneCall.State.IDLE) ||
179                                (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE)) {
180                            conn.update(imsCall, ImsPhoneCall.State.WAITING);
181                        }
182
183                        mPhone.notifyNewRingingConnection(conn);
184                        mPhone.notifyIncomingRing();
185                    }
186
187                    updatePhoneState();
188                    mPhone.notifyPreciseCallStateChanged();
189                } catch (ImsException e) {
190                    loge("onReceive : exception " + e);
191                } catch (RemoteException e) {
192                }
193            } else if (intent.getAction().equals(
194                    CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
195                int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
196                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
197                if (subId == mPhone.getSubId()) {
198                    cacheCarrierConfiguration(subId);
199                    log("onReceive : Updating mAllowEmergencyVideoCalls = " +
200                            mAllowEmergencyVideoCalls);
201                }
202            }
203        }
204    };
205
206    //***** Constants
207
208    static final int MAX_CONNECTIONS = 7;
209    static final int MAX_CONNECTIONS_PER_CALL = 5;
210
211    private static final int EVENT_HANGUP_PENDINGMO = 18;
212    private static final int EVENT_RESUME_BACKGROUND = 19;
213    private static final int EVENT_DIAL_PENDINGMO = 20;
214    private static final int EVENT_EXIT_ECBM_BEFORE_PENDINGMO = 21;
215    private static final int EVENT_VT_DATA_USAGE_UPDATE = 22;
216    private static final int EVENT_DATA_ENABLED_CHANGED = 23;
217    private static final int EVENT_GET_IMS_SERVICE = 24;
218    private static final int EVENT_CHECK_FOR_WIFI_HANDOVER = 25;
219    private static final int EVENT_CLEAR_DISCONNECTING_CONN = 26;
220
221    private static final int TIMEOUT_HANGUP_PENDINGMO = 500;
222
223    // The number of milliseconds the CallTracker will wait before manually disconnecting the
224    // connection due to the modem not responding.
225    private static final int TIMEOUT_CLEAR_DISCONNECTING_CONN = 5000;
226
227    // Initial condition for ims connection retry.
228    private static final int IMS_RETRY_STARTING_TIMEOUT_MS = 500; // ms
229    // Ceiling bitshift amount for service query timeout, calculated as:
230    // 2^mImsServiceRetryCount * IMS_RETRY_STARTING_TIMEOUT_MS, where
231    // mImsServiceRetryCount ∊ [0, CEILING_SERVICE_RETRY_COUNT].
232    private static final int CEILING_SERVICE_RETRY_COUNT = 6;
233
234    private static final int HANDOVER_TO_WIFI_TIMEOUT_MS = 60000; // ms
235
236    //***** Instance Variables
237    private ArrayList<ImsPhoneConnection> mConnections = new ArrayList<ImsPhoneConnection>();
238    private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList();
239    private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList();
240
241    public ImsPhoneCall mRingingCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_RINGING);
242    public ImsPhoneCall mForegroundCall = new ImsPhoneCall(this,
243            ImsPhoneCall.CONTEXT_FOREGROUND);
244    public ImsPhoneCall mBackgroundCall = new ImsPhoneCall(this,
245            ImsPhoneCall.CONTEXT_BACKGROUND);
246    public ImsPhoneCall mHandoverCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_HANDOVER);
247
248    // Hold aggregated video call data usage for each video call since boot.
249    // The ImsCall's call id is the key of the map.
250    private final HashMap<Integer, Long> mVtDataUsageMap = new HashMap<>();
251    private volatile long mTotalVtDataUsage = 0;
252
253    private ImsPhoneConnection mPendingMO;
254    private int mClirMode = CommandsInterface.CLIR_DEFAULT;
255    private Object mSyncHold = new Object();
256
257    private ImsCall mUssdSession = null;
258    private Message mPendingUssd = null;
259
260    ImsPhone mPhone;
261
262    private boolean mDesiredMute = false;    // false = mute off
263    private boolean mOnHoldToneStarted = false;
264    private int mOnHoldToneId = -1;
265
266    private PhoneConstants.State mState = PhoneConstants.State.IDLE;
267
268    private int mImsServiceRetryCount;
269    private ImsManager mImsManager;
270    private int mServiceId = -1;
271
272    private Call.SrvccState mSrvccState = Call.SrvccState.NONE;
273
274    private boolean mIsInEmergencyCall = false;
275
276    private int pendingCallClirMode;
277    private int mPendingCallVideoState;
278    private Bundle mPendingIntentExtras;
279    private boolean pendingCallInEcm = false;
280    private boolean mSwitchingFgAndBgCalls = false;
281    private ImsCall mCallExpectedToResume = null;
282    private boolean mAllowEmergencyVideoCalls = false;
283    private boolean mIgnoreDataEnabledChangedForVideoCalls = false;
284
285    /**
286     * Listeners to changes in the phone state.  Intended for use by other interested IMS components
287     * without the need to register a full blown {@link android.telephony.PhoneStateListener}.
288     */
289    private List<PhoneStateListener> mPhoneStateListeners = new ArrayList<>();
290
291    /**
292     * Carrier configuration option which determines if video calls which have been downgraded to an
293     * audio call should be treated as if they are still video calls.
294     */
295    private boolean mTreatDowngradedVideoCallsAsVideoCalls = false;
296
297    /**
298     * Carrier configuration option which determines if an ongoing video call over wifi should be
299     * dropped when an audio call is answered.
300     */
301    private boolean mDropVideoCallWhenAnsweringAudioCall = false;
302
303    /**
304     * Carrier configuration option which determines whether adding a call during a video call
305     * should be allowed.
306     */
307    private boolean mAllowAddCallDuringVideoCall = true;
308
309    /**
310     * Carrier configuration option which determines whether to notify the connection if a handover
311     * to wifi fails.
312     */
313    private boolean mNotifyVtHandoverToWifiFail = false;
314
315    /**
316     * Carrier configuration option which determines whether the carrier supports downgrading a
317     * TX/RX/TX-RX video call directly to an audio-only call.
318     */
319    private boolean mSupportDowngradeVtToAudio = false;
320
321    /**
322     * Stores the mapping of {@code ImsReasonInfo#CODE_*} to {@code PreciseDisconnectCause#*}
323     */
324    private static final SparseIntArray PRECISE_CAUSE_MAP = new SparseIntArray();
325    static {
326        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT,
327                PreciseDisconnectCause.LOCAL_ILLEGAL_ARGUMENT);
328        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE,
329                PreciseDisconnectCause.LOCAL_ILLEGAL_STATE);
330        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_INTERNAL_ERROR,
331                PreciseDisconnectCause.LOCAL_INTERNAL_ERROR);
332        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN,
333                PreciseDisconnectCause.LOCAL_IMS_SERVICE_DOWN);
334        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_NO_PENDING_CALL,
335                PreciseDisconnectCause.LOCAL_NO_PENDING_CALL);
336        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_POWER_OFF,
337                PreciseDisconnectCause.LOCAL_POWER_OFF);
338        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_LOW_BATTERY,
339                PreciseDisconnectCause.LOCAL_LOW_BATTERY);
340        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE,
341                PreciseDisconnectCause.LOCAL_NETWORK_NO_SERVICE);
342        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE,
343                PreciseDisconnectCause.LOCAL_NETWORK_NO_LTE_COVERAGE);
344        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING,
345                PreciseDisconnectCause.LOCAL_NETWORK_ROAMING);
346        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED,
347                PreciseDisconnectCause.LOCAL_NETWORK_IP_CHANGED);
348        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE,
349                PreciseDisconnectCause.LOCAL_SERVICE_UNAVAILABLE);
350        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED,
351                PreciseDisconnectCause.LOCAL_NOT_REGISTERED);
352        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_EXCEEDED,
353                PreciseDisconnectCause.LOCAL_MAX_CALL_EXCEEDED);
354        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_DECLINE,
355                PreciseDisconnectCause.LOCAL_CALL_DECLINE);
356        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING,
357                PreciseDisconnectCause.LOCAL_CALL_VCC_ON_PROGRESSING);
358        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_RESOURCE_RESERVATION_FAILED,
359                PreciseDisconnectCause.LOCAL_CALL_RESOURCE_RESERVATION_FAILED);
360        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED,
361                PreciseDisconnectCause.LOCAL_CALL_CS_RETRY_REQUIRED);
362        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_VOLTE_RETRY_REQUIRED,
363                PreciseDisconnectCause.LOCAL_CALL_VOLTE_RETRY_REQUIRED);
364        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED,
365                PreciseDisconnectCause.LOCAL_CALL_TERMINATED);
366        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_HO_NOT_FEASIBLE,
367                PreciseDisconnectCause.LOCAL_HO_NOT_FEASIBLE);
368        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING,
369                PreciseDisconnectCause.TIMEOUT_1XX_WAITING);
370        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER,
371                PreciseDisconnectCause.TIMEOUT_NO_ANSWER);
372        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE,
373                PreciseDisconnectCause.TIMEOUT_NO_ANSWER_CALL_UPDATE);
374        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_FDN_BLOCKED,
375                PreciseDisconnectCause.FDN_BLOCKED);
376        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_REDIRECTED,
377                PreciseDisconnectCause.SIP_REDIRECTED);
378        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_BAD_REQUEST,
379                PreciseDisconnectCause.SIP_BAD_REQUEST);
380        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_FORBIDDEN,
381                PreciseDisconnectCause.SIP_FORBIDDEN);
382        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_NOT_FOUND,
383                PreciseDisconnectCause.SIP_NOT_FOUND);
384        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_NOT_SUPPORTED,
385                PreciseDisconnectCause.SIP_NOT_SUPPORTED);
386        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT,
387                PreciseDisconnectCause.SIP_REQUEST_TIMEOUT);
388        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_TEMPRARILY_UNAVAILABLE,
389                PreciseDisconnectCause.SIP_TEMPRARILY_UNAVAILABLE);
390        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_BAD_ADDRESS,
391                PreciseDisconnectCause.SIP_BAD_ADDRESS);
392        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_BUSY,
393                PreciseDisconnectCause.SIP_BUSY);
394        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_REQUEST_CANCELLED,
395                PreciseDisconnectCause.SIP_REQUEST_CANCELLED);
396        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE,
397                PreciseDisconnectCause.SIP_NOT_ACCEPTABLE);
398        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_NOT_REACHABLE,
399                PreciseDisconnectCause.SIP_NOT_REACHABLE);
400        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_CLIENT_ERROR,
401                PreciseDisconnectCause.SIP_CLIENT_ERROR);
402        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_SERVER_INTERNAL_ERROR,
403                PreciseDisconnectCause.SIP_SERVER_INTERNAL_ERROR);
404        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE,
405                PreciseDisconnectCause.SIP_SERVICE_UNAVAILABLE);
406        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_SERVER_TIMEOUT,
407                PreciseDisconnectCause.SIP_SERVER_TIMEOUT);
408        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_SERVER_ERROR,
409                PreciseDisconnectCause.SIP_SERVER_ERROR);
410        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_USER_REJECTED,
411                PreciseDisconnectCause.SIP_USER_REJECTED);
412        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_GLOBAL_ERROR,
413                PreciseDisconnectCause.SIP_GLOBAL_ERROR);
414        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_EMERGENCY_TEMP_FAILURE,
415                PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE);
416        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_EMERGENCY_PERM_FAILURE,
417                PreciseDisconnectCause.EMERGENCY_PERM_FAILURE);
418        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_MEDIA_INIT_FAILED,
419                PreciseDisconnectCause.MEDIA_INIT_FAILED);
420        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_MEDIA_NO_DATA,
421                PreciseDisconnectCause.MEDIA_NO_DATA);
422        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_MEDIA_NOT_ACCEPTABLE,
423                PreciseDisconnectCause.MEDIA_NOT_ACCEPTABLE);
424        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_MEDIA_UNSPECIFIED,
425                PreciseDisconnectCause.MEDIA_UNSPECIFIED);
426        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_USER_TERMINATED,
427                PreciseDisconnectCause.USER_TERMINATED);
428        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_USER_NOANSWER,
429                PreciseDisconnectCause.USER_NOANSWER);
430        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_USER_IGNORE,
431                PreciseDisconnectCause.USER_IGNORE);
432        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_USER_DECLINE,
433                PreciseDisconnectCause.USER_DECLINE);
434        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOW_BATTERY,
435                PreciseDisconnectCause.LOW_BATTERY);
436        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_BLACKLISTED_CALL_ID,
437                PreciseDisconnectCause.BLACKLISTED_CALL_ID);
438        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE,
439                PreciseDisconnectCause.USER_TERMINATED_BY_REMOTE);
440        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_UT_NOT_SUPPORTED,
441                PreciseDisconnectCause.UT_NOT_SUPPORTED);
442        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE,
443                PreciseDisconnectCause.UT_SERVICE_UNAVAILABLE);
444        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_UT_OPERATION_NOT_ALLOWED,
445                PreciseDisconnectCause.UT_OPERATION_NOT_ALLOWED);
446        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_UT_NETWORK_ERROR,
447                PreciseDisconnectCause.UT_NETWORK_ERROR);
448        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_UT_CB_PASSWORD_MISMATCH,
449                PreciseDisconnectCause.UT_CB_PASSWORD_MISMATCH);
450        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_ECBM_NOT_SUPPORTED,
451                PreciseDisconnectCause.ECBM_NOT_SUPPORTED);
452        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_MULTIENDPOINT_NOT_SUPPORTED,
453                PreciseDisconnectCause.MULTIENDPOINT_NOT_SUPPORTED);
454        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE,
455                PreciseDisconnectCause.CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE);
456        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE,
457                PreciseDisconnectCause.ANSWERED_ELSEWHERE);
458        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_CALL_PULL_OUT_OF_SYNC,
459                PreciseDisconnectCause.CALL_PULL_OUT_OF_SYNC);
460        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_CALL_END_CAUSE_CALL_PULL,
461                PreciseDisconnectCause.CALL_PULLED);
462        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SUPP_SVC_FAILED,
463                PreciseDisconnectCause.SUPP_SVC_FAILED);
464        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SUPP_SVC_CANCELLED,
465                PreciseDisconnectCause.SUPP_SVC_CANCELLED);
466        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SUPP_SVC_REINVITE_COLLISION,
467                PreciseDisconnectCause.SUPP_SVC_REINVITE_COLLISION);
468        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_IWLAN_DPD_FAILURE,
469                PreciseDisconnectCause.IWLAN_DPD_FAILURE);
470        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_EPDG_TUNNEL_ESTABLISH_FAILURE,
471                PreciseDisconnectCause.EPDG_TUNNEL_ESTABLISH_FAILURE);
472        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_EPDG_TUNNEL_REKEY_FAILURE,
473                PreciseDisconnectCause.EPDG_TUNNEL_REKEY_FAILURE);
474        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_EPDG_TUNNEL_LOST_CONNECTION,
475                PreciseDisconnectCause.EPDG_TUNNEL_LOST_CONNECTION);
476        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_MAXIMUM_NUMBER_OF_CALLS_REACHED,
477                PreciseDisconnectCause.MAXIMUM_NUMBER_OF_CALLS_REACHED);
478        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_REMOTE_CALL_DECLINE,
479                PreciseDisconnectCause.REMOTE_CALL_DECLINE);
480        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_DATA_LIMIT_REACHED,
481                PreciseDisconnectCause.DATA_LIMIT_REACHED);
482        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_DATA_DISABLED,
483                PreciseDisconnectCause.DATA_DISABLED);
484        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_WIFI_LOST,
485                PreciseDisconnectCause.WIFI_LOST);
486        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_OFF,
487                PreciseDisconnectCause.RADIO_OFF);
488        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_NO_VALID_SIM,
489                PreciseDisconnectCause.NO_VALID_SIM);
490        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_INTERNAL_ERROR,
491                PreciseDisconnectCause.RADIO_INTERNAL_ERROR);
492        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_NETWORK_RESP_TIMEOUT,
493                PreciseDisconnectCause.NETWORK_RESP_TIMEOUT);
494        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_NETWORK_REJECT,
495                PreciseDisconnectCause.NETWORK_REJECT);
496        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_ACCESS_FAILURE,
497                PreciseDisconnectCause.RADIO_ACCESS_FAILURE);
498        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_LINK_FAILURE,
499                PreciseDisconnectCause.RADIO_LINK_FAILURE);
500        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_LINK_LOST,
501                PreciseDisconnectCause.RADIO_LINK_LOST);
502        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_UPLINK_FAILURE,
503                PreciseDisconnectCause.RADIO_UPLINK_FAILURE);
504        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_SETUP_FAILURE,
505                PreciseDisconnectCause.RADIO_SETUP_FAILURE);
506        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_RELEASE_NORMAL,
507                PreciseDisconnectCause.RADIO_RELEASE_NORMAL);
508        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_RELEASE_ABNORMAL,
509                PreciseDisconnectCause.RADIO_RELEASE_ABNORMAL);
510        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_ACCESS_CLASS_BLOCKED,
511                PreciseDisconnectCause.ACCESS_CLASS_BLOCKED);
512        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_NETWORK_DETACH,
513                PreciseDisconnectCause.NETWORK_DETACH);
514        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_1,
515                PreciseDisconnectCause.OEM_CAUSE_1);
516        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_2,
517                PreciseDisconnectCause.OEM_CAUSE_2);
518        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_3,
519                PreciseDisconnectCause.OEM_CAUSE_3);
520        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_4,
521                PreciseDisconnectCause.OEM_CAUSE_4);
522        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_5,
523                PreciseDisconnectCause.OEM_CAUSE_5);
524        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_6,
525                PreciseDisconnectCause.OEM_CAUSE_6);
526        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_7,
527                PreciseDisconnectCause.OEM_CAUSE_7);
528        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_8,
529                PreciseDisconnectCause.OEM_CAUSE_8);
530        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_9,
531                PreciseDisconnectCause.OEM_CAUSE_9);
532        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_10,
533                PreciseDisconnectCause.OEM_CAUSE_10);
534        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_11,
535                PreciseDisconnectCause.OEM_CAUSE_11);
536        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_12,
537                PreciseDisconnectCause.OEM_CAUSE_12);
538        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_13,
539                PreciseDisconnectCause.OEM_CAUSE_13);
540        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_14,
541                PreciseDisconnectCause.OEM_CAUSE_14);
542        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_15,
543                PreciseDisconnectCause.OEM_CAUSE_15);
544    }
545
546    /**
547     * Carrier configuration option which determines whether the carrier wants to inform the user
548     * when a video call is handed over from WIFI to LTE.
549     * See {@link CarrierConfigManager#KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL} for more
550     * information.
551     */
552    private boolean mNotifyHandoverVideoFromWifiToLTE = false;
553
554    /**
555     * Carrier configuration option which defines a mapping from pairs of
556     * {@link ImsReasonInfo#getCode()} and {@link ImsReasonInfo#getExtraMessage()} values to a new
557     * {@code ImsReasonInfo#CODE_*} value.
558     *
559     * See {@link CarrierConfigManager#KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY}.
560     */
561    private Map<Pair<Integer, String>, Integer> mImsReasonCodeMap = new ArrayMap<>();
562
563    private ImsServiceProxy.INotifyStatusChanged mNotifyFeatureRemovedCallback = () -> {
564        try {
565            if (mImsManager.getImsServiceStatus() != ImsFeature.STATE_READY) {
566                retryGetImsService();
567            }
568        } catch (ImsException e) {
569            // Could not get the ImsService, retry!
570            retryGetImsService();
571        }
572    };
573
574    //***** Events
575
576
577    //***** Constructors
578
579    public ImsPhoneCallTracker(ImsPhone phone) {
580        this.mPhone = phone;
581
582        mMetrics = TelephonyMetrics.getInstance();
583
584        IntentFilter intentfilter = new IntentFilter();
585        intentfilter.addAction(ImsManager.ACTION_IMS_INCOMING_CALL);
586        intentfilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
587        mPhone.getContext().registerReceiver(mReceiver, intentfilter);
588        cacheCarrierConfiguration(mPhone.getSubId());
589
590        mPhone.getDefaultPhone().registerForDataEnabledChanged(
591                this, EVENT_DATA_ENABLED_CHANGED, null);
592
593        mImsServiceRetryCount = 0;
594        // Send a message to connect to the Ims Service and open a connection through
595        // getImsService().
596        sendEmptyMessage(EVENT_GET_IMS_SERVICE);
597    }
598
599    private PendingIntent createIncomingCallPendingIntent() {
600        Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL);
601        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
602        return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
603                PendingIntent.FLAG_UPDATE_CURRENT);
604    }
605
606    private void getImsService() throws ImsException {
607        if (DBG) log("getImsService");
608        mImsManager = ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId());
609        // Adding to set, will be safe adding multiple times.
610        mImsManager.addNotifyStatusChangedCallback(mNotifyFeatureRemovedCallback);
611        if (mImsManager.getImsServiceStatus() != ImsFeature.STATE_READY) {
612            // We can not call "open" until the ims service is ready
613            throw new ImsException("getImsServiceStatus()",
614                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
615        }
616        mImsServiceRetryCount = 0;
617        mServiceId = mImsManager.open(ImsServiceClass.MMTEL,
618                createIncomingCallPendingIntent(),
619                mImsConnectionStateListener);
620
621        mImsManager.setImsConfigListener(mImsConfigListener);
622
623        // Get the ECBM interface and set IMSPhone's listener object for notifications
624        getEcbmInterface().setEcbmStateListener(mPhone.getImsEcbmStateListener());
625        if (mPhone.isInEcm()) {
626            // Call exit ECBM which will invoke onECBMExited
627            mPhone.exitEmergencyCallbackMode();
628        }
629        int mPreferredTtyMode = Settings.Secure.getInt(
630            mPhone.getContext().getContentResolver(),
631            Settings.Secure.PREFERRED_TTY_MODE,
632            Phone.TTY_MODE_OFF);
633        mImsManager.setUiTTYMode(mPhone.getContext(), mPreferredTtyMode, null);
634
635        ImsMultiEndpoint multiEndpoint = getMultiEndpointInterface();
636        if (multiEndpoint != null) {
637            multiEndpoint.setExternalCallStateListener(
638                    mPhone.getExternalCallTracker().getExternalCallStateListener());
639        }
640    }
641
642    public void dispose() {
643        if (DBG) log("dispose");
644        mRingingCall.dispose();
645        mBackgroundCall.dispose();
646        mForegroundCall.dispose();
647        mHandoverCall.dispose();
648
649        clearDisconnected();
650        mPhone.getContext().unregisterReceiver(mReceiver);
651        mPhone.getDefaultPhone().unregisterForDataEnabledChanged(this);
652        removeMessages(EVENT_GET_IMS_SERVICE);
653    }
654
655    @Override
656    protected void finalize() {
657        log("ImsPhoneCallTracker finalized");
658    }
659
660    //***** Instance Methods
661
662    //***** Public Methods
663    @Override
664    public void registerForVoiceCallStarted(Handler h, int what, Object obj) {
665        Registrant r = new Registrant(h, what, obj);
666        mVoiceCallStartedRegistrants.add(r);
667    }
668
669    @Override
670    public void unregisterForVoiceCallStarted(Handler h) {
671        mVoiceCallStartedRegistrants.remove(h);
672    }
673
674    @Override
675    public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
676        Registrant r = new Registrant(h, what, obj);
677        mVoiceCallEndedRegistrants.add(r);
678    }
679
680    @Override
681    public void unregisterForVoiceCallEnded(Handler h) {
682        mVoiceCallEndedRegistrants.remove(h);
683    }
684
685    public Connection dial(String dialString, int videoState, Bundle intentExtras) throws
686            CallStateException {
687        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
688        int oirMode = sp.getInt(Phone.CLIR_KEY, CommandsInterface.CLIR_DEFAULT);
689        return dial(dialString, oirMode, videoState, intentExtras);
690    }
691
692    /**
693     * oirMode is one of the CLIR_ constants
694     */
695    synchronized Connection
696    dial(String dialString, int clirMode, int videoState, Bundle intentExtras)
697            throws CallStateException {
698        boolean isPhoneInEcmMode = isPhoneInEcbMode();
699        boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(dialString);
700
701        if (DBG) log("dial clirMode=" + clirMode);
702
703        // note that this triggers call state changed notif
704        clearDisconnected();
705
706        if (mImsManager == null) {
707            throw new CallStateException("service not available");
708        }
709
710        if (!canDial()) {
711            throw new CallStateException("cannot dial in current state");
712        }
713
714        if (isPhoneInEcmMode && isEmergencyNumber) {
715            handleEcmTimer(ImsPhone.CANCEL_ECM_TIMER);
716        }
717
718        // If the call is to an emergency number and the carrier does not support video emergency
719        // calls, dial as an audio-only call.
720        if (isEmergencyNumber && VideoProfile.isVideo(videoState) &&
721                !mAllowEmergencyVideoCalls) {
722            loge("dial: carrier does not support video emergency calls; downgrade to audio-only");
723            videoState = VideoProfile.STATE_AUDIO_ONLY;
724        }
725
726        boolean holdBeforeDial = false;
727
728        // The new call must be assigned to the foreground call.
729        // That call must be idle, so place anything that's
730        // there on hold
731        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
732            if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
733                //we should have failed in !canDial() above before we get here
734                throw new CallStateException("cannot dial in current state");
735            }
736            // foreground call is empty for the newly dialed connection
737            holdBeforeDial = true;
738            // Cache the video state for pending MO call.
739            mPendingCallVideoState = videoState;
740            mPendingIntentExtras = intentExtras;
741            switchWaitingOrHoldingAndActive();
742        }
743
744        ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
745        ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;
746
747        mClirMode = clirMode;
748
749        synchronized (mSyncHold) {
750            if (holdBeforeDial) {
751                fgState = mForegroundCall.getState();
752                bgState = mBackgroundCall.getState();
753
754                //holding foreground call failed
755                if (fgState == ImsPhoneCall.State.ACTIVE) {
756                    throw new CallStateException("cannot dial in current state");
757                }
758
759                //holding foreground call succeeded
760                if (bgState == ImsPhoneCall.State.HOLDING) {
761                    holdBeforeDial = false;
762                }
763            }
764
765            mPendingMO = new ImsPhoneConnection(mPhone,
766                    checkForTestEmergencyNumber(dialString), this, mForegroundCall,
767                    isEmergencyNumber);
768            mPendingMO.setVideoState(videoState);
769        }
770        addConnection(mPendingMO);
771
772        if (!holdBeforeDial) {
773            if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
774                dialInternal(mPendingMO, clirMode, videoState, intentExtras);
775            } else {
776                try {
777                    getEcbmInterface().exitEmergencyCallbackMode();
778                } catch (ImsException e) {
779                    e.printStackTrace();
780                    throw new CallStateException("service not available");
781                }
782                mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
783                pendingCallClirMode = clirMode;
784                mPendingCallVideoState = videoState;
785                pendingCallInEcm = true;
786            }
787        }
788
789        updatePhoneState();
790        mPhone.notifyPreciseCallStateChanged();
791
792        return mPendingMO;
793    }
794
795    /**
796     * Caches frequently used carrier configuration items locally.
797     *
798     * @param subId The sub id.
799     */
800    private void cacheCarrierConfiguration(int subId) {
801        CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
802                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
803        if (carrierConfigManager == null) {
804            loge("cacheCarrierConfiguration: No carrier config service found.");
805            return;
806        }
807
808        PersistableBundle carrierConfig = carrierConfigManager.getConfigForSubId(subId);
809        if (carrierConfig == null) {
810            loge("cacheCarrierConfiguration: Empty carrier config.");
811            return;
812        }
813
814        mAllowEmergencyVideoCalls =
815                carrierConfig.getBoolean(CarrierConfigManager.KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL);
816        mTreatDowngradedVideoCallsAsVideoCalls =
817                carrierConfig.getBoolean(
818                        CarrierConfigManager.KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL);
819        mDropVideoCallWhenAnsweringAudioCall =
820                carrierConfig.getBoolean(
821                        CarrierConfigManager.KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL);
822        mAllowAddCallDuringVideoCall =
823                carrierConfig.getBoolean(
824                        CarrierConfigManager.KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL);
825        mNotifyVtHandoverToWifiFail = carrierConfig.getBoolean(
826                CarrierConfigManager.KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL);
827        mSupportDowngradeVtToAudio = carrierConfig.getBoolean(
828                CarrierConfigManager.KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL);
829        mNotifyHandoverVideoFromWifiToLTE = carrierConfig.getBoolean(
830                CarrierConfigManager.KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL);
831        mIgnoreDataEnabledChangedForVideoCalls = carrierConfig.getBoolean(
832                CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS);
833
834        String[] mappings = carrierConfig
835                .getStringArray(CarrierConfigManager.KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY);
836        if (mappings != null && mappings.length > 0) {
837            for (String mapping : mappings) {
838                String[] values = mapping.split(Pattern.quote("|"));
839                if (values.length != 3) {
840                    continue;
841                }
842
843                try {
844                    Integer fromCode;
845                    if (values[0].equals("*")) {
846                        fromCode = null;
847                    } else {
848                        fromCode = Integer.parseInt(values[0]);
849                    }
850                    String message = values[1];
851                    int toCode = Integer.parseInt(values[2]);
852
853                    addReasonCodeRemapping(fromCode, message, toCode);
854                    log("Loaded ImsReasonInfo mapping : fromCode = " +
855                            fromCode == null ? "any" : fromCode + " ; message = " +
856                            message + " ; toCode = " + toCode);
857                } catch (NumberFormatException nfe) {
858                    loge("Invalid ImsReasonInfo mapping found: " + mapping);
859                }
860            }
861        } else {
862            log("No carrier ImsReasonInfo mappings defined.");
863        }
864    }
865
866    private void handleEcmTimer(int action) {
867        mPhone.handleTimerInEmergencyCallbackMode(action);
868        switch (action) {
869            case ImsPhone.CANCEL_ECM_TIMER:
870                break;
871            case ImsPhone.RESTART_ECM_TIMER:
872                break;
873            default:
874                log("handleEcmTimer, unsupported action " + action);
875        }
876    }
877
878    private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState,
879            Bundle intentExtras) {
880
881        if (conn == null) {
882            return;
883        }
884
885        if (conn.getAddress()== null || conn.getAddress().length() == 0
886                || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
887            // Phone number is invalid
888            conn.setDisconnectCause(DisconnectCause.INVALID_NUMBER);
889            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
890            return;
891        }
892
893        // Always unmute when initiating a new call
894        setMute(false);
895        int serviceType = PhoneNumberUtils.isEmergencyNumber(conn.getAddress()) ?
896                ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL;
897        int callType = ImsCallProfile.getCallTypeFromVideoState(videoState);
898        //TODO(vt): Is this sufficient?  At what point do we know the video state of the call?
899        conn.setVideoState(videoState);
900
901        try {
902            String[] callees = new String[] { conn.getAddress() };
903            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
904                    serviceType, callType);
905            profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
906
907            // Translate call subject intent-extra from Telecom-specific extra key to the
908            // ImsCallProfile key.
909            if (intentExtras != null) {
910                if (intentExtras.containsKey(android.telecom.TelecomManager.EXTRA_CALL_SUBJECT)) {
911                    intentExtras.putString(ImsCallProfile.EXTRA_DISPLAY_TEXT,
912                            cleanseInstantLetteringMessage(intentExtras.getString(
913                                    android.telecom.TelecomManager.EXTRA_CALL_SUBJECT))
914                    );
915                }
916
917                if (intentExtras.containsKey(ImsCallProfile.EXTRA_IS_CALL_PULL)) {
918                    profile.mCallExtras.putBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL,
919                            intentExtras.getBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL));
920                    int dialogId = intentExtras.getInt(
921                            ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID);
922                    conn.setIsPulledCall(true);
923                    conn.setPulledDialogId(dialogId);
924                }
925
926                // Pack the OEM-specific call extras.
927                profile.mCallExtras.putBundle(ImsCallProfile.EXTRA_OEM_EXTRAS, intentExtras);
928
929                // NOTE: Extras to be sent over the network are packed into the
930                // intentExtras individually, with uniquely defined keys.
931                // These key-value pairs are processed by IMS Service before
932                // being sent to the lower layers/to the network.
933            }
934
935            ImsCall imsCall = mImsManager.makeCall(mServiceId, profile,
936                    callees, mImsCallListener);
937            conn.setImsCall(imsCall);
938
939            mMetrics.writeOnImsCallStart(mPhone.getPhoneId(),
940                    imsCall.getSession());
941
942            setVideoCallProvider(conn, imsCall);
943            conn.setAllowAddCallDuringVideoCall(mAllowAddCallDuringVideoCall);
944        } catch (ImsException e) {
945            loge("dialInternal : " + e);
946            conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
947            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
948        } catch (RemoteException e) {
949        }
950    }
951
952    /**
953     * Accepts a call with the specified video state.  The video state is the video state that the
954     * user has agreed upon in the InCall UI.
955     *
956     * @param videoState The video State
957     * @throws CallStateException
958     */
959    public void acceptCall (int videoState) throws CallStateException {
960        if (DBG) log("acceptCall");
961
962        if (mForegroundCall.getState().isAlive()
963                && mBackgroundCall.getState().isAlive()) {
964            throw new CallStateException("cannot accept call");
965        }
966
967        if ((mRingingCall.getState() == ImsPhoneCall.State.WAITING)
968                && mForegroundCall.getState().isAlive()) {
969            setMute(false);
970
971            boolean answeringWillDisconnect = false;
972            ImsCall activeCall = mForegroundCall.getImsCall();
973            ImsCall ringingCall = mRingingCall.getImsCall();
974            if (mForegroundCall.hasConnections() && mRingingCall.hasConnections()) {
975                answeringWillDisconnect =
976                        shouldDisconnectActiveCallOnAnswer(activeCall, ringingCall);
977            }
978
979            // Cache video state for pending MT call.
980            mPendingCallVideoState = videoState;
981
982            if (answeringWillDisconnect) {
983                // We need to disconnect the foreground call before answering the background call.
984                mForegroundCall.hangup();
985                try {
986                    ringingCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState));
987                } catch (ImsException e) {
988                    throw new CallStateException("cannot accept call");
989                }
990            } else {
991                switchWaitingOrHoldingAndActive();
992            }
993        } else if (mRingingCall.getState().isRinging()) {
994            if (DBG) log("acceptCall: incoming...");
995            // Always unmute when answering a new call
996            setMute(false);
997            try {
998                ImsCall imsCall = mRingingCall.getImsCall();
999                if (imsCall != null) {
1000                    imsCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState));
1001                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
1002                            ImsCommand.IMS_CMD_ACCEPT);
1003                } else {
1004                    throw new CallStateException("no valid ims call");
1005                }
1006            } catch (ImsException e) {
1007                throw new CallStateException("cannot accept call");
1008            }
1009        } else {
1010            throw new CallStateException("phone not ringing");
1011        }
1012    }
1013
1014    public void rejectCall () throws CallStateException {
1015        if (DBG) log("rejectCall");
1016
1017        if (mRingingCall.getState().isRinging()) {
1018            hangup(mRingingCall);
1019        } else {
1020            throw new CallStateException("phone not ringing");
1021        }
1022    }
1023
1024
1025    private void switchAfterConferenceSuccess() {
1026        if (DBG) log("switchAfterConferenceSuccess fg =" + mForegroundCall.getState() +
1027                ", bg = " + mBackgroundCall.getState());
1028
1029        if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
1030            log("switchAfterConferenceSuccess");
1031            mForegroundCall.switchWith(mBackgroundCall);
1032        }
1033    }
1034
1035    public void switchWaitingOrHoldingAndActive() throws CallStateException {
1036        if (DBG) log("switchWaitingOrHoldingAndActive");
1037
1038        if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) {
1039            throw new CallStateException("cannot be in the incoming state");
1040        }
1041
1042        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
1043            ImsCall imsCall = mForegroundCall.getImsCall();
1044            if (imsCall == null) {
1045                throw new CallStateException("no ims call");
1046            }
1047
1048            // Swap the ImsCalls pointed to by the foreground and background ImsPhoneCalls.
1049            // If hold or resume later fails, we will swap them back.
1050            boolean switchingWithWaitingCall = mBackgroundCall.getImsCall() == null &&
1051                    mRingingCall != null &&
1052                    mRingingCall.getState() == ImsPhoneCall.State.WAITING;
1053
1054            mSwitchingFgAndBgCalls = true;
1055            if (switchingWithWaitingCall) {
1056                mCallExpectedToResume = mRingingCall.getImsCall();
1057            } else {
1058                mCallExpectedToResume = mBackgroundCall.getImsCall();
1059            }
1060            mForegroundCall.switchWith(mBackgroundCall);
1061
1062            // Hold the foreground call; once the foreground call is held, the background call will
1063            // be resumed.
1064            try {
1065                imsCall.hold();
1066                mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
1067                        ImsCommand.IMS_CMD_HOLD);
1068
1069                // If there is no background call to resume, then don't expect there to be a switch.
1070                if (mCallExpectedToResume == null) {
1071                    log("mCallExpectedToResume is null");
1072                    mSwitchingFgAndBgCalls = false;
1073                }
1074            } catch (ImsException e) {
1075                mForegroundCall.switchWith(mBackgroundCall);
1076                throw new CallStateException(e.getMessage());
1077            }
1078        } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
1079            resumeWaitingOrHolding();
1080        }
1081    }
1082
1083    public void
1084    conference() {
1085        if (DBG) log("conference");
1086
1087        ImsCall fgImsCall = mForegroundCall.getImsCall();
1088        if (fgImsCall == null) {
1089            log("conference no foreground ims call");
1090            return;
1091        }
1092
1093        ImsCall bgImsCall = mBackgroundCall.getImsCall();
1094        if (bgImsCall == null) {
1095            log("conference no background ims call");
1096            return;
1097        }
1098
1099        // Keep track of the connect time of the earliest call so that it can be set on the
1100        // {@code ImsConference} when it is created.
1101        long foregroundConnectTime = mForegroundCall.getEarliestConnectTime();
1102        long backgroundConnectTime = mBackgroundCall.getEarliestConnectTime();
1103        long conferenceConnectTime;
1104        if (foregroundConnectTime > 0 && backgroundConnectTime > 0) {
1105            conferenceConnectTime = Math.min(mForegroundCall.getEarliestConnectTime(),
1106                    mBackgroundCall.getEarliestConnectTime());
1107            log("conference - using connect time = " + conferenceConnectTime);
1108        } else if (foregroundConnectTime > 0) {
1109            log("conference - bg call connect time is 0; using fg = " + foregroundConnectTime);
1110            conferenceConnectTime = foregroundConnectTime;
1111        } else {
1112            log("conference - fg call connect time is 0; using bg = " + backgroundConnectTime);
1113            conferenceConnectTime = backgroundConnectTime;
1114        }
1115
1116        ImsPhoneConnection foregroundConnection = mForegroundCall.getFirstConnection();
1117        if (foregroundConnection != null) {
1118            foregroundConnection.setConferenceConnectTime(conferenceConnectTime);
1119        }
1120
1121        try {
1122            fgImsCall.merge(bgImsCall);
1123        } catch (ImsException e) {
1124            log("conference " + e.getMessage());
1125        }
1126    }
1127
1128    public void
1129    explicitCallTransfer() {
1130        //TODO : implement
1131    }
1132
1133    public void
1134    clearDisconnected() {
1135        if (DBG) log("clearDisconnected");
1136
1137        internalClearDisconnected();
1138
1139        updatePhoneState();
1140        mPhone.notifyPreciseCallStateChanged();
1141    }
1142
1143    public boolean
1144    canConference() {
1145        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
1146            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING
1147            && !mBackgroundCall.isFull()
1148            && !mForegroundCall.isFull();
1149    }
1150
1151    public boolean
1152    canDial() {
1153        boolean ret;
1154        int serviceState = mPhone.getServiceState().getState();
1155        String disableCall = SystemProperties.get(
1156                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
1157
1158        ret = (serviceState != ServiceState.STATE_POWER_OFF)
1159            && mPendingMO == null
1160            && !mRingingCall.isRinging()
1161            && !disableCall.equals("true")
1162            && (!mForegroundCall.getState().isAlive()
1163                    || !mBackgroundCall.getState().isAlive());
1164
1165        return ret;
1166    }
1167
1168    public boolean
1169    canTransfer() {
1170        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
1171            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING;
1172    }
1173
1174    //***** Private Instance Methods
1175
1176    private void
1177    internalClearDisconnected() {
1178        mRingingCall.clearDisconnected();
1179        mForegroundCall.clearDisconnected();
1180        mBackgroundCall.clearDisconnected();
1181        mHandoverCall.clearDisconnected();
1182    }
1183
1184    private void
1185    updatePhoneState() {
1186        PhoneConstants.State oldState = mState;
1187
1188        boolean isPendingMOIdle = mPendingMO == null || !mPendingMO.getState().isAlive();
1189
1190        if (mRingingCall.isRinging()) {
1191            mState = PhoneConstants.State.RINGING;
1192        } else if (!isPendingMOIdle || !mForegroundCall.isIdle() || !mBackgroundCall.isIdle()) {
1193            // There is a non-idle call, so we're off the hook.
1194            mState = PhoneConstants.State.OFFHOOK;
1195        } else {
1196            mState = PhoneConstants.State.IDLE;
1197        }
1198
1199        if (mState == PhoneConstants.State.IDLE && oldState != mState) {
1200            mVoiceCallEndedRegistrants.notifyRegistrants(
1201                    new AsyncResult(null, null, null));
1202        } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
1203            mVoiceCallStartedRegistrants.notifyRegistrants (
1204                    new AsyncResult(null, null, null));
1205        }
1206
1207        if (DBG) {
1208            log("updatePhoneState pendingMo = " + (mPendingMO == null ? "null"
1209                    : mPendingMO.getState()) + ", fg= " + mForegroundCall.getState() + "("
1210                    + mForegroundCall.getConnections().size() + "), bg= " + mBackgroundCall
1211                    .getState() + "(" + mBackgroundCall.getConnections().size() + ")");
1212            log("updatePhoneState oldState=" + oldState + ", newState=" + mState);
1213        }
1214
1215        if (mState != oldState) {
1216            mPhone.notifyPhoneStateChanged();
1217            mMetrics.writePhoneState(mPhone.getPhoneId(), mState);
1218            notifyPhoneStateChanged(oldState, mState);
1219        }
1220    }
1221
1222    private void
1223    handleRadioNotAvailable() {
1224        // handlePollCalls will clear out its
1225        // call list when it gets the CommandException
1226        // error result from this
1227        pollCallsWhenSafe();
1228    }
1229
1230    private void
1231    dumpState() {
1232        List l;
1233
1234        log("Phone State:" + mState);
1235
1236        log("Ringing call: " + mRingingCall.toString());
1237
1238        l = mRingingCall.getConnections();
1239        for (int i = 0, s = l.size(); i < s; i++) {
1240            log(l.get(i).toString());
1241        }
1242
1243        log("Foreground call: " + mForegroundCall.toString());
1244
1245        l = mForegroundCall.getConnections();
1246        for (int i = 0, s = l.size(); i < s; i++) {
1247            log(l.get(i).toString());
1248        }
1249
1250        log("Background call: " + mBackgroundCall.toString());
1251
1252        l = mBackgroundCall.getConnections();
1253        for (int i = 0, s = l.size(); i < s; i++) {
1254            log(l.get(i).toString());
1255        }
1256
1257    }
1258
1259    //***** Called from ImsPhone
1260
1261    public void setUiTTYMode(int uiTtyMode, Message onComplete) {
1262        if (mImsManager == null) {
1263            mPhone.sendErrorResponse(onComplete, getImsManagerIsNullException());
1264            return;
1265        }
1266
1267        try {
1268            mImsManager.setUiTTYMode(mPhone.getContext(), uiTtyMode, onComplete);
1269        } catch (ImsException e) {
1270            loge("setTTYMode : " + e);
1271            mPhone.sendErrorResponse(onComplete, e);
1272        }
1273    }
1274
1275    public void setMute(boolean mute) {
1276        mDesiredMute = mute;
1277        mForegroundCall.setMute(mute);
1278    }
1279
1280    public boolean getMute() {
1281        return mDesiredMute;
1282    }
1283
1284    public void sendDtmf(char c, Message result) {
1285        if (DBG) log("sendDtmf");
1286
1287        ImsCall imscall = mForegroundCall.getImsCall();
1288        if (imscall != null) {
1289            imscall.sendDtmf(c, result);
1290        }
1291    }
1292
1293    public void
1294    startDtmf(char c) {
1295        if (DBG) log("startDtmf");
1296
1297        ImsCall imscall = mForegroundCall.getImsCall();
1298        if (imscall != null) {
1299            imscall.startDtmf(c);
1300        } else {
1301            loge("startDtmf : no foreground call");
1302        }
1303    }
1304
1305    public void
1306    stopDtmf() {
1307        if (DBG) log("stopDtmf");
1308
1309        ImsCall imscall = mForegroundCall.getImsCall();
1310        if (imscall != null) {
1311            imscall.stopDtmf();
1312        } else {
1313            loge("stopDtmf : no foreground call");
1314        }
1315    }
1316
1317    //***** Called from ImsPhoneConnection
1318
1319    public void hangup (ImsPhoneConnection conn) throws CallStateException {
1320        if (DBG) log("hangup connection");
1321
1322        if (conn.getOwner() != this) {
1323            throw new CallStateException ("ImsPhoneConnection " + conn
1324                    + "does not belong to ImsPhoneCallTracker " + this);
1325        }
1326
1327        hangup(conn.getCall());
1328    }
1329
1330    //***** Called from ImsPhoneCall
1331
1332    public void hangup (ImsPhoneCall call) throws CallStateException {
1333        if (DBG) log("hangup call");
1334
1335        if (call.getConnections().size() == 0) {
1336            throw new CallStateException("no connections");
1337        }
1338
1339        ImsCall imsCall = call.getImsCall();
1340        // Get first connection that is associated with imsCall.
1341        ImsPhoneConnection imsPhoneConnection = call.getFirstConnection();
1342        boolean rejectCall = false;
1343
1344        if (call == mRingingCall) {
1345            if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming");
1346            rejectCall = true;
1347        } else if (call == mForegroundCall) {
1348            if (call.isDialingOrAlerting()) {
1349                if (Phone.DEBUG_PHONE) {
1350                    log("(foregnd) hangup dialing or alerting...");
1351                }
1352            } else {
1353                if (Phone.DEBUG_PHONE) {
1354                    log("(foregnd) hangup foreground");
1355                }
1356                //held call will be resumed by onCallTerminated
1357            }
1358        } else if (call == mBackgroundCall) {
1359            if (Phone.DEBUG_PHONE) {
1360                log("(backgnd) hangup waiting or background");
1361            }
1362        } else {
1363            throw new CallStateException ("ImsPhoneCall " + call +
1364                    "does not belong to ImsPhoneCallTracker " + this);
1365        }
1366
1367        call.onHangupLocal();
1368        // Schedule a cleaning event for the ImsPhoneCall. If the modem has not responded in
1369        // TIMEOUT_CLEAR_DISCONNECTING_CONN milliseconds, manually disconnect the connection.
1370        if (!hasMessages(EVENT_CLEAR_DISCONNECTING_CONN, imsPhoneConnection)) {
1371            sendMessageDelayed(obtainMessage(EVENT_CLEAR_DISCONNECTING_CONN, imsPhoneConnection),
1372                    TIMEOUT_CLEAR_DISCONNECTING_CONN);
1373        }
1374
1375        try {
1376            if (imsCall != null) {
1377                if (rejectCall) {
1378                    imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
1379                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
1380                            ImsCommand.IMS_CMD_REJECT);
1381                } else {
1382                    imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
1383                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
1384                            ImsCommand.IMS_CMD_TERMINATE);
1385                }
1386            } else if (mPendingMO != null && call == mForegroundCall) {
1387                // is holding a foreground call
1388                mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED);
1389                mPendingMO.onDisconnect();
1390                removeConnection(mPendingMO);
1391                mPendingMO = null;
1392                updatePhoneState();
1393                removeMessages(EVENT_DIAL_PENDINGMO);
1394            }
1395        } catch (ImsException e) {
1396            throw new CallStateException(e.getMessage());
1397        }
1398
1399        mPhone.notifyPreciseCallStateChanged();
1400    }
1401
1402    void callEndCleanupHandOverCallIfAny() {
1403        if (mHandoverCall.mConnections.size() > 0) {
1404            if (DBG) log("callEndCleanupHandOverCallIfAny, mHandoverCall.mConnections="
1405                    + mHandoverCall.mConnections);
1406            mHandoverCall.mConnections.clear();
1407            mConnections.clear();
1408            mState = PhoneConstants.State.IDLE;
1409        }
1410    }
1411
1412    /* package */
1413    void resumeWaitingOrHolding() throws CallStateException {
1414        if (DBG) log("resumeWaitingOrHolding");
1415
1416        try {
1417            if (mForegroundCall.getState().isAlive()) {
1418                //resume foreground call after holding background call
1419                //they were switched before holding
1420                ImsCall imsCall = mForegroundCall.getImsCall();
1421                if (imsCall != null) {
1422                    imsCall.resume();
1423                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
1424                            ImsCommand.IMS_CMD_RESUME);
1425                }
1426            } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
1427                //accept waiting call after holding background call
1428                ImsCall imsCall = mRingingCall.getImsCall();
1429                if (imsCall != null) {
1430                    imsCall.accept(
1431                        ImsCallProfile.getCallTypeFromVideoState(mPendingCallVideoState));
1432                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
1433                            ImsCommand.IMS_CMD_ACCEPT);
1434                }
1435            } else {
1436                //Just resume background call.
1437                //To distinguish resuming call with swapping calls
1438                //we do not switch calls.here
1439                //ImsPhoneConnection.update will chnage the parent when completed
1440                ImsCall imsCall = mBackgroundCall.getImsCall();
1441                if (imsCall != null) {
1442                    imsCall.resume();
1443                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
1444                            ImsCommand.IMS_CMD_RESUME);
1445                }
1446            }
1447        } catch (ImsException e) {
1448            throw new CallStateException(e.getMessage());
1449        }
1450    }
1451
1452    public void sendUSSD (String ussdString, Message response) {
1453        if (DBG) log("sendUSSD");
1454
1455        try {
1456            if (mUssdSession != null) {
1457                mUssdSession.sendUssd(ussdString);
1458                AsyncResult.forMessage(response, null, null);
1459                response.sendToTarget();
1460                return;
1461            }
1462
1463            if (mImsManager == null) {
1464                mPhone.sendErrorResponse(response, getImsManagerIsNullException());
1465                return;
1466            }
1467
1468            String[] callees = new String[] { ussdString };
1469            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
1470                    ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE);
1471            profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING,
1472                    ImsCallProfile.DIALSTRING_USSD);
1473
1474            mUssdSession = mImsManager.makeCall(mServiceId, profile,
1475                    callees, mImsUssdListener);
1476        } catch (ImsException e) {
1477            loge("sendUSSD : " + e);
1478            mPhone.sendErrorResponse(response, e);
1479        }
1480    }
1481
1482    public void cancelUSSD() {
1483        if (mUssdSession == null) return;
1484
1485        try {
1486            mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
1487        } catch (ImsException e) {
1488        }
1489
1490    }
1491
1492    private synchronized ImsPhoneConnection findConnection(final ImsCall imsCall) {
1493        for (ImsPhoneConnection conn : mConnections) {
1494            if (conn.getImsCall() == imsCall) {
1495                return conn;
1496            }
1497        }
1498        return null;
1499    }
1500
1501    private synchronized void removeConnection(ImsPhoneConnection conn) {
1502        mConnections.remove(conn);
1503        // If not emergency call is remaining, notify emergency call registrants
1504        if (mIsInEmergencyCall) {
1505            boolean isEmergencyCallInList = false;
1506            // if no emergency calls pending, set this to false
1507            for (ImsPhoneConnection imsPhoneConnection : mConnections) {
1508                if (imsPhoneConnection != null && imsPhoneConnection.isEmergency() == true) {
1509                    isEmergencyCallInList = true;
1510                    break;
1511                }
1512            }
1513
1514            if (!isEmergencyCallInList) {
1515                mIsInEmergencyCall = false;
1516                mPhone.sendEmergencyCallStateChange(false);
1517            }
1518        }
1519    }
1520
1521    private synchronized void addConnection(ImsPhoneConnection conn) {
1522        mConnections.add(conn);
1523        if (conn.isEmergency()) {
1524            mIsInEmergencyCall = true;
1525            mPhone.sendEmergencyCallStateChange(true);
1526        }
1527    }
1528
1529    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
1530        if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause);
1531        // This method is called on onCallUpdate() where there is not necessarily a call state
1532        // change. In these situations, we'll ignore the state related updates and only process
1533        // the change in media capabilities (as expected).  The default is to not ignore state
1534        // changes so we do not change existing behavior.
1535        processCallStateChange(imsCall, state, cause, false /* do not ignore state update */);
1536    }
1537
1538    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause,
1539            boolean ignoreState) {
1540        if (DBG) {
1541            log("processCallStateChange state=" + state + " cause=" + cause
1542                    + " ignoreState=" + ignoreState);
1543        }
1544
1545        if (imsCall == null) return;
1546
1547        boolean changed = false;
1548        ImsPhoneConnection conn = findConnection(imsCall);
1549
1550        if (conn == null) {
1551            // TODO : what should be done?
1552            return;
1553        }
1554
1555        // processCallStateChange is triggered for onCallUpdated as well.
1556        // onCallUpdated should not modify the state of the call
1557        // It should modify only other capabilities of call through updateMediaCapabilities
1558        // State updates will be triggered through individual callbacks
1559        // i.e. onCallHeld, onCallResume, etc and conn.update will be responsible for the update
1560        conn.updateMediaCapabilities(imsCall);
1561        if (ignoreState) {
1562            conn.updateAddressDisplay(imsCall);
1563            conn.updateExtras(imsCall);
1564
1565            maybeSetVideoCallProvider(conn, imsCall);
1566            return;
1567        }
1568
1569        changed = conn.update(imsCall, state);
1570        if (state == ImsPhoneCall.State.DISCONNECTED) {
1571            changed = conn.onDisconnect(cause) || changed;
1572            //detach the disconnected connections
1573            conn.getCall().detach(conn);
1574            removeConnection(conn);
1575        }
1576
1577        if (changed) {
1578            if (conn.getCall() == mHandoverCall) return;
1579            updatePhoneState();
1580            mPhone.notifyPreciseCallStateChanged();
1581        }
1582    }
1583
1584    private void maybeSetVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall) {
1585        android.telecom.Connection.VideoProvider connVideoProvider = conn.getVideoProvider();
1586        if (connVideoProvider != null || imsCall.getCallSession().getVideoCallProvider() == null) {
1587            return;
1588        }
1589
1590        try {
1591            setVideoCallProvider(conn, imsCall);
1592        } catch (RemoteException e) {
1593            loge("maybeSetVideoCallProvider: exception " + e);
1594        }
1595    }
1596
1597    /**
1598     * Adds a reason code remapping, for test purposes.
1599     *
1600     * @param fromCode The from code, or {@code null} if all.
1601     * @param message The message to map.
1602     * @param toCode The code to remap to.
1603     */
1604    @VisibleForTesting
1605    public void addReasonCodeRemapping(Integer fromCode, String message, Integer toCode) {
1606        mImsReasonCodeMap.put(new Pair<>(fromCode, message), toCode);
1607    }
1608
1609    /**
1610     * Returns the {@link ImsReasonInfo#getCode()}, potentially remapping to a new value based on
1611     * the {@link ImsReasonInfo#getCode()} and {@link ImsReasonInfo#getExtraMessage()}.
1612     *
1613     * See {@link #mImsReasonCodeMap}.
1614     *
1615     * @param reasonInfo The {@link ImsReasonInfo}.
1616     * @return The remapped code.
1617     */
1618    @VisibleForTesting
1619    public int maybeRemapReasonCode(ImsReasonInfo reasonInfo) {
1620        int code = reasonInfo.getCode();
1621
1622        Pair<Integer, String> toCheck = new Pair<>(code, reasonInfo.getExtraMessage());
1623        Pair<Integer, String> wildcardToCheck = new Pair<>(null, reasonInfo.getExtraMessage());
1624        if (mImsReasonCodeMap.containsKey(toCheck)) {
1625            int toCode = mImsReasonCodeMap.get(toCheck);
1626
1627            log("maybeRemapReasonCode : fromCode = " + reasonInfo.getCode() + " ; message = "
1628                    + reasonInfo.getExtraMessage() + " ; toCode = " + toCode);
1629            return toCode;
1630        } else if (mImsReasonCodeMap.containsKey(wildcardToCheck)) {
1631            // Handle the case where a wildcard is specified for the fromCode; in this case we will
1632            // match without caring about the fromCode.
1633            int toCode = mImsReasonCodeMap.get(wildcardToCheck);
1634
1635            log("maybeRemapReasonCode : fromCode(wildcard) = " + reasonInfo.getCode() +
1636                    " ; message = " + reasonInfo.getExtraMessage() + " ; toCode = " + toCode);
1637            return toCode;
1638        }
1639        return code;
1640    }
1641
1642    private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
1643        int cause = DisconnectCause.ERROR_UNSPECIFIED;
1644
1645        //int type = reasonInfo.getReasonType();
1646        int code = maybeRemapReasonCode(reasonInfo);
1647        switch (code) {
1648            case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
1649            case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
1650                return DisconnectCause.NUMBER_UNREACHABLE;
1651
1652            case ImsReasonInfo.CODE_SIP_BUSY:
1653                return DisconnectCause.BUSY;
1654
1655            case ImsReasonInfo.CODE_USER_TERMINATED:
1656                return DisconnectCause.LOCAL;
1657
1658            case ImsReasonInfo.CODE_LOCAL_CALL_DECLINE:
1659            case ImsReasonInfo.CODE_REMOTE_CALL_DECLINE:
1660                // If the call has been declined locally (on this device), or on remotely (on
1661                // another device using multiendpoint functionality), mark it as rejected.
1662                return DisconnectCause.INCOMING_REJECTED;
1663
1664            case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
1665                return DisconnectCause.NORMAL;
1666
1667            case ImsReasonInfo.CODE_SIP_FORBIDDEN:
1668                return DisconnectCause.SERVER_ERROR;
1669
1670            case ImsReasonInfo.CODE_SIP_REDIRECTED:
1671            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
1672            case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
1673            case ImsReasonInfo.CODE_SIP_USER_REJECTED:
1674            case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
1675                return DisconnectCause.SERVER_ERROR;
1676
1677            case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
1678            case ImsReasonInfo.CODE_SIP_NOT_FOUND:
1679            case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
1680                return DisconnectCause.SERVER_UNREACHABLE;
1681
1682            case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
1683            case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
1684            case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
1685            case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE:
1686            case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED:
1687            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE:
1688            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE:
1689            case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING:
1690                return DisconnectCause.OUT_OF_SERVICE;
1691
1692            case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT:
1693            case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING:
1694            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER:
1695            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
1696                return DisconnectCause.TIMED_OUT;
1697
1698            case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
1699            case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
1700                return DisconnectCause.POWER_OFF;
1701
1702            case ImsReasonInfo.CODE_FDN_BLOCKED:
1703                return DisconnectCause.FDN_BLOCKED;
1704
1705            case ImsReasonInfo.CODE_ANSWERED_ELSEWHERE:
1706                return DisconnectCause.ANSWERED_ELSEWHERE;
1707
1708            case ImsReasonInfo.CODE_CALL_END_CAUSE_CALL_PULL:
1709                return DisconnectCause.CALL_PULLED;
1710
1711            case ImsReasonInfo.CODE_MAXIMUM_NUMBER_OF_CALLS_REACHED:
1712                return DisconnectCause.MAXIMUM_NUMBER_OF_CALLS_REACHED;
1713
1714            case ImsReasonInfo.CODE_DATA_DISABLED:
1715                return DisconnectCause.DATA_DISABLED;
1716
1717            case ImsReasonInfo.CODE_DATA_LIMIT_REACHED:
1718                return DisconnectCause.DATA_LIMIT_REACHED;
1719
1720            case ImsReasonInfo.CODE_WIFI_LOST:
1721                return DisconnectCause.WIFI_LOST;
1722            default:
1723        }
1724
1725        return cause;
1726    }
1727
1728    private int getPreciseDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
1729        return PRECISE_CAUSE_MAP.get(maybeRemapReasonCode(reasonInfo),
1730                PreciseDisconnectCause.ERROR_UNSPECIFIED);
1731    }
1732
1733    /**
1734     * @return true if the phone is in Emergency Callback mode, otherwise false
1735     */
1736    private boolean isPhoneInEcbMode() {
1737        return SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false);
1738    }
1739
1740    /**
1741     * Before dialing pending MO request, check for the Emergency Callback mode.
1742     * If device is in Emergency callback mode, then exit the mode before dialing pending MO.
1743     */
1744    private void dialPendingMO() {
1745        boolean isPhoneInEcmMode = isPhoneInEcbMode();
1746        boolean isEmergencyNumber = mPendingMO.isEmergency();
1747        if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
1748            sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1749        } else {
1750            sendEmptyMessage(EVENT_EXIT_ECBM_BEFORE_PENDINGMO);
1751        }
1752    }
1753
1754    /**
1755     * Listen to the IMS call state change
1756     */
1757    private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
1758        @Override
1759        public void onCallProgressing(ImsCall imsCall) {
1760            if (DBG) log("onCallProgressing");
1761
1762            mPendingMO = null;
1763            processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING,
1764                    DisconnectCause.NOT_DISCONNECTED);
1765            mMetrics.writeOnImsCallProgressing(mPhone.getPhoneId(), imsCall.getCallSession());
1766        }
1767
1768        @Override
1769        public void onCallStarted(ImsCall imsCall) {
1770            if (DBG) log("onCallStarted");
1771
1772            if (mSwitchingFgAndBgCalls) {
1773                // If we put a call on hold to answer an incoming call, we should reset the
1774                // variables that keep track of the switch here.
1775                if (mCallExpectedToResume != null && mCallExpectedToResume == imsCall) {
1776                    if (DBG) log("onCallStarted: starting a call as a result of a switch.");
1777                    mSwitchingFgAndBgCalls = false;
1778                    mCallExpectedToResume = null;
1779                }
1780            }
1781
1782            mPendingMO = null;
1783            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1784                    DisconnectCause.NOT_DISCONNECTED);
1785
1786            if (mNotifyVtHandoverToWifiFail &&
1787                    !imsCall.isWifiCall() && imsCall.isVideoCall() && isWifiConnected()) {
1788                // Schedule check to see if handover succeeded.
1789                sendMessageDelayed(obtainMessage(EVENT_CHECK_FOR_WIFI_HANDOVER, imsCall),
1790                        HANDOVER_TO_WIFI_TIMEOUT_MS);
1791            }
1792
1793            mMetrics.writeOnImsCallStarted(mPhone.getPhoneId(), imsCall.getCallSession());
1794        }
1795
1796        @Override
1797        public void onCallUpdated(ImsCall imsCall) {
1798            if (DBG) log("onCallUpdated");
1799            if (imsCall == null) {
1800                return;
1801            }
1802            ImsPhoneConnection conn = findConnection(imsCall);
1803            if (conn != null) {
1804                processCallStateChange(imsCall, conn.getCall().mState,
1805                        DisconnectCause.NOT_DISCONNECTED, true /*ignore state update*/);
1806                mMetrics.writeImsCallState(mPhone.getPhoneId(),
1807                        imsCall.getCallSession(), conn.getCall().mState);
1808            }
1809        }
1810
1811        /**
1812         * onCallStartFailed will be invoked when:
1813         * case 1) Dialing fails
1814         * case 2) Ringing call is disconnected by local or remote user
1815         */
1816        @Override
1817        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1818            if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
1819
1820            if (mSwitchingFgAndBgCalls) {
1821                // If we put a call on hold to answer an incoming call, we should reset the
1822                // variables that keep track of the switch here.
1823                if (mCallExpectedToResume != null && mCallExpectedToResume == imsCall) {
1824                    if (DBG) log("onCallStarted: starting a call as a result of a switch.");
1825                    mSwitchingFgAndBgCalls = false;
1826                    mCallExpectedToResume = null;
1827                }
1828            }
1829
1830            if (mPendingMO != null) {
1831                // To initiate dialing circuit-switched call
1832                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
1833                        && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE
1834                        && mRingingCall.getState() == ImsPhoneCall.State.IDLE) {
1835                    mForegroundCall.detach(mPendingMO);
1836                    removeConnection(mPendingMO);
1837                    mPendingMO.finalize();
1838                    mPendingMO = null;
1839                    mPhone.initiateSilentRedial();
1840                    return;
1841                } else {
1842                    mPendingMO = null;
1843                    int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1844                    ImsPhoneConnection conn = findConnection(imsCall);
1845
1846                    if(conn != null) {
1847                        conn.setPreciseDisconnectCause(
1848                                getPreciseDisconnectCauseFromReasonInfo(reasonInfo));
1849                    }
1850
1851                    processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1852                }
1853                mMetrics.writeOnImsCallStartFailed(mPhone.getPhoneId(), imsCall.getCallSession(),
1854                        reasonInfo);
1855            }
1856        }
1857
1858        @Override
1859        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1860            if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
1861
1862            int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1863            ImsPhoneConnection conn = findConnection(imsCall);
1864            if (DBG) log("cause = " + cause + " conn = " + conn);
1865
1866            if (conn != null) {
1867                android.telecom.Connection.VideoProvider videoProvider = conn.getVideoProvider();
1868                if (videoProvider instanceof ImsVideoCallProviderWrapper) {
1869                    ImsVideoCallProviderWrapper wrapper = (ImsVideoCallProviderWrapper)
1870                            videoProvider;
1871
1872                    wrapper.removeImsVideoProviderCallback(conn);
1873                }
1874            }
1875            if (mOnHoldToneId == System.identityHashCode(conn)) {
1876                if (conn != null && mOnHoldToneStarted) {
1877                    mPhone.stopOnHoldTone(conn);
1878                }
1879                mOnHoldToneStarted = false;
1880                mOnHoldToneId = -1;
1881            }
1882            if (conn != null) {
1883                if (conn.isPulledCall() && (
1884                        reasonInfo.getCode() == ImsReasonInfo.CODE_CALL_PULL_OUT_OF_SYNC ||
1885                        reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_TEMPRARILY_UNAVAILABLE ||
1886                        reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_FORBIDDEN) &&
1887                        mPhone != null && mPhone.getExternalCallTracker() != null) {
1888
1889                    log("Call pull failed.");
1890                    // Call was being pulled, but the call pull has failed -- inform the associated
1891                    // TelephonyConnection that the pull failed, and provide it with the original
1892                    // external connection which was pulled so that it can be swapped back.
1893                    conn.onCallPullFailed(mPhone.getExternalCallTracker()
1894                            .getConnectionById(conn.getPulledDialogId()));
1895                    // Do not mark as disconnected; the call will just change from being a regular
1896                    // call to being an external call again.
1897                    cause = DisconnectCause.NOT_DISCONNECTED;
1898
1899                } else if (conn.isIncoming() && conn.getConnectTime() == 0
1900                        && cause != DisconnectCause.ANSWERED_ELSEWHERE) {
1901                    // Missed
1902                    if (cause == DisconnectCause.NORMAL) {
1903                        cause = DisconnectCause.INCOMING_MISSED;
1904                    } else {
1905                        cause = DisconnectCause.INCOMING_REJECTED;
1906                    }
1907                    if (DBG) log("Incoming connection of 0 connect time detected - translated " +
1908                            "cause = " + cause);
1909                }
1910            }
1911
1912            if (cause == DisconnectCause.NORMAL && conn != null && conn.getImsCall().isMerged()) {
1913                // Call was terminated while it is merged instead of a remote disconnect.
1914                cause = DisconnectCause.IMS_MERGED_SUCCESSFULLY;
1915            }
1916
1917            mMetrics.writeOnImsCallTerminated(mPhone.getPhoneId(), imsCall.getCallSession(),
1918                    reasonInfo);
1919
1920            if(conn != null) {
1921                conn.setPreciseDisconnectCause(getPreciseDisconnectCauseFromReasonInfo(reasonInfo));
1922            }
1923
1924            processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1925            if (mForegroundCall.getState() != ImsPhoneCall.State.ACTIVE) {
1926                if (mRingingCall.getState().isRinging()) {
1927                    // Drop pending MO. We should address incoming call first
1928                    mPendingMO = null;
1929                } else if (mPendingMO != null) {
1930                    sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1931                }
1932            }
1933
1934            if (mSwitchingFgAndBgCalls) {
1935                if (DBG) {
1936                    log("onCallTerminated: Call terminated in the midst of Switching " +
1937                            "Fg and Bg calls.");
1938                }
1939                // If we are the in midst of swapping FG and BG calls and the call that was
1940                // terminated was the one that we expected to resume, we need to swap the FG and
1941                // BG calls back.
1942                if (imsCall == mCallExpectedToResume) {
1943                    if (DBG) {
1944                        log("onCallTerminated: switching " + mForegroundCall + " with "
1945                                + mBackgroundCall);
1946                    }
1947                    mForegroundCall.switchWith(mBackgroundCall);
1948                }
1949                // This call terminated in the midst of a switch after the other call was held, so
1950                // resume it back to ACTIVE state since the switch failed.
1951                log("onCallTerminated: foreground call in state " + mForegroundCall.getState() +
1952                        " and ringing call in state " + (mRingingCall == null ? "null" :
1953                        mRingingCall.getState().toString()));
1954
1955                if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING ||
1956                        mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
1957                    sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1958                    mSwitchingFgAndBgCalls = false;
1959                    mCallExpectedToResume = null;
1960                }
1961            }
1962        }
1963
1964        @Override
1965        public void onCallHeld(ImsCall imsCall) {
1966            if (DBG) {
1967                if (mForegroundCall.getImsCall() == imsCall) {
1968                    log("onCallHeld (fg) " + imsCall);
1969                } else if (mBackgroundCall.getImsCall() == imsCall) {
1970                    log("onCallHeld (bg) " + imsCall);
1971                }
1972            }
1973
1974            synchronized (mSyncHold) {
1975                ImsPhoneCall.State oldState = mBackgroundCall.getState();
1976                processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING,
1977                        DisconnectCause.NOT_DISCONNECTED);
1978
1979                // Note: If we're performing a switchWaitingOrHoldingAndActive, the call to
1980                // processCallStateChange above may have caused the mBackgroundCall and
1981                // mForegroundCall references below to change meaning.  Watch out for this if you
1982                // are reading through this code.
1983                if (oldState == ImsPhoneCall.State.ACTIVE) {
1984                    // Note: This case comes up when we have just held a call in response to a
1985                    // switchWaitingOrHoldingAndActive.  We now need to resume the background call.
1986                    // The EVENT_RESUME_BACKGROUND causes resumeWaitingOrHolding to be called.
1987                    if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
1988                            || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
1989                            sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1990                    } else {
1991                        //when multiple connections belong to background call,
1992                        //only the first callback reaches here
1993                        //otherwise the oldState is already HOLDING
1994                        if (mPendingMO != null) {
1995                            dialPendingMO();
1996                        }
1997
1998                        // In this case there will be no call resumed, so we can assume that we
1999                        // are done switching fg and bg calls now.
2000                        // This may happen if there is no BG call and we are holding a call so that
2001                        // we can dial another one.
2002                        mSwitchingFgAndBgCalls = false;
2003                    }
2004                } else if (oldState == ImsPhoneCall.State.IDLE && mSwitchingFgAndBgCalls) {
2005                    // The other call terminated in the midst of a switch before this call was held,
2006                    // so resume the foreground call back to ACTIVE state since the switch failed.
2007                    if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) {
2008                        sendEmptyMessage(EVENT_RESUME_BACKGROUND);
2009                        mSwitchingFgAndBgCalls = false;
2010                        mCallExpectedToResume = null;
2011                    }
2012                }
2013            }
2014            mMetrics.writeOnImsCallHeld(mPhone.getPhoneId(), imsCall.getCallSession());
2015        }
2016
2017        @Override
2018        public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
2019            if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode());
2020
2021            synchronized (mSyncHold) {
2022                ImsPhoneCall.State bgState = mBackgroundCall.getState();
2023                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) {
2024                    // disconnected while processing hold
2025                    if (mPendingMO != null) {
2026                        dialPendingMO();
2027                    }
2028                } else if (bgState == ImsPhoneCall.State.ACTIVE) {
2029                    mForegroundCall.switchWith(mBackgroundCall);
2030
2031                    if (mPendingMO != null) {
2032                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
2033                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
2034                    }
2035                }
2036                mPhone.notifySuppServiceFailed(Phone.SuppService.HOLD);
2037            }
2038            mMetrics.writeOnImsCallHoldFailed(mPhone.getPhoneId(), imsCall.getCallSession(),
2039                    reasonInfo);
2040        }
2041
2042        @Override
2043        public void onCallResumed(ImsCall imsCall) {
2044            if (DBG) log("onCallResumed");
2045
2046            // If we are the in midst of swapping FG and BG calls and the call we end up resuming
2047            // is not the one we expected, we likely had a resume failure and we need to swap the
2048            // FG and BG calls back.
2049            if (mSwitchingFgAndBgCalls) {
2050                if (imsCall != mCallExpectedToResume) {
2051                    // If the call which resumed isn't as expected, we need to swap back to the
2052                    // previous configuration; the swap has failed.
2053                    if (DBG) {
2054                        log("onCallResumed : switching " + mForegroundCall + " with "
2055                                + mBackgroundCall);
2056                    }
2057                    mForegroundCall.switchWith(mBackgroundCall);
2058                } else {
2059                    // The call which resumed is the one we expected to resume, so we can clear out
2060                    // the mSwitchingFgAndBgCalls flag.
2061                    if (DBG) {
2062                        log("onCallResumed : expected call resumed.");
2063                    }
2064                }
2065                mSwitchingFgAndBgCalls = false;
2066                mCallExpectedToResume = null;
2067            }
2068            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
2069                    DisconnectCause.NOT_DISCONNECTED);
2070            mMetrics.writeOnImsCallResumed(mPhone.getPhoneId(), imsCall.getCallSession());
2071        }
2072
2073        @Override
2074        public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
2075            if (mSwitchingFgAndBgCalls) {
2076                // If we are in the midst of swapping the FG and BG calls and
2077                // we got a resume fail, we need to swap back the FG and BG calls.
2078                // Since the FG call was held, will also try to resume the same.
2079                if (imsCall == mCallExpectedToResume) {
2080                    if (DBG) {
2081                        log("onCallResumeFailed : switching " + mForegroundCall + " with "
2082                                + mBackgroundCall);
2083                    }
2084                    mForegroundCall.switchWith(mBackgroundCall);
2085                    if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) {
2086                            sendEmptyMessage(EVENT_RESUME_BACKGROUND);
2087                    }
2088                }
2089
2090                //Call swap is done, reset the relevant variables
2091                mCallExpectedToResume = null;
2092                mSwitchingFgAndBgCalls = false;
2093            }
2094            mPhone.notifySuppServiceFailed(Phone.SuppService.RESUME);
2095            mMetrics.writeOnImsCallResumeFailed(mPhone.getPhoneId(), imsCall.getCallSession(),
2096                    reasonInfo);
2097        }
2098
2099        @Override
2100        public void onCallResumeReceived(ImsCall imsCall) {
2101            if (DBG) log("onCallResumeReceived");
2102            ImsPhoneConnection conn = findConnection(imsCall);
2103            if (conn != null) {
2104                if (mOnHoldToneStarted) {
2105                    mPhone.stopOnHoldTone(conn);
2106                    mOnHoldToneStarted = false;
2107                }
2108
2109                conn.onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_UNHELD, null);
2110            }
2111
2112            SuppServiceNotification supp = new SuppServiceNotification();
2113            // Type of notification: 0 = MO; 1 = MT
2114            // Refer SuppServiceNotification class documentation.
2115            supp.notificationType = 1;
2116            supp.code = SuppServiceNotification.MT_CODE_CALL_RETRIEVED;
2117            mPhone.notifySuppSvcNotification(supp);
2118            mMetrics.writeOnImsCallResumeReceived(mPhone.getPhoneId(), imsCall.getCallSession());
2119        }
2120
2121        @Override
2122        public void onCallHoldReceived(ImsCall imsCall) {
2123            if (DBG) log("onCallHoldReceived");
2124
2125            ImsPhoneConnection conn = findConnection(imsCall);
2126            if (conn != null) {
2127                if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall) &&
2128                        conn.getState() == ImsPhoneCall.State.ACTIVE) {
2129                    mPhone.startOnHoldTone(conn);
2130                    mOnHoldToneStarted = true;
2131                    mOnHoldToneId = System.identityHashCode(conn);
2132                }
2133
2134                conn.onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_HELD, null);
2135            }
2136
2137            SuppServiceNotification supp = new SuppServiceNotification();
2138            // Type of notification: 0 = MO; 1 = MT
2139            // Refer SuppServiceNotification class documentation.
2140            supp.notificationType = 1;
2141            supp.code = SuppServiceNotification.MT_CODE_CALL_ON_HOLD;
2142            mPhone.notifySuppSvcNotification(supp);
2143            mMetrics.writeOnImsCallHoldReceived(mPhone.getPhoneId(), imsCall.getCallSession());
2144        }
2145
2146        @Override
2147        public void onCallSuppServiceReceived(ImsCall call,
2148                ImsSuppServiceNotification suppServiceInfo) {
2149            if (DBG) log("onCallSuppServiceReceived: suppServiceInfo=" + suppServiceInfo);
2150
2151            SuppServiceNotification supp = new SuppServiceNotification();
2152            supp.notificationType = suppServiceInfo.notificationType;
2153            supp.code = suppServiceInfo.code;
2154            supp.index = suppServiceInfo.index;
2155            supp.number = suppServiceInfo.number;
2156            supp.history = suppServiceInfo.history;
2157
2158            mPhone.notifySuppSvcNotification(supp);
2159        }
2160
2161        @Override
2162        public void onCallMerged(final ImsCall call, final ImsCall peerCall, boolean swapCalls) {
2163            if (DBG) log("onCallMerged");
2164
2165            ImsPhoneCall foregroundImsPhoneCall = findConnection(call).getCall();
2166            ImsPhoneConnection peerConnection = findConnection(peerCall);
2167            ImsPhoneCall peerImsPhoneCall = peerConnection == null ? null
2168                    : peerConnection.getCall();
2169
2170            if (swapCalls) {
2171                switchAfterConferenceSuccess();
2172            }
2173            foregroundImsPhoneCall.merge(peerImsPhoneCall, ImsPhoneCall.State.ACTIVE);
2174
2175            try {
2176                final ImsPhoneConnection conn = findConnection(call);
2177                log("onCallMerged: ImsPhoneConnection=" + conn);
2178                log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
2179                setVideoCallProvider(conn, call);
2180                log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
2181            } catch (Exception e) {
2182                loge("onCallMerged: exception " + e);
2183            }
2184
2185            // After merge complete, update foreground as Active
2186            // and background call as Held, if background call exists
2187            processCallStateChange(mForegroundCall.getImsCall(), ImsPhoneCall.State.ACTIVE,
2188                    DisconnectCause.NOT_DISCONNECTED);
2189            if (peerConnection != null) {
2190                processCallStateChange(mBackgroundCall.getImsCall(), ImsPhoneCall.State.HOLDING,
2191                    DisconnectCause.NOT_DISCONNECTED);
2192            }
2193
2194            // Check if the merge was requested by an existing conference call. In that
2195            // case, no further action is required.
2196            if (!call.isMergeRequestedByConf()) {
2197                log("onCallMerged :: calling onMultipartyStateChanged()");
2198                onMultipartyStateChanged(call, true);
2199            } else {
2200                log("onCallMerged :: Merge requested by existing conference.");
2201                // Reset the flag.
2202                call.resetIsMergeRequestedByConf(false);
2203            }
2204            logState();
2205        }
2206
2207        @Override
2208        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
2209            if (DBG) log("onCallMergeFailed reasonInfo=" + reasonInfo);
2210
2211            // TODO: the call to notifySuppServiceFailed throws up the "merge failed" dialog
2212            // We should move this into the InCallService so that it is handled appropriately
2213            // based on the user facing UI.
2214            mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
2215
2216            // Start plumbing this even through Telecom so other components can take
2217            // appropriate action.
2218            ImsPhoneConnection conn = findConnection(call);
2219            if (conn != null) {
2220                conn.onConferenceMergeFailed();
2221            }
2222        }
2223
2224        /**
2225         * Called when the state of IMS conference participant(s) has changed.
2226         *
2227         * @param call the call object that carries out the IMS call.
2228         * @param participants the participant(s) and their new state information.
2229         */
2230        @Override
2231        public void onConferenceParticipantsStateChanged(ImsCall call,
2232                List<ConferenceParticipant> participants) {
2233            if (DBG) log("onConferenceParticipantsStateChanged");
2234
2235            ImsPhoneConnection conn = findConnection(call);
2236            if (conn != null) {
2237                conn.updateConferenceParticipants(participants);
2238            }
2239        }
2240
2241        @Override
2242        public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
2243            mPhone.onTtyModeReceived(mode);
2244        }
2245
2246        @Override
2247        public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
2248            ImsReasonInfo reasonInfo) {
2249            if (DBG) {
2250                log("onCallHandover ::  srcAccessTech=" + srcAccessTech + ", targetAccessTech=" +
2251                    targetAccessTech + ", reasonInfo=" + reasonInfo);
2252            }
2253
2254            boolean isHandoverToWifi = srcAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN &&
2255                    targetAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
2256            if (isHandoverToWifi) {
2257                // If we handed over to wifi successfully, don't check for failure in the future.
2258                removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
2259            }
2260
2261            boolean isHandoverFromWifi =
2262                    srcAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN &&
2263                            targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
2264            if (mNotifyHandoverVideoFromWifiToLTE && isHandoverFromWifi && imsCall.isVideoCall()) {
2265                log("onCallHandover :: notifying of WIFI to LTE handover.");
2266                ImsPhoneConnection conn = findConnection(imsCall);
2267                if (conn != null) {
2268                    conn.onConnectionEvent(
2269                            TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE, null);
2270                } else {
2271                    loge("onCallHandover :: failed to notify of handover; connection is null.");
2272                }
2273            }
2274
2275            mMetrics.writeOnImsCallHandoverEvent(mPhone.getPhoneId(),
2276                    TelephonyCallSession.Event.Type.IMS_CALL_HANDOVER, imsCall.getCallSession(),
2277                    srcAccessTech, targetAccessTech, reasonInfo);
2278        }
2279
2280        @Override
2281        public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
2282            ImsReasonInfo reasonInfo) {
2283            if (DBG) {
2284                log("onCallHandoverFailed :: srcAccessTech=" + srcAccessTech +
2285                    ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + reasonInfo);
2286            }
2287            mMetrics.writeOnImsCallHandoverEvent(mPhone.getPhoneId(),
2288                    TelephonyCallSession.Event.Type.IMS_CALL_HANDOVER_FAILED,
2289                    imsCall.getCallSession(), srcAccessTech, targetAccessTech, reasonInfo);
2290
2291            boolean isHandoverToWifi = srcAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN &&
2292                    targetAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
2293            ImsPhoneConnection conn = findConnection(imsCall);
2294            if (conn != null && isHandoverToWifi) {
2295                log("onCallHandoverFailed - handover to WIFI Failed");
2296
2297                // If we know we failed to handover, don't check for failure in the future.
2298                removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
2299
2300                if (mNotifyVtHandoverToWifiFail) {
2301                    // Only notify others if carrier config indicates to do so.
2302                    conn.onHandoverToWifiFailed();
2303                }
2304            }
2305        }
2306
2307        /**
2308         * Handles a change to the multiparty state for an {@code ImsCall}.  Notifies the associated
2309         * {@link ImsPhoneConnection} of the change.
2310         *
2311         * @param imsCall The IMS call.
2312         * @param isMultiParty {@code true} if the call became multiparty, {@code false}
2313         *      otherwise.
2314         */
2315        @Override
2316        public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
2317            if (DBG) log("onMultipartyStateChanged to " + (isMultiParty ? "Y" : "N"));
2318
2319            ImsPhoneConnection conn = findConnection(imsCall);
2320            if (conn != null) {
2321                conn.updateMultipartyState(isMultiParty);
2322            }
2323        }
2324    };
2325
2326    /**
2327     * Listen to the IMS call state change
2328     */
2329    private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
2330        @Override
2331        public void onCallStarted(ImsCall imsCall) {
2332            if (DBG) log("mImsUssdListener onCallStarted");
2333
2334            if (imsCall == mUssdSession) {
2335                if (mPendingUssd != null) {
2336                    AsyncResult.forMessage(mPendingUssd);
2337                    mPendingUssd.sendToTarget();
2338                    mPendingUssd = null;
2339                }
2340            }
2341        }
2342
2343        @Override
2344        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
2345            if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
2346
2347            onCallTerminated(imsCall, reasonInfo);
2348        }
2349
2350        @Override
2351        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
2352            if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
2353            removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
2354
2355            if (imsCall == mUssdSession) {
2356                mUssdSession = null;
2357                if (mPendingUssd != null) {
2358                    CommandException ex =
2359                            new CommandException(CommandException.Error.GENERIC_FAILURE);
2360                    AsyncResult.forMessage(mPendingUssd, null, ex);
2361                    mPendingUssd.sendToTarget();
2362                    mPendingUssd = null;
2363                }
2364            }
2365            imsCall.close();
2366        }
2367
2368        @Override
2369        public void onCallUssdMessageReceived(ImsCall call,
2370                int mode, String ussdMessage) {
2371            if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
2372
2373            int ussdMode = -1;
2374
2375            switch(mode) {
2376                case ImsCall.USSD_MODE_REQUEST:
2377                    ussdMode = CommandsInterface.USSD_MODE_REQUEST;
2378                    break;
2379
2380                case ImsCall.USSD_MODE_NOTIFY:
2381                    ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
2382                    break;
2383            }
2384
2385            mPhone.onIncomingUSSD(ussdMode, ussdMessage);
2386        }
2387    };
2388
2389    /**
2390     * Listen to the IMS service state change
2391     *
2392     */
2393    private ImsConnectionStateListener mImsConnectionStateListener =
2394        new ImsConnectionStateListener() {
2395        @Override
2396        public void onImsConnected() {
2397            if (DBG) log("onImsConnected");
2398            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
2399            mPhone.setImsRegistered(true);
2400            mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(),
2401                    ImsConnectionState.State.CONNECTED, null);
2402        }
2403
2404        @Override
2405        public void onImsDisconnected(ImsReasonInfo imsReasonInfo) {
2406            if (DBG) log("onImsDisconnected imsReasonInfo=" + imsReasonInfo);
2407            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
2408            mPhone.setImsRegistered(false);
2409            mPhone.processDisconnectReason(imsReasonInfo);
2410            mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(),
2411                    ImsConnectionState.State.DISCONNECTED, imsReasonInfo);
2412        }
2413
2414        @Override
2415        public void onImsProgressing() {
2416            if (DBG) log("onImsProgressing");
2417            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
2418            mPhone.setImsRegistered(false);
2419            mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(),
2420                    ImsConnectionState.State.PROGRESSING, null);
2421        }
2422
2423        @Override
2424        public void onImsResumed() {
2425            if (DBG) log("onImsResumed");
2426            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
2427            mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(),
2428                    ImsConnectionState.State.RESUMED, null);
2429        }
2430
2431        @Override
2432        public void onImsSuspended() {
2433            if (DBG) log("onImsSuspended");
2434            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
2435            mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(),
2436                    ImsConnectionState.State.SUSPENDED, null);
2437
2438        }
2439
2440        @Override
2441        public void onFeatureCapabilityChanged(int serviceClass,
2442                int[] enabledFeatures, int[] disabledFeatures) {
2443            if (serviceClass == ImsServiceClass.MMTEL) {
2444                boolean tmpIsVideoCallEnabled = isVideoCallEnabled();
2445                // Check enabledFeatures to determine capabilities. We ignore disabledFeatures.
2446                StringBuilder sb;
2447                if (DBG) {
2448                    sb = new StringBuilder(120);
2449                    sb.append("onFeatureCapabilityChanged: ");
2450                }
2451                for (int  i = ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE;
2452                        i <= ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI &&
2453                        i < enabledFeatures.length; i++) {
2454                    if (enabledFeatures[i] == i) {
2455                        // If the feature is set to its own integer value it is enabled.
2456                        if (DBG) {
2457                            sb.append(mImsFeatureStrings[i]);
2458                            sb.append(":true ");
2459                        }
2460
2461                        mImsFeatureEnabled[i] = true;
2462                    } else if (enabledFeatures[i]
2463                            == ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) {
2464                        // FEATURE_TYPE_UNKNOWN indicates that a feature is disabled.
2465                        if (DBG) {
2466                            sb.append(mImsFeatureStrings[i]);
2467                            sb.append(":false ");
2468                        }
2469
2470                        mImsFeatureEnabled[i] = false;
2471                    } else {
2472                        // Feature has unknown state; it is not its own value or -1.
2473                        if (DBG) {
2474                            loge("onFeatureCapabilityChanged(" + i + ", " + mImsFeatureStrings[i]
2475                                    + "): unexpectedValue=" + enabledFeatures[i]);
2476                        }
2477                    }
2478                }
2479                if (DBG) {
2480                    log(sb.toString());
2481                }
2482                if (tmpIsVideoCallEnabled != isVideoCallEnabled()) {
2483                    mPhone.notifyForVideoCapabilityChanged(isVideoCallEnabled());
2484                }
2485
2486                // TODO: Use the ImsCallSession or ImsCallProfile to tell the initial Wifi state and
2487                // {@link ImsCallSession.Listener#callSessionHandover} to listen for changes to
2488                // wifi capability caused by a handover.
2489                if (DBG) log("onFeatureCapabilityChanged: isVolteEnabled=" + isVolteEnabled()
2490                            + ", isVideoCallEnabled=" + isVideoCallEnabled()
2491                            + ", isVowifiEnabled=" + isVowifiEnabled()
2492                            + ", isUtEnabled=" + isUtEnabled());
2493                for (ImsPhoneConnection connection : mConnections) {
2494                    connection.updateWifiState();
2495                }
2496
2497                mPhone.onFeatureCapabilityChanged();
2498
2499                mMetrics.writeOnImsCapabilities(
2500                        mPhone.getPhoneId(), mImsFeatureEnabled);
2501            }
2502        }
2503
2504        @Override
2505        public void onVoiceMessageCountChanged(int count) {
2506            if (DBG) log("onVoiceMessageCountChanged :: count=" + count);
2507            mPhone.mDefaultPhone.setVoiceMessageCount(count);
2508        }
2509
2510        @Override
2511        public void registrationAssociatedUriChanged(Uri[] uris) {
2512            if (DBG) log("registrationAssociatedUriChanged");
2513            mPhone.setCurrentSubscriberUris(uris);
2514        }
2515    };
2516
2517    private ImsConfigListener.Stub mImsConfigListener = new ImsConfigListener.Stub() {
2518        @Override
2519        public void onGetFeatureResponse(int feature, int network, int value, int status) {}
2520
2521        @Override
2522        public void onSetFeatureResponse(int feature, int network, int value, int status) {
2523            mMetrics.writeImsSetFeatureValue(
2524                    mPhone.getPhoneId(), feature, network, value, status);
2525        }
2526
2527        @Override
2528        public void onGetVideoQuality(int status, int quality) {}
2529
2530        @Override
2531        public void onSetVideoQuality(int status) {}
2532
2533    };
2534
2535    public ImsUtInterface getUtInterface() throws ImsException {
2536        if (mImsManager == null) {
2537            throw getImsManagerIsNullException();
2538        }
2539
2540        ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration();
2541        return ut;
2542    }
2543
2544    private void transferHandoverConnections(ImsPhoneCall call) {
2545        if (call.mConnections != null) {
2546            for (Connection c : call.mConnections) {
2547                c.mPreHandoverState = call.mState;
2548                log ("Connection state before handover is " + c.getStateBeforeHandover());
2549            }
2550        }
2551        if (mHandoverCall.mConnections == null ) {
2552            mHandoverCall.mConnections = call.mConnections;
2553        } else { // Multi-call SRVCC
2554            mHandoverCall.mConnections.addAll(call.mConnections);
2555        }
2556        if (mHandoverCall.mConnections != null) {
2557            if (call.getImsCall() != null) {
2558                call.getImsCall().close();
2559            }
2560            for (Connection c : mHandoverCall.mConnections) {
2561                ((ImsPhoneConnection)c).changeParent(mHandoverCall);
2562                ((ImsPhoneConnection)c).releaseWakeLock();
2563            }
2564        }
2565        if (call.getState().isAlive()) {
2566            log ("Call is alive and state is " + call.mState);
2567            mHandoverCall.mState = call.mState;
2568        }
2569        call.mConnections.clear();
2570        call.mState = ImsPhoneCall.State.IDLE;
2571    }
2572
2573    /* package */
2574    void notifySrvccState(Call.SrvccState state) {
2575        if (DBG) log("notifySrvccState state=" + state);
2576
2577        mSrvccState = state;
2578
2579        if (mSrvccState == Call.SrvccState.COMPLETED) {
2580            transferHandoverConnections(mForegroundCall);
2581            transferHandoverConnections(mBackgroundCall);
2582            transferHandoverConnections(mRingingCall);
2583        }
2584    }
2585
2586    //****** Overridden from Handler
2587
2588    @Override
2589    public void
2590    handleMessage (Message msg) {
2591        AsyncResult ar;
2592        if (DBG) log("handleMessage what=" + msg.what);
2593
2594        switch (msg.what) {
2595            case EVENT_HANGUP_PENDINGMO:
2596                if (mPendingMO != null) {
2597                    mPendingMO.onDisconnect();
2598                    removeConnection(mPendingMO);
2599                    mPendingMO = null;
2600                }
2601                mPendingIntentExtras = null;
2602                updatePhoneState();
2603                mPhone.notifyPreciseCallStateChanged();
2604                break;
2605            case EVENT_RESUME_BACKGROUND:
2606                try {
2607                    resumeWaitingOrHolding();
2608                } catch (CallStateException e) {
2609                    if (Phone.DEBUG_PHONE) {
2610                        loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
2611                    }
2612                }
2613                break;
2614            case EVENT_DIAL_PENDINGMO:
2615                dialInternal(mPendingMO, mClirMode, mPendingCallVideoState, mPendingIntentExtras);
2616                mPendingIntentExtras = null;
2617                break;
2618
2619            case EVENT_EXIT_ECBM_BEFORE_PENDINGMO:
2620                if (mPendingMO != null) {
2621                    //Send ECBM exit request
2622                    try {
2623                        getEcbmInterface().exitEmergencyCallbackMode();
2624                        mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
2625                        pendingCallClirMode = mClirMode;
2626                        pendingCallInEcm = true;
2627                    } catch (ImsException e) {
2628                        e.printStackTrace();
2629                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
2630                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
2631                    }
2632                }
2633                break;
2634
2635            case EVENT_EXIT_ECM_RESPONSE_CDMA:
2636                // no matter the result, we still do the same here
2637                if (pendingCallInEcm) {
2638                    dialInternal(mPendingMO, pendingCallClirMode,
2639                            mPendingCallVideoState, mPendingIntentExtras);
2640                    mPendingIntentExtras = null;
2641                    pendingCallInEcm = false;
2642                }
2643                mPhone.unsetOnEcbModeExitResponse(this);
2644                break;
2645            case EVENT_VT_DATA_USAGE_UPDATE:
2646                ar = (AsyncResult) msg.obj;
2647                ImsCall call = (ImsCall) ar.userObj;
2648                Long usage = (long) ar.result;
2649                log("VT data usage update. usage = " + usage + ", imsCall = " + call);
2650
2651                Long oldUsage = 0L;
2652                if (mVtDataUsageMap.containsKey(call.uniqueId)) {
2653                    oldUsage = mVtDataUsageMap.get(call.uniqueId);
2654                }
2655                mTotalVtDataUsage += (usage - oldUsage);
2656                mVtDataUsageMap.put(call.uniqueId, usage);
2657                break;
2658            case EVENT_DATA_ENABLED_CHANGED:
2659                ar = (AsyncResult) msg.obj;
2660                if (ar.result instanceof Pair) {
2661                    Pair<Boolean, Integer> p = (Pair<Boolean, Integer>) ar.result;
2662                    onDataEnabledChanged(p.first, p.second);
2663                }
2664                break;
2665            case EVENT_GET_IMS_SERVICE:
2666                try {
2667                    getImsService();
2668                } catch (ImsException e) {
2669                    loge("getImsService: " + e);
2670                    retryGetImsService();
2671                }
2672                break;
2673            case EVENT_CHECK_FOR_WIFI_HANDOVER:
2674                if (msg.obj instanceof ImsCall) {
2675                    ImsCall imsCall = (ImsCall) msg.obj;
2676                    if (!imsCall.isWifiCall()) {
2677                        // Call did not handover to wifi, notify of handover failure.
2678                        ImsPhoneConnection conn = findConnection(imsCall);
2679                        if (conn != null) {
2680                            conn.onHandoverToWifiFailed();
2681                        }
2682                    }
2683                }
2684                break;
2685            case EVENT_CLEAR_DISCONNECTING_CONN:
2686                if (msg.obj instanceof ImsPhoneConnection) {
2687                    ImsPhoneConnection imsPhoneConnection = (ImsPhoneConnection) msg.obj;
2688                    removeMessages(EVENT_CLEAR_DISCONNECTING_CONN, imsPhoneConnection);
2689
2690                    // We have timed out waiting for the modem while disconnecting this connection.
2691                    // Manually disconnect to avoid tracking an invalid call.
2692                    if (imsPhoneConnection != null && imsPhoneConnection.isDisconnecting()) {
2693                        Rlog.e(LOG_TAG, "No response from modem. Manually disconnecting: " +
2694                                imsPhoneConnection);
2695                        imsPhoneConnection.onDisconnect();
2696                    }
2697                }
2698                break;
2699        }
2700    }
2701
2702    @Override
2703    protected void log(String msg) {
2704        Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
2705    }
2706
2707    protected void loge(String msg) {
2708        Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
2709    }
2710
2711    /**
2712     * Logs the current state of the ImsPhoneCallTracker.  Useful for debugging issues with
2713     * call tracking.
2714     */
2715    /* package */
2716    void logState() {
2717        if (!VERBOSE_STATE_LOGGING) {
2718            return;
2719        }
2720
2721        StringBuilder sb = new StringBuilder();
2722        sb.append("Current IMS PhoneCall State:\n");
2723        sb.append(" Foreground: ");
2724        sb.append(mForegroundCall);
2725        sb.append("\n");
2726        sb.append(" Background: ");
2727        sb.append(mBackgroundCall);
2728        sb.append("\n");
2729        sb.append(" Ringing: ");
2730        sb.append(mRingingCall);
2731        sb.append("\n");
2732        sb.append(" Handover: ");
2733        sb.append(mHandoverCall);
2734        sb.append("\n");
2735        Rlog.v(LOG_TAG, sb.toString());
2736    }
2737
2738    @Override
2739    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2740        pw.println("ImsPhoneCallTracker extends:");
2741        super.dump(fd, pw, args);
2742        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
2743        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
2744        pw.println(" mRingingCall=" + mRingingCall);
2745        pw.println(" mForegroundCall=" + mForegroundCall);
2746        pw.println(" mBackgroundCall=" + mBackgroundCall);
2747        pw.println(" mHandoverCall=" + mHandoverCall);
2748        pw.println(" mPendingMO=" + mPendingMO);
2749        //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
2750        pw.println(" mPhone=" + mPhone);
2751        pw.println(" mDesiredMute=" + mDesiredMute);
2752        pw.println(" mState=" + mState);
2753        for (int i = 0; i < mImsFeatureEnabled.length; i++) {
2754            pw.println(" " + mImsFeatureStrings[i] + ": "
2755                    + ((mImsFeatureEnabled[i]) ? "enabled" : "disabled"));
2756        }
2757        pw.println(" mTotalVtDataUsage=" + mTotalVtDataUsage);
2758        for (Map.Entry<Integer, Long> entry : mVtDataUsageMap.entrySet()) {
2759            pw.println("    id=" + entry.getKey() + " ,usage=" + entry.getValue());
2760        }
2761
2762        pw.flush();
2763        pw.println("++++++++++++++++++++++++++++++++");
2764
2765        try {
2766            if (mImsManager != null) {
2767                mImsManager.dump(fd, pw, args);
2768            }
2769        } catch (Exception e) {
2770            e.printStackTrace();
2771        }
2772
2773        if (mConnections != null && mConnections.size() > 0) {
2774            pw.println("mConnections:");
2775            for (int i = 0; i < mConnections.size(); i++) {
2776                pw.println("  [" + i + "]: " + mConnections.get(i));
2777            }
2778        }
2779    }
2780
2781    @Override
2782    protected void handlePollCalls(AsyncResult ar) {
2783    }
2784
2785    /* package */
2786    ImsEcbm getEcbmInterface() throws ImsException {
2787        if (mImsManager == null) {
2788            throw getImsManagerIsNullException();
2789        }
2790
2791        ImsEcbm ecbm = mImsManager.getEcbmInterface(mServiceId);
2792        return ecbm;
2793    }
2794
2795    /* package */
2796    ImsMultiEndpoint getMultiEndpointInterface() throws ImsException {
2797        if (mImsManager == null) {
2798            throw getImsManagerIsNullException();
2799        }
2800
2801        try {
2802            return mImsManager.getMultiEndpointInterface(mServiceId);
2803        } catch (ImsException e) {
2804            if (e.getCode() == ImsReasonInfo.CODE_MULTIENDPOINT_NOT_SUPPORTED) {
2805                return null;
2806            } else {
2807                throw e;
2808            }
2809
2810        }
2811    }
2812
2813    public boolean isInEmergencyCall() {
2814        return mIsInEmergencyCall;
2815    }
2816
2817    public boolean isVolteEnabled() {
2818        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE];
2819    }
2820
2821    public boolean isVowifiEnabled() {
2822        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI];
2823    }
2824
2825    public boolean isVideoCallEnabled() {
2826        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE]
2827                || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI]);
2828    }
2829
2830    @Override
2831    public PhoneConstants.State getState() {
2832        return mState;
2833    }
2834
2835    private void retryGetImsService() {
2836        //Leave mImsManager as null, then CallStateException will be thrown when dialing
2837        mImsManager = null;
2838        // Exponential backoff during retry, limited to 32 seconds.
2839        loge("getImsService: Retrying getting ImsService...");
2840        removeMessages(EVENT_GET_IMS_SERVICE);
2841        sendEmptyMessageDelayed(EVENT_GET_IMS_SERVICE,
2842                (1 << mImsServiceRetryCount) * IMS_RETRY_STARTING_TIMEOUT_MS);
2843        if (mImsServiceRetryCount <= CEILING_SERVICE_RETRY_COUNT) {
2844            mImsServiceRetryCount++;
2845        }
2846    }
2847
2848    private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall)
2849            throws RemoteException {
2850        IImsVideoCallProvider imsVideoCallProvider =
2851                imsCall.getCallSession().getVideoCallProvider();
2852        if (imsVideoCallProvider != null) {
2853            ImsVideoCallProviderWrapper imsVideoCallProviderWrapper =
2854                    new ImsVideoCallProviderWrapper(imsVideoCallProvider);
2855            conn.setVideoProvider(imsVideoCallProviderWrapper);
2856            imsVideoCallProviderWrapper.registerForDataUsageUpdate
2857                    (this, EVENT_VT_DATA_USAGE_UPDATE, imsCall);
2858            imsVideoCallProviderWrapper.addImsVideoProviderCallback(conn);
2859        }
2860    }
2861
2862    public boolean isUtEnabled() {
2863        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_LTE]
2864            || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI]);
2865    }
2866
2867    /**
2868     * Given a call subject, removes any characters considered by the current carrier to be
2869     * invalid, as well as escaping (using \) any characters which the carrier requires to be
2870     * escaped.
2871     *
2872     * @param callSubject The call subject.
2873     * @return The call subject with invalid characters removed and escaping applied as required.
2874     */
2875    private String cleanseInstantLetteringMessage(String callSubject) {
2876        if (TextUtils.isEmpty(callSubject)) {
2877            return callSubject;
2878        }
2879
2880        // Get the carrier config for the current sub.
2881        CarrierConfigManager configMgr = (CarrierConfigManager)
2882                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
2883        // Bail if we can't find the carrier config service.
2884        if (configMgr == null) {
2885            return callSubject;
2886        }
2887
2888        PersistableBundle carrierConfig = configMgr.getConfigForSubId(mPhone.getSubId());
2889        // Bail if no carrier config found.
2890        if (carrierConfig == null) {
2891            return callSubject;
2892        }
2893
2894        // Try to replace invalid characters
2895        String invalidCharacters = carrierConfig.getString(
2896                CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING);
2897        if (!TextUtils.isEmpty(invalidCharacters)) {
2898            callSubject = callSubject.replaceAll(invalidCharacters, "");
2899        }
2900
2901        // Try to escape characters which need to be escaped.
2902        String escapedCharacters = carrierConfig.getString(
2903                CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING);
2904        if (!TextUtils.isEmpty(escapedCharacters)) {
2905            callSubject = escapeChars(escapedCharacters, callSubject);
2906        }
2907        return callSubject;
2908    }
2909
2910    /**
2911     * Given a source string, return a string where a set of characters are escaped using the
2912     * backslash character.
2913     *
2914     * @param toEscape The characters to escape with a backslash.
2915     * @param source The source string.
2916     * @return The source string with characters escaped.
2917     */
2918    private String escapeChars(String toEscape, String source) {
2919        StringBuilder escaped = new StringBuilder();
2920        for (char c : source.toCharArray()) {
2921            if (toEscape.contains(Character.toString(c))) {
2922                escaped.append("\\");
2923            }
2924            escaped.append(c);
2925        }
2926
2927        return escaped.toString();
2928    }
2929
2930    /**
2931     * Initiates a pull of an external call.
2932     *
2933     * Initiates a pull by making a dial request with the {@link ImsCallProfile#EXTRA_IS_CALL_PULL}
2934     * extra specified.  We call {@link ImsPhone#notifyUnknownConnection(Connection)} which notifies
2935     * Telecom of the new dialed connection.  The
2936     * {@code PstnIncomingCallNotifier#maybeSwapWithUnknownConnection} logic ensures that the new
2937     * {@link ImsPhoneConnection} resulting from the dial gets swapped with the
2938     * {@link ImsExternalConnection}, which effectively makes the external call become a regular
2939     * call.  Magic!
2940     *
2941     * @param number The phone number of the call to be pulled.
2942     * @param videoState The desired video state of the pulled call.
2943     * @param dialogId The {@link ImsExternalConnection#getCallId()} dialog id associated with the
2944     *                 call which is being pulled.
2945     */
2946    @Override
2947    public void pullExternalCall(String number, int videoState, int dialogId) {
2948        Bundle extras = new Bundle();
2949        extras.putBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL, true);
2950        extras.putInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID, dialogId);
2951        try {
2952            Connection connection = dial(number, videoState, extras);
2953            mPhone.notifyUnknownConnection(connection);
2954        } catch (CallStateException e) {
2955            loge("pullExternalCall failed - " + e);
2956        }
2957    }
2958
2959    private ImsException getImsManagerIsNullException() {
2960        return new ImsException("no ims manager", ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
2961    }
2962
2963    /**
2964     * Determines if answering an incoming call will cause the active call to be disconnected.
2965     * <p>
2966     * This will be the case if
2967     * {@link CarrierConfigManager#KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL} is
2968     * {@code true} for the carrier, the active call is a video call over WIFI, and the incoming
2969     * call is an audio call.
2970     *
2971     * @param activeCall The active call.
2972     * @param incomingCall The incoming call.
2973     * @return {@code true} if answering the incoming call will cause the active call to be
2974     *      disconnected, {@code false} otherwise.
2975     */
2976    private boolean shouldDisconnectActiveCallOnAnswer(ImsCall activeCall,
2977            ImsCall incomingCall) {
2978
2979        if (activeCall == null || incomingCall == null) {
2980            return false;
2981        }
2982
2983        if (!mDropVideoCallWhenAnsweringAudioCall) {
2984            return false;
2985        }
2986
2987        boolean isActiveCallVideo = activeCall.isVideoCall() ||
2988                (mTreatDowngradedVideoCallsAsVideoCalls && activeCall.wasVideoCall());
2989        boolean isActiveCallOnWifi = activeCall.isWifiCall();
2990        boolean isVoWifiEnabled = mImsManager.isWfcEnabledByPlatform(mPhone.getContext()) &&
2991                mImsManager.isWfcEnabledByUser(mPhone.getContext());
2992        boolean isIncomingCallAudio = !incomingCall.isVideoCall();
2993        log("shouldDisconnectActiveCallOnAnswer : isActiveCallVideo=" + isActiveCallVideo +
2994                " isActiveCallOnWifi=" + isActiveCallOnWifi + " isIncomingCallAudio=" +
2995                isIncomingCallAudio + " isVowifiEnabled=" + isVoWifiEnabled);
2996
2997        return isActiveCallVideo && isActiveCallOnWifi && isIncomingCallAudio && !isVoWifiEnabled;
2998    }
2999
3000    /** Get aggregated video call data usage since boot.
3001     *
3002     * @return data usage in bytes
3003     */
3004    public long getVtDataUsage() {
3005
3006        // If there is an ongoing VT call, request the latest VT usage from the modem. The latest
3007        // usage will return asynchronously so it won't be counted in this round, but it will be
3008        // eventually counted when next getVtDataUsage is called.
3009        if (mState != PhoneConstants.State.IDLE) {
3010            for (ImsPhoneConnection conn : mConnections) {
3011                android.telecom.Connection.VideoProvider videoProvider = conn.getVideoProvider();
3012                if (videoProvider != null) {
3013                    videoProvider.onRequestConnectionDataUsage();
3014                }
3015            }
3016        }
3017
3018        return mTotalVtDataUsage;
3019    }
3020
3021    public void registerPhoneStateListener(PhoneStateListener listener) {
3022        mPhoneStateListeners.add(listener);
3023    }
3024
3025    public void unregisterPhoneStateListener(PhoneStateListener listener) {
3026        mPhoneStateListeners.remove(listener);
3027    }
3028
3029    /**
3030     * Notifies local telephony listeners of changes to the IMS phone state.
3031     *
3032     * @param oldState The old state.
3033     * @param newState The new state.
3034     */
3035    private void notifyPhoneStateChanged(PhoneConstants.State oldState,
3036            PhoneConstants.State newState) {
3037
3038        for (PhoneStateListener listener : mPhoneStateListeners) {
3039            listener.onPhoneStateChanged(oldState, newState);
3040        }
3041    }
3042
3043    /** Modify video call to a new video state.
3044     *
3045     * @param imsCall IMS call to be modified
3046     * @param newVideoState New video state. (Refer to VideoProfile)
3047     */
3048    private void modifyVideoCall(ImsCall imsCall, int newVideoState) {
3049        ImsPhoneConnection conn = findConnection(imsCall);
3050        if (conn != null) {
3051            int oldVideoState = conn.getVideoState();
3052            if (conn.getVideoProvider() != null) {
3053                conn.getVideoProvider().onSendSessionModifyRequest(
3054                        new VideoProfile(oldVideoState), new VideoProfile(newVideoState));
3055            }
3056        }
3057    }
3058
3059    /**
3060     * Handler of data enabled changed event
3061     * @param enabled True if data is enabled, otherwise disabled.
3062     * @param reason Reason for data enabled/disabled (see {@code REASON_*} in
3063     *      {@link DataEnabledSettings}.
3064     */
3065    private void onDataEnabledChanged(boolean enabled, int reason) {
3066
3067        log("onDataEnabledChanged: enabled=" + enabled + ", reason=" + reason);
3068
3069        ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId()).setDataEnabled(enabled);
3070
3071        if (mIgnoreDataEnabledChangedForVideoCalls) {
3072            log("Ignore data " + ((enabled) ? "enabled" : "disabled") + " due to carrier policy.");
3073            return;
3074        }
3075
3076        if (!enabled) {
3077            int reasonCode;
3078            if (reason == DataEnabledSettings.REASON_POLICY_DATA_ENABLED) {
3079                reasonCode = ImsReasonInfo.CODE_DATA_LIMIT_REACHED;
3080            } else if (reason == DataEnabledSettings.REASON_USER_DATA_ENABLED) {
3081                reasonCode = ImsReasonInfo.CODE_DATA_DISABLED;
3082            } else {
3083                // Unexpected code, default to data disabled.
3084                reasonCode = ImsReasonInfo.CODE_DATA_DISABLED;
3085            }
3086
3087            // If data is disabled while there are ongoing VT calls which are not taking place over
3088            // wifi, then they should be disconnected to prevent the user from incurring further
3089            // data charges.
3090            for (ImsPhoneConnection conn : mConnections) {
3091                ImsCall imsCall = conn.getImsCall();
3092                if (imsCall != null && imsCall.isVideoCall() && !imsCall.isWifiCall()) {
3093                    if (conn.hasCapabilities(
3094                            Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL |
3095                                    Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE)) {
3096
3097                        // If the carrier supports downgrading to voice, then we can simply issue a
3098                        // downgrade to voice instead of terminating the call.
3099                        if (reasonCode == ImsReasonInfo.CODE_DATA_DISABLED) {
3100                            conn.onConnectionEvent(TelephonyManager.EVENT_DOWNGRADE_DATA_DISABLED,
3101                                    null);
3102                        } else if (reasonCode == ImsReasonInfo.CODE_DATA_LIMIT_REACHED) {
3103                            conn.onConnectionEvent(
3104                                    TelephonyManager.EVENT_DOWNGRADE_DATA_LIMIT_REACHED, null);
3105                        }
3106                        modifyVideoCall(imsCall, VideoProfile.STATE_AUDIO_ONLY);
3107                    } else {
3108                        // If the carrier does not support downgrading to voice, the only choice we
3109                        // have is to terminate the call.
3110                        try {
3111                            imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED, reasonCode);
3112                        } catch (ImsException ie) {
3113                            loge("Couldn't terminate call " + imsCall);
3114                        }
3115                    }
3116                }
3117            }
3118        }
3119
3120        // This will call into updateVideoCallFeatureValue and eventually all clients will be
3121        // asynchronously notified that the availability of VT over LTE has changed.
3122        ImsManager.updateImsServiceConfig(mPhone.getContext(), mPhone.getPhoneId(), true);
3123    }
3124
3125    /**
3126     * @return {@code true} if the device is connected to a WIFI network, {@code false} otherwise.
3127     */
3128    private boolean isWifiConnected() {
3129        ConnectivityManager cm = (ConnectivityManager) mPhone.getContext()
3130                .getSystemService(Context.CONNECTIVITY_SERVICE);
3131        if (cm != null) {
3132            NetworkInfo ni = cm.getActiveNetworkInfo();
3133            if (ni != null && ni.isConnected()) {
3134                return ni.getType() == ConnectivityManager.TYPE_WIFI;
3135            }
3136        }
3137        return false;
3138    }
3139
3140    /**
3141     * @return {@code true} if downgrading of a video call to audio is supported.
3142     */
3143    public boolean isCarrierDowngradeOfVtCallSupported() {
3144        return mSupportDowngradeVtToAudio;
3145    }
3146}
3147