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