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