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