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