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