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