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