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