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