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