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