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