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