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