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