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