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