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