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