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