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