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