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