All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.internal.telephony.imsphone.ImsPhone Maven / Gradle / Ivy

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.telephony.imsphone;

import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE;
import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE;

import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAIC;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAICr;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAOC;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAOIC;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAOICxH;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BA_ALL;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BA_MO;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BA_MT;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BIC_ACR;
import static com.android.internal.telephony.CommandsInterface.CF_ACTION_DISABLE;
import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ERASURE;
import static com.android.internal.telephony.CommandsInterface.CF_ACTION_REGISTRATION;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL_CONDITIONAL;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_BUSY;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_NOT_REACHABLE;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_NO_REPLY;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;

import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncResult;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.Registrant;
import android.os.RegistrantList;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.preference.PreferenceManager;
import android.sysprop.TelephonyProperties;
import android.telephony.AccessNetworkConstants;
import android.telephony.CarrierConfigManager;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.UssdResponse;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsCallForwardInfo;
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsSsData;
import android.telephony.ims.ImsSsInfo;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.stub.ImsUtImplBase;
import android.text.TextUtils;
import android.util.LocalLog;

import com.android.ims.ImsEcbm;
import com.android.ims.ImsEcbmStateListener;
import com.android.ims.ImsException;
import com.android.ims.ImsManager;
import com.android.ims.ImsUtInterface;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallFailCause;
import com.android.internal.telephony.CallForwardInfo;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.CallTracker;
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.GsmCdmaPhone;
import com.android.internal.telephony.MmiCode;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneNotifier;
import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.TelephonyComponentFactory;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.dataconnection.TransportManager;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.metrics.ImsStats;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.metrics.VoiceCallSessionStats;
import com.android.internal.telephony.nano.TelephonyProto.ImsConnectionState;
import com.android.internal.telephony.uicc.IccRecords;
import com.android.internal.telephony.util.NotificationChannelController;
import com.android.internal.util.IndentingPrintWriter;
import com.android.telephony.Rlog;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
 * {@hide}
 */
public class ImsPhone extends ImsPhoneBase {
    private static final String LOG_TAG = "ImsPhone";
    private static final boolean DBG = true;
    private static final boolean VDBG = false; // STOPSHIP if true

    private static final int EVENT_SET_CALL_BARRING_DONE             = EVENT_LAST + 1;
    private static final int EVENT_GET_CALL_BARRING_DONE             = EVENT_LAST + 2;
    private static final int EVENT_SET_CALL_WAITING_DONE             = EVENT_LAST + 3;
    private static final int EVENT_GET_CALL_WAITING_DONE             = EVENT_LAST + 4;
    private static final int EVENT_SET_CLIR_DONE                     = EVENT_LAST + 5;
    private static final int EVENT_GET_CLIR_DONE                     = EVENT_LAST + 6;
    private static final int EVENT_DEFAULT_PHONE_DATA_STATE_CHANGED  = EVENT_LAST + 7;
    @VisibleForTesting
    public static final int EVENT_SERVICE_STATE_CHANGED             = EVENT_LAST + 8;
    private static final int EVENT_VOICE_CALL_ENDED                  = EVENT_LAST + 9;
    private static final int EVENT_INITIATE_VOLTE_SILENT_REDIAL      = EVENT_LAST + 10;
    private static final int EVENT_GET_CLIP_DONE                     = EVENT_LAST + 11;

    static final int RESTART_ECM_TIMER = 0; // restart Ecm timer
    static final int CANCEL_ECM_TIMER  = 1; // cancel Ecm timer

    // Default Emergency Callback Mode exit timer
    private static final long DEFAULT_ECM_EXIT_TIMER_VALUE = 300000;

    // String to Call Composer Option Prefix set by user
    private static final String PREF_USER_SET_CALL_COMPOSER_PREFIX = "userset_callcomposer_prefix";

    /**
     * Used to create ImsManager instances, which may be injected during testing.
     */
    @VisibleForTesting
    public interface ImsManagerFactory {
        /**
         * Create a new instance of ImsManager for the specified phoneId.
         */
        ImsManager create(Context context, int phoneId);
    }

    public static class ImsDialArgs extends DialArgs {
        public static class Builder extends DialArgs.Builder {
            private android.telecom.Connection.RttTextStream mRttTextStream;
            private int mRetryCallFailCause = ImsReasonInfo.CODE_UNSPECIFIED;
            private int mRetryCallFailNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
            private boolean mIsWpsCall = false;

            public static ImsDialArgs.Builder from(DialArgs dialArgs) {
                if (dialArgs instanceof ImsDialArgs) {
                    return new ImsDialArgs.Builder()
                            .setUusInfo(dialArgs.uusInfo)
                            .setIsEmergency(dialArgs.isEmergency)
                            .setVideoState(dialArgs.videoState)
                            .setIntentExtras(dialArgs.intentExtras)
                            .setRttTextStream(((ImsDialArgs)dialArgs).rttTextStream)
                            .setClirMode(dialArgs.clirMode)
                            .setRetryCallFailCause(((ImsDialArgs)dialArgs).retryCallFailCause)
                            .setRetryCallFailNetworkType(
                                    ((ImsDialArgs)dialArgs).retryCallFailNetworkType)
                            .setIsWpsCall(((ImsDialArgs)dialArgs).isWpsCall);
                }
                return new ImsDialArgs.Builder()
                        .setUusInfo(dialArgs.uusInfo)
                        .setIsEmergency(dialArgs.isEmergency)
                        .setVideoState(dialArgs.videoState)
                        .setClirMode(dialArgs.clirMode)
                        .setIntentExtras(dialArgs.intentExtras);
            }

            public ImsDialArgs.Builder setRttTextStream(
                    android.telecom.Connection.RttTextStream s) {
                mRttTextStream = s;
                return this;
            }

            public ImsDialArgs.Builder setRetryCallFailCause(int retryCallFailCause) {
                this.mRetryCallFailCause = retryCallFailCause;
                return this;
            }

            public ImsDialArgs.Builder setRetryCallFailNetworkType(int retryCallFailNetworkType) {
                this.mRetryCallFailNetworkType = retryCallFailNetworkType;
                return this;
            }

            public ImsDialArgs.Builder setIsWpsCall(boolean isWpsCall) {
                this.mIsWpsCall = isWpsCall;
                return this;
            }

            public ImsDialArgs build() {
                return new ImsDialArgs(this);
            }
        }

        /**
         * The RTT text stream. If non-null, indicates that connection supports RTT
         * communication with the in-call app.
         */
        public final android.telecom.Connection.RttTextStream rttTextStream;

        public final int retryCallFailCause;
        public final int retryCallFailNetworkType;

        /** Indicates the call is Wireless Priority Service call */
        public final boolean isWpsCall;

        private ImsDialArgs(ImsDialArgs.Builder b) {
            super(b);
            this.rttTextStream = b.mRttTextStream;
            this.retryCallFailCause = b.mRetryCallFailCause;
            this.retryCallFailNetworkType = b.mRetryCallFailNetworkType;
            this.isWpsCall = b.mIsWpsCall;
        }
    }

    // Instance Variables
    Phone mDefaultPhone;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    ImsPhoneCallTracker mCT;
    ImsExternalCallTracker mExternalCallTracker;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private ArrayList  mPendingMMIs = new ArrayList();
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private ServiceState mSS = new ServiceState();

    private final ImsManagerFactory mImsManagerFactory;

    private SharedPreferences mImsPhoneSharedPreferences;

    // To redial silently through GSM or CDMA when dialing through IMS fails
    private String mLastDialString;

    private WakeLock mWakeLock;

    // mEcmExitRespRegistrant is informed after the phone has been exited the emergency
    // callback mode keep track of if phone is in emergency callback mode
    private Registrant mEcmExitRespRegistrant;

    private final RegistrantList mSilentRedialRegistrants = new RegistrantList();

    private final LocalLog mRegLocalLog = new LocalLog(100);
    private TelephonyMetrics mMetrics;

    // The helper class to receive and store the MmTel registration status updated.
    private ImsRegistrationCallbackHelper mImsMmTelRegistrationHelper;

    // The roaming state if currently in service, or the last roaming state when was in service.
    private boolean mLastKnownRoamingState = false;

    private boolean mIsInImsEcm = false;

    // List of Registrants to send supplementary service notifications to.
    private RegistrantList mSsnRegistrants = new RegistrantList();

    private ImsStats mImsStats;

    // A runnable which is used to automatically exit from Ecm after a period of time.
    private Runnable mExitEcmRunnable = new Runnable() {
        @Override
        public void run() {
            exitEmergencyCallbackMode();
        }
    };

    private Uri[] mCurrentSubscriberUris;

    protected void setCurrentSubscriberUris(Uri[] currentSubscriberUris) {
        this.mCurrentSubscriberUris = currentSubscriberUris;
    }

    @Override
    public Uri[] getCurrentSubscriberUris() {
        return mCurrentSubscriberUris;
    }

    /** Set call composer status from users for the current subscription */
    public void setCallComposerStatus(int status) {
        mImsPhoneSharedPreferences.edit().putInt(
                PREF_USER_SET_CALL_COMPOSER_PREFIX + getSubId(), status).commit();
    }

    /** Get call composer status from users for the current subscription */
    public int getCallComposerStatus() {
        return mImsPhoneSharedPreferences.getInt(PREF_USER_SET_CALL_COMPOSER_PREFIX + getSubId(),
                TelephonyManager.CALL_COMPOSER_STATUS_OFF);
    }

    @Override
    public int getEmergencyNumberDbVersion() {
        return getEmergencyNumberTracker().getEmergencyNumberDbVersion();
    }

    @Override
    public EmergencyNumberTracker getEmergencyNumberTracker() {
        return mDefaultPhone.getEmergencyNumberTracker();
    }

    @Override
    public ServiceStateTracker getServiceStateTracker() {
        return mDefaultPhone.getServiceStateTracker();
    }

    // Create Cf (Call forward) so that dialling number &
    // mIsCfu (true if reason is call forward unconditional)
    // mOnComplete (Message object passed by client) can be packed &
    // given as a single Cf object as user data to UtInterface.
    private static class Cf {
        final String mSetCfNumber;
        final Message mOnComplete;
        final boolean mIsCfu;

        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        Cf(String cfNumber, boolean isCfu, Message onComplete) {
            mSetCfNumber = cfNumber;
            mIsCfu = isCfu;
            mOnComplete = onComplete;
        }
    }

    // Create SS (Supplementary Service) so that save SS params &
    // mOnComplete (Message object passed by client) can be packed
    // given as a single SS object as user data to UtInterface.
    @VisibleForTesting
    public static class SS {
        int mCfAction;
        int mCfReason;
        String mDialingNumber;
        int mTimerSeconds;
        boolean mEnable;
        int mClirMode;
        String mFacility;
        boolean mLockState;
        String mPassword;
        int mServiceClass;
        @VisibleForTesting
        public Message mOnComplete;

        // Default // Query CW, CLIR, CLIP
        SS(Message onComplete) {
            mOnComplete = onComplete;
        }

        // Update CLIP
        SS(boolean enable, Message onComplete) {
            mEnable = enable;
            mOnComplete = onComplete;
        }

        // Update CLIR
        SS(int clirMode, Message onComplete) {
            mClirMode = clirMode;
            mOnComplete = onComplete;
        }

        // Update CW
        SS(boolean enable, int serviceClass, Message onComplete) {
            mEnable = enable;
            mServiceClass = serviceClass;
            mOnComplete = onComplete;
        }

        // Query CF
        SS(int cfReason, int serviceClass, Message onComplete) {
            mCfReason = cfReason;
            mServiceClass = serviceClass;
            mOnComplete = onComplete;
        }

        // Update CF
        SS(int cfAction, int cfReason, String dialingNumber,
           int serviceClass, int timerSeconds, Message onComplete) {
            mCfAction = cfAction;
            mCfReason = cfReason;
            mDialingNumber = dialingNumber;
            mServiceClass = serviceClass;
            mTimerSeconds = timerSeconds;
            mOnComplete = onComplete;
        }

        // Query CB
        SS(String facility, String password, int serviceClass, Message onComplete) {
            mFacility = facility;
            mPassword = password;
            mServiceClass = serviceClass;
            mOnComplete = onComplete;
        }

        // Update CB
        SS(String facility, boolean lockState, String password,
                int serviceClass, Message onComplete) {
            mFacility = facility;
            mLockState = lockState;
            mPassword = password;
            mServiceClass = serviceClass;
            mOnComplete = onComplete;
        }
    }

    // Constructors
    public ImsPhone(Context context, PhoneNotifier notifier, Phone defaultPhone) {
        this(context, notifier, defaultPhone, ImsManager::getInstance, false);
    }

    @VisibleForTesting
    public ImsPhone(Context context, PhoneNotifier notifier, Phone defaultPhone,
            ImsManagerFactory imsManagerFactory, boolean unitTestMode) {
        super("ImsPhone", context, notifier, unitTestMode);

        mDefaultPhone = defaultPhone;
        mImsManagerFactory = imsManagerFactory;
        mImsPhoneSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
        mImsStats = new ImsStats(this);
        // The ImsExternalCallTracker needs to be defined before the ImsPhoneCallTracker, as the
        // ImsPhoneCallTracker uses a thread to spool up the ImsManager.  Part of this involves
        // setting the multiendpoint listener on the external call tracker.  So we need to ensure
        // the external call tracker is available first to avoid potential timing issues.
        mExternalCallTracker =
                TelephonyComponentFactory.getInstance()
                        .inject(ImsExternalCallTracker.class.getName())
                        .makeImsExternalCallTracker(this);
        mCT = TelephonyComponentFactory.getInstance().inject(ImsPhoneCallTracker.class.getName())
                .makeImsPhoneCallTracker(this);
        mCT.registerPhoneStateListener(mExternalCallTracker);
        mExternalCallTracker.setCallPuller(mCT);

        mSS.setStateOff();

        mPhoneId = mDefaultPhone.getPhoneId();

        mMetrics = TelephonyMetrics.getInstance();

        mImsMmTelRegistrationHelper = new ImsRegistrationCallbackHelper(mMmTelRegistrationUpdate,
                context.getMainExecutor());

        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
        mWakeLock.setReferenceCounted(false);

        if (mDefaultPhone.getServiceStateTracker() != null
                && mDefaultPhone.getTransportManager() != null) {
            for (int transport : mDefaultPhone.getTransportManager().getAvailableTransports()) {
                mDefaultPhone.getServiceStateTracker()
                        .registerForDataRegStateOrRatChanged(transport, this,
                                EVENT_DEFAULT_PHONE_DATA_STATE_CHANGED, null);
            }
        }
        // Sets the Voice reg state to STATE_OUT_OF_SERVICE and also queries the data service
        // state. We don't ever need the voice reg state to be anything other than in or out of
        // service.
        setServiceState(ServiceState.STATE_OUT_OF_SERVICE);

        mDefaultPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
        // Force initial roaming state update later, on EVENT_CARRIER_CONFIG_CHANGED.
        // Settings provider or CarrierConfig may not be loaded now.

        mDefaultPhone.registerForVolteSilentRedial(this, EVENT_INITIATE_VOLTE_SILENT_REDIAL, null);
    }

    //todo: get rid of this function. It is not needed since parentPhone obj never changes
    @Override
    public void dispose() {
        logd("dispose");
        // Nothing to dispose in Phone
        //super.dispose();
        mPendingMMIs.clear();
        mExternalCallTracker.tearDown();
        mCT.unregisterPhoneStateListener(mExternalCallTracker);
        mCT.unregisterForVoiceCallEnded(this);
        mCT.dispose();

        //Force all referenced classes to unregister their former registered events
        if (mDefaultPhone != null && mDefaultPhone.getServiceStateTracker() != null) {
            for (int transport : mDefaultPhone.getTransportManager().getAvailableTransports()) {
                mDefaultPhone.getServiceStateTracker()
                        .unregisterForDataRegStateOrRatChanged(transport, this);
            }
            mDefaultPhone.unregisterForServiceStateChanged(this);
        }

        if (mDefaultPhone != null) {
            mDefaultPhone.unregisterForVolteSilentRedial(this);
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    public ServiceState getServiceState() {
        return mSS;
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @VisibleForTesting
    public void setServiceState(int state) {
        boolean isVoiceRegStateChanged = false;

        synchronized (this) {
            isVoiceRegStateChanged = mSS.getState() != state;
            mSS.setVoiceRegState(state);
        }
        updateDataServiceState();

        if (isVoiceRegStateChanged) {
            if (mDefaultPhone.getServiceStateTracker() != null) {
                mDefaultPhone.getServiceStateTracker().onImsServiceStateChanged();
            }
        }
    }

    @Override
    public CallTracker getCallTracker() {
        return mCT;
    }

    public ImsExternalCallTracker getExternalCallTracker() {
        return mExternalCallTracker;
    }

    @Override
    public List
    getPendingMmiCodes() {
        return mPendingMMIs;
    }

    @Override
    public void
    acceptCall(int videoState) throws CallStateException {
        mCT.acceptCall(videoState);
    }

    @Override
    public void
    rejectCall() throws CallStateException {
        mCT.rejectCall();
    }

    @Override
    public void
    switchHoldingAndActive() throws CallStateException {
        throw new UnsupportedOperationException("Use hold() and unhold() instead.");
    }

    @Override
    public boolean canConference() {
        return mCT.canConference();
    }

    public boolean canDial() {
        try {
            mCT.checkForDialIssues();
        } catch (CallStateException cse) {
            return false;
        }
        return true;
    }

    @Override
    public void conference() {
        mCT.conference();
    }

    @Override
    public void clearDisconnected() {
        mCT.clearDisconnected();
    }

    @Override
    public boolean canTransfer() {
        return mCT.canTransfer();
    }

    @Override
    public void explicitCallTransfer() throws CallStateException {
        mCT.explicitCallTransfer();
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    public ImsPhoneCall
    getForegroundCall() {
        return mCT.mForegroundCall;
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    public ImsPhoneCall
    getBackgroundCall() {
        return mCT.mBackgroundCall;
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    public ImsPhoneCall
    getRingingCall() {
        return mCT.mRingingCall;
    }

    @Override
    public boolean isImsAvailable() {
        return mCT.isImsServiceReady();
    }

    /**
     * Hold the currently active call, possibly unholding a currently held call.
     * @throws CallStateException
     */
    public void holdActiveCall() throws CallStateException {
        mCT.holdActiveCall();
    }

    /**
     * Unhold the currently active call, possibly holding a currently active call.
     * If the call tracker is already in the middle of a hold operation, this is a noop.
     * @throws CallStateException
     */
    public void unholdHeldCall() throws CallStateException {
        mCT.unholdHeldCall();
    }

    private boolean handleCallDeflectionIncallSupplementaryService(
            String dialString) {
        if (dialString.length() > 1) {
            return false;
        }

        if (getRingingCall().getState() != ImsPhoneCall.State.IDLE) {
            if (DBG) logd("MmiCode 0: rejectCall");
            try {
                mCT.rejectCall();
            } catch (CallStateException e) {
                if (DBG) Rlog.d(LOG_TAG, "reject failed", e);
                notifySuppServiceFailed(Phone.SuppService.REJECT);
            }
        } else if (getBackgroundCall().getState() != ImsPhoneCall.State.IDLE) {
            if (DBG) logd("MmiCode 0: hangupWaitingOrBackground");
            try {
                mCT.hangup(getBackgroundCall());
            } catch (CallStateException e) {
                if (DBG) Rlog.d(LOG_TAG, "hangup failed", e);
            }
        }

        return true;
    }

    private void sendUssdResponse(String ussdRequest, CharSequence message, int returnCode,
                                   ResultReceiver wrappedCallback) {
        UssdResponse response = new UssdResponse(ussdRequest, message);
        Bundle returnData = new Bundle();
        returnData.putParcelable(TelephonyManager.USSD_RESPONSE, response);
        wrappedCallback.send(returnCode, returnData);

    }

    @Override
    public boolean handleUssdRequest(String ussdRequest, ResultReceiver wrappedCallback)
            throws CallStateException {
        if (mPendingMMIs.size() > 0) {
            // There are MMI codes in progress; fail attempt now.
            logi("handleUssdRequest: queue full: " + Rlog.pii(LOG_TAG, ussdRequest));
            sendUssdResponse(ussdRequest, null, TelephonyManager.USSD_RETURN_FAILURE,
                    wrappedCallback );
            return true;
        }
        try {
            dialInternal(ussdRequest, new ImsDialArgs.Builder().build(), wrappedCallback);
        } catch (CallStateException cse) {
            if (CS_FALLBACK.equals(cse.getMessage())) {
                throw cse;
            } else {
                Rlog.w(LOG_TAG, "Could not execute USSD " + cse);
                sendUssdResponse(ussdRequest, null, TelephonyManager.USSD_RETURN_FAILURE,
                        wrappedCallback);
            }
        } catch (Exception e) {
            Rlog.w(LOG_TAG, "Could not execute USSD " + e);
            sendUssdResponse(ussdRequest, null, TelephonyManager.USSD_RETURN_FAILURE,
                    wrappedCallback);
            return false;
        }
        return true;
    }

    private boolean handleCallWaitingIncallSupplementaryService(
            String dialString) {
        int len = dialString.length();

        if (len > 2) {
            return false;
        }

        ImsPhoneCall call = getForegroundCall();

        try {
            if (len > 1) {
                if (DBG) logd("not support 1X SEND");
                notifySuppServiceFailed(Phone.SuppService.HANGUP);
            } else {
                if (call.getState() != ImsPhoneCall.State.IDLE) {
                    if (DBG) logd("MmiCode 1: hangup foreground");
                    mCT.hangup(call);
                } else {
                    if (DBG) logd("MmiCode 1: holdActiveCallForWaitingCall");
                    mCT.holdActiveCallForWaitingCall();
                }
            }
        } catch (CallStateException e) {
            if (DBG) Rlog.d(LOG_TAG, "hangup failed", e);
            notifySuppServiceFailed(Phone.SuppService.HANGUP);
        }

        return true;
    }

    private boolean handleCallHoldIncallSupplementaryService(String dialString) {
        int len = dialString.length();

        if (len > 2) {
            return false;
        }

        if (len > 1) {
            if (DBG) logd("separate not supported");
            notifySuppServiceFailed(Phone.SuppService.SEPARATE);
        } else {
            try {
                if (getRingingCall().getState() != ImsPhoneCall.State.IDLE) {
                    if (DBG) logd("MmiCode 2: accept ringing call");
                    mCT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
                } else if (getBackgroundCall().getState() == ImsPhoneCall.State.HOLDING) {
                    // If there's an active ongoing call as well, hold it and the background one
                    // should automatically unhold. Otherwise just unhold the background call.
                    if (getForegroundCall().getState() != ImsPhoneCall.State.IDLE) {
                        if (DBG) logd("MmiCode 2: switch holding and active");
                        mCT.holdActiveCall();
                    } else {
                        if (DBG) logd("MmiCode 2: unhold held call");
                        mCT.unholdHeldCall();
                    }
                } else if (getForegroundCall().getState() != ImsPhoneCall.State.IDLE) {
                    if (DBG) logd("MmiCode 2: hold active call");
                    mCT.holdActiveCall();
                }
            } catch (CallStateException e) {
                if (DBG) Rlog.d(LOG_TAG, "switch failed", e);
                notifySuppServiceFailed(Phone.SuppService.SWITCH);
            }
        }

        return true;
    }

    private boolean handleMultipartyIncallSupplementaryService(
            String dialString) {
        if (dialString.length() > 1) {
            return false;
        }

        if (DBG) logd("MmiCode 3: merge calls");
        conference();
        return true;
    }

    private boolean handleEctIncallSupplementaryService(String dialString) {
        if (dialString.length() != 1) {
            return false;
        }

        if (DBG) logd("MmiCode 4: explicit call transfer");
        try {
            explicitCallTransfer();
        } catch (CallStateException e) {
            if (DBG) Rlog.d(LOG_TAG, "explicit call transfer failed", e);
            notifySuppServiceFailed(Phone.SuppService.TRANSFER);
        }
        return true;
    }

    private boolean handleCcbsIncallSupplementaryService(String dialString) {
        if (dialString.length() > 1) {
            return false;
        }

        logi("MmiCode 5: CCBS not supported!");
        // Treat it as an "unknown" service.
        notifySuppServiceFailed(Phone.SuppService.UNKNOWN);
        return true;
    }

    public void notifySuppSvcNotification(SuppServiceNotification suppSvc) {
        logd("notifySuppSvcNotification: suppSvc = " + suppSvc);

        AsyncResult ar = new AsyncResult(null, suppSvc, null);
        mSsnRegistrants.notifyRegistrants(ar);
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    public boolean handleInCallMmiCommands(String dialString) {
        if (!isInCall()) {
            return false;
        }

        if (TextUtils.isEmpty(dialString)) {
            return false;
        }

        boolean result = false;
        char ch = dialString.charAt(0);
        switch (ch) {
            case '0':
                result = handleCallDeflectionIncallSupplementaryService(
                        dialString);
                break;
            case '1':
                result = handleCallWaitingIncallSupplementaryService(
                        dialString);
                break;
            case '2':
                result = handleCallHoldIncallSupplementaryService(dialString);
                break;
            case '3':
                result = handleMultipartyIncallSupplementaryService(dialString);
                break;
            case '4':
                result = handleEctIncallSupplementaryService(dialString);
                break;
            case '5':
                result = handleCcbsIncallSupplementaryService(dialString);
                break;
            default:
                break;
        }

        return result;
    }

    boolean isInCall() {
        ImsPhoneCall.State foregroundCallState = getForegroundCall().getState();
        ImsPhoneCall.State backgroundCallState = getBackgroundCall().getState();
        ImsPhoneCall.State ringingCallState = getRingingCall().getState();

       return (foregroundCallState.isAlive() ||
               backgroundCallState.isAlive() ||
               ringingCallState.isAlive());
    }

    @Override
    public boolean isInImsEcm() {
        return mIsInImsEcm;
    }

    @Override
    public boolean isInEcm() {
        return mDefaultPhone.isInEcm();
    }

    @Override
    public void setIsInEcm(boolean isInEcm){
        mIsInImsEcm = isInEcm;
        mDefaultPhone.setIsInEcm(isInEcm);
    }

    public void notifyNewRingingConnection(Connection c) {
        mDefaultPhone.notifyNewRingingConnectionP(c);
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    void notifyUnknownConnection(Connection c) {
        mDefaultPhone.notifyUnknownConnectionP(c);
    }

    @Override
    public void notifyForVideoCapabilityChanged(boolean isVideoCapable) {
        mIsVideoCapable = isVideoCapable;
        mDefaultPhone.notifyForVideoCapabilityChanged(isVideoCapable);
    }

    @Override
    public void setRadioPower(boolean on, boolean forEmergencyCall,
            boolean isSelectedPhoneForEmergencyCall, boolean forceApply) {
        mDefaultPhone.setRadioPower(on, forEmergencyCall, isSelectedPhoneForEmergencyCall,
                forceApply);
    }

    @Override
    public Connection startConference(String[] participantsToDial, DialArgs dialArgs)
            throws CallStateException {
         ImsDialArgs.Builder imsDialArgsBuilder;
         imsDialArgsBuilder = ImsDialArgs.Builder.from(dialArgs);
         return mCT.startConference(participantsToDial, imsDialArgsBuilder.build());
    }

    @Override
    public Connection dial(String dialString, DialArgs dialArgs,
            Consumer chosenPhoneConsumer) throws CallStateException {
        chosenPhoneConsumer.accept(this);
        return dialInternal(dialString, dialArgs, null);
    }

    private Connection dialInternal(String dialString, DialArgs dialArgs,
                                    ResultReceiver wrappedCallback)
            throws CallStateException {

        mLastDialString = dialString;

        // Need to make sure dialString gets parsed properly
        String newDialString = PhoneNumberUtils.stripSeparators(dialString);

        // handle in-call MMI first if applicable
        if (handleInCallMmiCommands(newDialString)) {
            return null;
        }

        ImsDialArgs.Builder imsDialArgsBuilder;
        imsDialArgsBuilder = ImsDialArgs.Builder.from(dialArgs);
        // Get the CLIR info if needed
        imsDialArgsBuilder.setClirMode(mCT.getClirMode());

        if (mDefaultPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
            return mCT.dial(dialString, imsDialArgsBuilder.build());
        }

        // Only look at the Network portion for mmi
        String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);
        ImsPhoneMmiCode mmi =
                ImsPhoneMmiCode.newFromDialString(networkPortion, this, wrappedCallback);
        if (DBG) logd("dialInternal: dialing w/ mmi '" + mmi + "'...");

        if (mmi == null) {
            return mCT.dial(dialString, imsDialArgsBuilder.build());
        } else if (mmi.isTemporaryModeCLIR()) {
            imsDialArgsBuilder.setClirMode(mmi.getCLIRMode());
            return mCT.dial(mmi.getDialingNumber(), imsDialArgsBuilder.build());
        } else if (!mmi.isSupportedOverImsPhone()) {
            // If the mmi is not supported by IMS service,
            // try to initiate dialing with default phone
            // Note: This code is never reached; there is a bug in isSupportedOverImsPhone which
            // causes it to return true even though the "processCode" method ultimately throws the
            // exception.
            logi("dialInternal: USSD not supported by IMS; fallback to CS.");
            throw new CallStateException(CS_FALLBACK);
        } else {
            mPendingMMIs.add(mmi);
            mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));

            try {
                mmi.processCode();
            } catch (CallStateException cse) {
                if (CS_FALLBACK.equals(cse.getMessage())) {
                    logi("dialInternal: fallback to GSM required.");
                    // Make sure we remove from the list of pending MMIs since it will handover to
                    // GSM.
                    mPendingMMIs.remove(mmi);
                    throw cse;
                }
            }

            return null;
        }
    }

    @Override
    public void
    sendDtmf(char c) {
        if (!PhoneNumberUtils.is12Key(c)) {
            loge("sendDtmf called with invalid character '" + c + "'");
        } else {
            if (mCT.getState() ==  PhoneConstants.State.OFFHOOK) {
                mCT.sendDtmf(c, null);
            }
        }
    }

    @Override
    public void
    startDtmf(char c) {
        if (!(PhoneNumberUtils.is12Key(c) || (c >= 'A' && c <= 'D'))) {
            loge("startDtmf called with invalid character '" + c + "'");
        } else {
            mCT.startDtmf(c);
        }
    }

    @Override
    public void
    stopDtmf() {
        mCT.stopDtmf();
    }

    public void notifyIncomingRing() {
        if (DBG) logd("notifyIncomingRing");
        AsyncResult ar = new AsyncResult(null, null, null);
        sendMessage(obtainMessage(EVENT_CALL_RING, ar));
    }

    @Override
    public void setMute(boolean muted) {
        mCT.setMute(muted);
    }

    @Override
    public void setTTYMode(int ttyMode, Message onComplete) {
        mCT.setTtyMode(ttyMode);
    }

    @Override
    public void setUiTTYMode(int uiTtyMode, Message onComplete) {
        mCT.setUiTTYMode(uiTtyMode, onComplete);
    }

    @Override
    public boolean getMute() {
        return mCT.getMute();
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    public PhoneConstants.State getState() {
        return mCT.getState();
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private boolean isValidCommandInterfaceCFReason (int commandInterfaceCFReason) {
        switch (commandInterfaceCFReason) {
        case CF_REASON_UNCONDITIONAL:
        case CF_REASON_BUSY:
        case CF_REASON_NO_REPLY:
        case CF_REASON_NOT_REACHABLE:
        case CF_REASON_ALL:
        case CF_REASON_ALL_CONDITIONAL:
            return true;
        default:
            return false;
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private boolean isValidCommandInterfaceCFAction (int commandInterfaceCFAction) {
        switch (commandInterfaceCFAction) {
        case CF_ACTION_DISABLE:
        case CF_ACTION_ENABLE:
        case CF_ACTION_REGISTRATION:
        case CF_ACTION_ERASURE:
            return true;
        default:
            return false;
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private  boolean isCfEnable(int action) {
        return (action == CF_ACTION_ENABLE) || (action == CF_ACTION_REGISTRATION);
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private int getConditionFromCFReason(int reason) {
        switch(reason) {
            case CF_REASON_UNCONDITIONAL: return ImsUtInterface.CDIV_CF_UNCONDITIONAL;
            case CF_REASON_BUSY: return ImsUtInterface.CDIV_CF_BUSY;
            case CF_REASON_NO_REPLY: return ImsUtInterface.CDIV_CF_NO_REPLY;
            case CF_REASON_NOT_REACHABLE: return ImsUtInterface.CDIV_CF_NOT_REACHABLE;
            case CF_REASON_ALL: return ImsUtInterface.CDIV_CF_ALL;
            case CF_REASON_ALL_CONDITIONAL: return ImsUtInterface.CDIV_CF_ALL_CONDITIONAL;
            default:
                break;
        }

        return ImsUtInterface.INVALID;
    }

    private int getCFReasonFromCondition(int condition) {
        switch(condition) {
            case ImsUtInterface.CDIV_CF_UNCONDITIONAL: return CF_REASON_UNCONDITIONAL;
            case ImsUtInterface.CDIV_CF_BUSY: return CF_REASON_BUSY;
            case ImsUtInterface.CDIV_CF_NO_REPLY: return CF_REASON_NO_REPLY;
            case ImsUtInterface.CDIV_CF_NOT_REACHABLE: return CF_REASON_NOT_REACHABLE;
            case ImsUtInterface.CDIV_CF_ALL: return CF_REASON_ALL;
            case ImsUtInterface.CDIV_CF_ALL_CONDITIONAL: return CF_REASON_ALL_CONDITIONAL;
            default:
                break;
        }

        return CF_REASON_NOT_REACHABLE;
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private int getActionFromCFAction(int action) {
        switch(action) {
            case CF_ACTION_DISABLE: return ImsUtInterface.ACTION_DEACTIVATION;
            case CF_ACTION_ENABLE: return ImsUtInterface.ACTION_ACTIVATION;
            case CF_ACTION_ERASURE: return ImsUtInterface.ACTION_ERASURE;
            case CF_ACTION_REGISTRATION: return ImsUtInterface.ACTION_REGISTRATION;
            default:
                break;
        }

        return ImsUtInterface.INVALID;
    }

    @Override
    public void getOutgoingCallerIdDisplay(Message onComplete) {
        if (DBG) logd("getCLIR");
        Message resp;
        SS ss = new SS(onComplete);
        resp = obtainMessage(EVENT_GET_CLIR_DONE, ss);

        try {
            ImsUtInterface ut = mCT.getUtInterface();
            ut.queryCLIR(resp);
        } catch (ImsException e) {
            sendErrorResponse(onComplete, e);
        }
    }

    @Override
    public void setOutgoingCallerIdDisplay(int clirMode, Message onComplete) {
        if (DBG) logd("setCLIR action= " + clirMode);
        Message resp;
        // Packing CLIR value in the message. This will be required for
        // SharedPreference caching, if the message comes back as part of
        // a success response.
        SS ss = new SS(clirMode, onComplete);
        resp = obtainMessage(EVENT_SET_CLIR_DONE, ss);
        try {
            ImsUtInterface ut = mCT.getUtInterface();
            ut.updateCLIR(clirMode, resp);
        } catch (ImsException e) {
            sendErrorResponse(onComplete, e);
        }
    }

    @Override
    public void queryCLIP(Message onComplete) {
        Message resp;
        SS ss = new SS(onComplete);
        resp = obtainMessage(EVENT_GET_CLIP_DONE, ss);

        try {
            Rlog.d(LOG_TAG, "ut.queryCLIP");
            ImsUtInterface ut = mCT.getUtInterface();
            ut.queryCLIP(resp);
        } catch (ImsException e) {
            sendErrorResponse(onComplete, e);
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    public void getCallForwardingOption(int commandInterfaceCFReason,
            Message onComplete) {
        getCallForwardingOption(commandInterfaceCFReason,
                SERVICE_CLASS_VOICE, onComplete);
    }

    @Override
    public void getCallForwardingOption(int commandInterfaceCFReason, int serviceClass,
            Message onComplete) {
        if (DBG) logd("getCallForwardingOption reason=" + commandInterfaceCFReason);
        if (isValidCommandInterfaceCFReason(commandInterfaceCFReason)) {
            if (DBG) logd("requesting call forwarding query.");
            Message resp;
            SS ss = new SS(commandInterfaceCFReason, serviceClass, onComplete);
            resp = obtainMessage(EVENT_GET_CALL_FORWARD_DONE, ss);

            try {
                ImsUtInterface ut = mCT.getUtInterface();
                ut.queryCallForward(getConditionFromCFReason(commandInterfaceCFReason), null, resp);
            } catch (ImsException e) {
                sendErrorResponse(onComplete, e);
            }
        } else if (onComplete != null) {
            sendErrorResponse(onComplete);
        }
    }

    @Override
    public void setCallForwardingOption(int commandInterfaceCFAction,
            int commandInterfaceCFReason,
            String dialingNumber,
            int timerSeconds,
            Message onComplete) {
        setCallForwardingOption(commandInterfaceCFAction, commandInterfaceCFReason, dialingNumber,
                CommandsInterface.SERVICE_CLASS_VOICE, timerSeconds, onComplete);
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    public void setCallForwardingOption(int commandInterfaceCFAction,
            int commandInterfaceCFReason,
            String dialingNumber,
            int serviceClass,
            int timerSeconds,
            Message onComplete) {
        if (DBG) {
            logd("setCallForwardingOption action=" + commandInterfaceCFAction
                    + ", reason=" + commandInterfaceCFReason + " serviceClass=" + serviceClass);
        }
        if ((isValidCommandInterfaceCFAction(commandInterfaceCFAction)) &&
                (isValidCommandInterfaceCFReason(commandInterfaceCFReason))) {
            Message resp;
            SS ss = new SS(commandInterfaceCFAction, commandInterfaceCFReason,
                    dialingNumber, serviceClass, timerSeconds, onComplete);
            resp = obtainMessage(EVENT_SET_CALL_FORWARD_DONE, ss);

            try {
                ImsUtInterface ut = mCT.getUtInterface();
                ut.updateCallForward(getActionFromCFAction(commandInterfaceCFAction),
                        getConditionFromCFReason(commandInterfaceCFReason),
                        dialingNumber,
                        serviceClass,
                        timerSeconds,
                        resp);
            } catch (ImsException e) {
                sendErrorResponse(onComplete, e);
            }
        } else if (onComplete != null) {
            sendErrorResponse(onComplete);
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    public void getCallWaiting(Message onComplete) {
        if (DBG) logd("getCallWaiting");
        Message resp;
        SS ss = new SS(onComplete);
        resp = obtainMessage(EVENT_GET_CALL_WAITING_DONE, ss);

        try {
            ImsUtInterface ut = mCT.getUtInterface();
            ut.queryCallWaiting(resp);
        } catch (ImsException e) {
            sendErrorResponse(onComplete, e);
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    public void setCallWaiting(boolean enable, Message onComplete) {
        int serviceClass = CommandsInterface.SERVICE_CLASS_VOICE;
        CarrierConfigManager configManager = (CarrierConfigManager)
                getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
        PersistableBundle b = configManager.getConfigForSubId(getSubId());
        if (b != null) {
            serviceClass = b.getInt(CarrierConfigManager.KEY_CALL_WAITING_SERVICE_CLASS_INT,
                    CommandsInterface.SERVICE_CLASS_VOICE);
        }
        setCallWaiting(enable, serviceClass, onComplete);
    }

    public void setCallWaiting(boolean enable, int serviceClass, Message onComplete) {
        if (DBG) logd("setCallWaiting enable=" + enable);
        Message resp;
        SS ss = new SS(enable, serviceClass, onComplete);
        resp = obtainMessage(EVENT_SET_CALL_WAITING_DONE, ss);

        try {
            ImsUtInterface ut = mCT.getUtInterface();
            ut.updateCallWaiting(enable, serviceClass, resp);
        } catch (ImsException e) {
            sendErrorResponse(onComplete, e);
        }
    }

    private int getCBTypeFromFacility(String facility) {
        if (CB_FACILITY_BAOC.equals(facility)) {
            return ImsUtImplBase.CALL_BARRING_ALL_OUTGOING;
        } else if (CB_FACILITY_BAOIC.equals(facility)) {
            return ImsUtImplBase.CALL_BARRING_OUTGOING_INTL;
        } else if (CB_FACILITY_BAOICxH.equals(facility)) {
            return ImsUtImplBase.CALL_BARRING_OUTGOING_INTL_EXCL_HOME;
        } else if (CB_FACILITY_BAIC.equals(facility)) {
            return ImsUtImplBase.CALL_BARRING_ALL_INCOMING;
        } else if (CB_FACILITY_BAICr.equals(facility)) {
            return ImsUtImplBase.CALL_BLOCKING_INCOMING_WHEN_ROAMING;
        } else if (CB_FACILITY_BA_ALL.equals(facility)) {
            return ImsUtImplBase.CALL_BARRING_ALL;
        } else if (CB_FACILITY_BA_MO.equals(facility)) {
            return ImsUtImplBase.CALL_BARRING_OUTGOING_ALL_SERVICES;
        } else if (CB_FACILITY_BA_MT.equals(facility)) {
            return ImsUtImplBase.CALL_BARRING_INCOMING_ALL_SERVICES;
        } else if (CB_FACILITY_BIC_ACR.equals(facility)) {
            return ImsUtImplBase.CALL_BARRING_ANONYMOUS_INCOMING;
        }

        return 0;
    }

    public void getCallBarring(String facility, Message onComplete) {
        getCallBarring(facility, onComplete, CommandsInterface.SERVICE_CLASS_VOICE);
    }

    public void getCallBarring(String facility, Message onComplete, int serviceClass) {
        getCallBarring(facility, "", onComplete, serviceClass);
    }

    @Override
    public void getCallBarring(String facility, String password, Message onComplete,
            int serviceClass) {
        if (DBG) logd("getCallBarring facility=" + facility + ", serviceClass = " + serviceClass);
        Message resp;
        SS ss = new SS(facility, password, serviceClass, onComplete);
        resp = obtainMessage(EVENT_GET_CALL_BARRING_DONE, ss);

        try {
            ImsUtInterface ut = mCT.getUtInterface();
            // password is not required with Ut interface
            ut.queryCallBarring(getCBTypeFromFacility(facility), resp, serviceClass);
        } catch (ImsException e) {
            sendErrorResponse(onComplete, e);
        }
    }

    public void setCallBarring(String facility, boolean lockState, String password,
            Message onComplete) {
        setCallBarring(facility, lockState, password, onComplete,
                CommandsInterface.SERVICE_CLASS_VOICE);
    }

    @Override
    public void setCallBarring(String facility, boolean lockState, String password,
            Message onComplete, int serviceClass) {
        if (DBG) {
            logd("setCallBarring facility=" + facility
                    + ", lockState=" + lockState + ", serviceClass = " + serviceClass);
        }
        Message resp;
        SS ss = new SS(facility, lockState, password, serviceClass, onComplete);
        resp = obtainMessage(EVENT_SET_CALL_BARRING_DONE, ss);

        int action;
        if (lockState) {
            action = CommandsInterface.CF_ACTION_ENABLE;
        }
        else {
            action = CommandsInterface.CF_ACTION_DISABLE;
        }

        try {
            ImsUtInterface ut = mCT.getUtInterface();
            ut.updateCallBarring(getCBTypeFromFacility(facility), action,
                    resp, null, serviceClass, password);
        } catch (ImsException e) {
            sendErrorResponse(onComplete, e);
        }
    }

    @Override
    public void sendUssdResponse(String ussdMessge) {
        logd("sendUssdResponse");
        ImsPhoneMmiCode mmi = ImsPhoneMmiCode.newFromUssdUserInput(ussdMessge, this);
        mPendingMMIs.add(mmi);
        mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
        mmi.sendUssd(ussdMessge);
    }

    public void sendUSSD(String ussdString, Message response) {
        Rlog.d(LOG_TAG, "sendUssd ussdString = " + ussdString);
        mLastDialString = ussdString;
        mCT.sendUSSD(ussdString, response);
    }

    @Override
    public void cancelUSSD(Message msg) {
        mCT.cancelUSSD(msg);
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private void sendErrorResponse(Message onComplete) {
        logd("sendErrorResponse");
        if (onComplete != null) {
            AsyncResult.forMessage(onComplete, null,
                    new CommandException(CommandException.Error.GENERIC_FAILURE));
            onComplete.sendToTarget();
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @VisibleForTesting
    public void sendErrorResponse(Message onComplete, Throwable e) {
        logd("sendErrorResponse");
        if (onComplete != null) {
            AsyncResult.forMessage(onComplete, null, getCommandException(e));
            onComplete.sendToTarget();
        }
    }

    private CommandException getCommandException(int code, String errorString) {
        logd("getCommandException code= " + code + ", errorString= " + errorString);
        CommandException.Error error = CommandException.Error.GENERIC_FAILURE;

        switch(code) {
            case ImsReasonInfo.CODE_UT_NOT_SUPPORTED:
                error = CommandException.Error.REQUEST_NOT_SUPPORTED;
                break;
            case ImsReasonInfo.CODE_UT_CB_PASSWORD_MISMATCH:
                error = CommandException.Error.PASSWORD_INCORRECT;
                break;
            case ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE:
                error = CommandException.Error.RADIO_NOT_AVAILABLE;
                break;
            case ImsReasonInfo.CODE_FDN_BLOCKED:
                error = CommandException.Error.FDN_CHECK_FAILURE;
                break;
            case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_DIAL:
                error = CommandException.Error.SS_MODIFIED_TO_DIAL;
                break;
            case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_USSD:
                error = CommandException.Error.SS_MODIFIED_TO_USSD;
                break;
            case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_SS:
                error = CommandException.Error.SS_MODIFIED_TO_SS;
                break;
            case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_DIAL_VIDEO:
                error = CommandException.Error.SS_MODIFIED_TO_DIAL_VIDEO;
                break;
            default:
                break;
        }

        return new CommandException(error, errorString);
    }

    private CommandException getCommandException(Throwable e) {
        CommandException ex = null;

        if (e instanceof ImsException) {
            ex = getCommandException(((ImsException)e).getCode(), e.getMessage());
        } else {
            logd("getCommandException generic failure");
            ex = new CommandException(CommandException.Error.GENERIC_FAILURE);
        }
        return ex;
    }

    private void
    onNetworkInitiatedUssd(ImsPhoneMmiCode mmi) {
        logd("onNetworkInitiatedUssd");
        mMmiCompleteRegistrants.notifyRegistrants(
            new AsyncResult(null, mmi, null));
    }

    /* package */
    void onIncomingUSSD(int ussdMode, String ussdMessage) {
        if (DBG) logd("onIncomingUSSD ussdMode=" + ussdMode);

        boolean isUssdError;
        boolean isUssdRequest;

        isUssdRequest
            = (ussdMode == CommandsInterface.USSD_MODE_REQUEST);

        isUssdError
            = (ussdMode != CommandsInterface.USSD_MODE_NOTIFY
                && ussdMode != CommandsInterface.USSD_MODE_REQUEST);

        ImsPhoneMmiCode found = null;
        for (int i = 0, s = mPendingMMIs.size() ; i < s; i++) {
            if(mPendingMMIs.get(i).isPendingUSSD()) {
                found = mPendingMMIs.get(i);
                break;
            }
        }

        if (found != null) {
            // Complete pending USSD
            if (isUssdError) {
                found.onUssdFinishedError();
            } else {
                found.onUssdFinished(ussdMessage, isUssdRequest);
            }
        } else if (!isUssdError && !TextUtils.isEmpty(ussdMessage)) {
                // pending USSD not found
                // The network may initiate its own USSD request

                // ignore everything that isnt a Notify or a Request
                // also, discard if there is no message to present
                ImsPhoneMmiCode mmi;
                mmi = ImsPhoneMmiCode.newNetworkInitiatedUssd(ussdMessage,
                        isUssdRequest,
                        this);
                onNetworkInitiatedUssd(mmi);
        } else if (isUssdError) {
            ImsPhoneMmiCode mmi;
            mmi = ImsPhoneMmiCode.newNetworkInitiatedUssd(ussdMessage,
                    true,
                    this);
            mmi.onUssdFinishedError();
        }
    }

    /**
     * Removes the given MMI from the pending list and notifies
     * registrants that it is complete.
     * @param mmi MMI that is done
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void onMMIDone(ImsPhoneMmiCode mmi) {
        /* Only notify complete if it's on the pending list.
         * Otherwise, it's already been handled (eg, previously canceled).
         * The exception is cancellation of an incoming USSD-REQUEST, which is
         * not on the list.
         */
        logd("onMMIDone: mmi=" + mmi);
        if (mPendingMMIs.remove(mmi) || mmi.isUssdRequest() || mmi.isSsInfo()) {
            ResultReceiver receiverCallback = mmi.getUssdCallbackReceiver();
            if (receiverCallback != null) {
                int returnCode = (mmi.getState() ==  MmiCode.State.COMPLETE) ?
                        TelephonyManager.USSD_RETURN_SUCCESS : TelephonyManager.USSD_RETURN_FAILURE;
                sendUssdResponse(mmi.getDialString(), mmi.getMessage(), returnCode,
                        receiverCallback );
            } else {
                logv("onMMIDone: notifyRegistrants");
                mMmiCompleteRegistrants.notifyRegistrants(
                    new AsyncResult(null, mmi, null));
            }
        }
    }

    @Override
    public ArrayList getHandoverConnection() {
        ArrayList connList = new ArrayList();
        // Add all foreground call connections
        connList.addAll(getForegroundCall().getConnections());
        // Add all background call connections
        connList.addAll(getBackgroundCall().getConnections());
        // Add all background call connections
        connList.addAll(getRingingCall().getConnections());
        if (connList.size() > 0) {
            return connList;
        } else {
            return null;
        }
    }

    @Override
    public void notifySrvccState(Call.SrvccState state) {
        mCT.notifySrvccState(state);
    }

    /* package */ void
    initiateSilentRedial() {
        initiateSilentRedial(false, EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED);
    }

    /* package */ void
    initiateSilentRedial(boolean isEmergency, int eccCategory) {
        DialArgs dialArgs = new DialArgs.Builder()
                                        .setIsEmergency(isEmergency)
                                        .setEccCategory(eccCategory)
                                        .build();
        int cause = CallFailCause.LOCAL_CALL_CS_RETRY_REQUIRED;
        AsyncResult ar = new AsyncResult(null,
                                         new SilentRedialParam(mLastDialString, cause, dialArgs),
                                         null);
        if (ar != null) {
            // There is a race condition that can happen in some cases:
            // (Main thread) dial start
            // (Binder Thread) onCallSessionFailed
            // (Binder Thread) schedule a redial for CS on the main thread
            // (Main Thread) dial finish
            // (Main Thread) schedule to associate ImsPhoneConnection with
            //               GsmConnection on the main thread
            // If scheduling the CS redial occurs before the command to schedule the
            // ImsPhoneConnection to be  associated with the GsmConnection, the CS redial will occur
            // before GsmConnection has had callbacks to ImsPhone correctly updated. This will cause
            // Callbacks back to GsmCdmaPhone to never be set up correctly and we will lose track of
            // the instance.
            // Instead, schedule this redial to happen on the main thread, so that we know dial has
            // finished before scheduling a redial:
            // (Main thread) dial start
            // (Binder Thread) onCallSessionFailed -> move notify registrants to main thread
            // (Main Thread) dial finish
            // (Main Thread) schedule on main thread to associate ImsPhoneConnection with
            //               GsmConnection
            // (Main Thread) schedule a redial for CS
            mContext.getMainExecutor().execute(() -> {
                logd("initiateSilentRedial: notifying registrants, isEmergency=" + isEmergency
                        + ", eccCategory=" + eccCategory);
                mSilentRedialRegistrants.notifyRegistrants(ar);
            });
        }
    }

    @Override
    public void registerForSilentRedial(Handler h, int what, Object obj) {
        mSilentRedialRegistrants.addUnique(h, what, obj);
    }

    @Override
    public void unregisterForSilentRedial(Handler h) {
        mSilentRedialRegistrants.remove(h);
    }

    @Override
    public void registerForSuppServiceNotification(Handler h, int what, Object obj) {
        mSsnRegistrants.addUnique(h, what, obj);
    }

    @Override
    public void unregisterForSuppServiceNotification(Handler h) {
        mSsnRegistrants.remove(h);
    }

    @Override
    public int getSubId() {
        return mDefaultPhone.getSubId();
    }

    @Override
    public int getPhoneId() {
        return mDefaultPhone.getPhoneId();
    }

    private CallForwardInfo getCallForwardInfo(ImsCallForwardInfo info) {
        CallForwardInfo cfInfo = new CallForwardInfo();
        cfInfo.status = info.getStatus();
        cfInfo.reason = getCFReasonFromCondition(info.getCondition());
        cfInfo.serviceClass = SERVICE_CLASS_VOICE;
        cfInfo.toa = info.getToA();
        cfInfo.number = info.getNumber();
        cfInfo.timeSeconds = info.getTimeSeconds();
        return cfInfo;
    }

    @Override
    public String getLine1Number() {
        return mDefaultPhone.getLine1Number();
    }

    /**
     * Used to Convert ImsCallForwardInfo[] to CallForwardInfo[].
     * Update received call forward status to default IccRecords.
     */
    public CallForwardInfo[] handleCfQueryResult(ImsCallForwardInfo[] infos) {
        CallForwardInfo[] cfInfos = null;

        if (infos != null && infos.length != 0) {
            cfInfos = new CallForwardInfo[infos.length];
        }

        if (infos == null || infos.length == 0) {
            // Assume the default is not active
            // Set unconditional CFF in SIM to false
            setVoiceCallForwardingFlag(getIccRecords(), 1, false, null);
        } else {
            for (int i = 0, s = infos.length; i < s; i++) {
                if (infos[i].getCondition() == ImsUtInterface.CDIV_CF_UNCONDITIONAL) {
                    setVoiceCallForwardingFlag(getIccRecords(), 1, (infos[i].getStatus() == 1),
                        infos[i].getNumber());
                }
                cfInfos[i] = getCallForwardInfo(infos[i]);
            }
        }

        return cfInfos;
    }

    private int[] handleCbQueryResult(ImsSsInfo[] infos) {
        int[] cbInfos = new int[1];
        cbInfos[0] = SERVICE_CLASS_NONE;

        if (infos[0].getStatus() == 1) {
            cbInfos[0] = SERVICE_CLASS_VOICE;
        }

        return cbInfos;
    }

    private int[] handleCwQueryResult(ImsSsInfo[] infos) {
        int[] cwInfos = new int[2];
        cwInfos[0] = 0;

        if (infos[0].getStatus() == 1) {
            cwInfos[0] = 1;
            cwInfos[1] = SERVICE_CLASS_VOICE;
        }

        return cwInfos;
    }

    private void
    sendResponse(Message onComplete, Object result, Throwable e) {
        if (onComplete != null) {
            CommandException ex = null;
            if (e != null) {
                ex = getCommandException(e);
            }
            AsyncResult.forMessage(onComplete, result, ex);
            onComplete.sendToTarget();
        }
    }

    private void updateDataServiceState() {
        if (mSS != null && mDefaultPhone.getServiceStateTracker() != null
                && mDefaultPhone.getServiceStateTracker().mSS != null) {
            ServiceState ss = mDefaultPhone.getServiceStateTracker().mSS;
            mSS.setDataRegState(ss.getDataRegistrationState());
            List nriList =
                    ss.getNetworkRegistrationInfoListForDomain(NetworkRegistrationInfo.DOMAIN_PS);
            for (NetworkRegistrationInfo nri : nriList) {
                mSS.addNetworkRegistrationInfo(nri);
            }

            mSS.setIwlanPreferred(ss.isIwlanPreferred());
            logd("updateDataServiceState: defSs = " + ss + " imsSs = " + mSS);
        }
    }

    boolean isCsRetryException(Throwable e) {
        if ((e != null) && (e instanceof ImsException)
                && (((ImsException)e).getCode()
                    == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED)) {
            return true;
        }
        return false;
    }

    private Bundle setCsfbBundle(boolean isCsRetry) {
        Bundle b = new Bundle();
        b.putBoolean(CS_FALLBACK_SS, isCsRetry);
        return b;
    }

    private void sendResponseOrRetryOnCsfbSs(SS ss, int what, Throwable e, Object obj) {
        if (!isCsRetryException(e)) {
            sendResponse(ss.mOnComplete, obj, e);
            return;
        }

        Rlog.d(LOG_TAG, "Try CSFB: " + what);
        ss.mOnComplete.setData(setCsfbBundle(true));

        switch (what) {
            case EVENT_GET_CALL_FORWARD_DONE:
                mDefaultPhone.getCallForwardingOption(ss.mCfReason,
                                                      ss.mServiceClass,
                                                      ss.mOnComplete);
                break;
            case EVENT_SET_CALL_FORWARD_DONE:
                mDefaultPhone.setCallForwardingOption(ss.mCfAction,
                                                      ss.mCfReason,
                                                      ss.mDialingNumber,
                                                      ss.mServiceClass,
                                                      ss.mTimerSeconds,
                                                      ss.mOnComplete);
                break;
            case EVENT_GET_CALL_BARRING_DONE:
                mDefaultPhone.getCallBarring(ss.mFacility,
                                             ss.mPassword,
                                             ss.mOnComplete,
                                             ss.mServiceClass);
                break;
            case EVENT_SET_CALL_BARRING_DONE:
                mDefaultPhone.setCallBarring(ss.mFacility,
                                             ss.mLockState,
                                             ss.mPassword,
                                             ss.mOnComplete,
                                             ss.mServiceClass);
                break;
            case EVENT_GET_CALL_WAITING_DONE:
                mDefaultPhone.getCallWaiting(ss.mOnComplete);
                break;
            case EVENT_SET_CALL_WAITING_DONE:
                mDefaultPhone.setCallWaiting(ss.mEnable,
                                             ss.mServiceClass,
                                             ss.mOnComplete);
                break;
            case EVENT_GET_CLIR_DONE:
                mDefaultPhone.getOutgoingCallerIdDisplay(ss.mOnComplete);
                break;
            case EVENT_SET_CLIR_DONE:
                mDefaultPhone.setOutgoingCallerIdDisplay(ss.mClirMode, ss.mOnComplete);
                break;
            case EVENT_GET_CLIP_DONE:
                mDefaultPhone.queryCLIP(ss.mOnComplete);
                break;
            default:
                break;
        }
    }

    @Override
    public void handleMessage(Message msg) {
        AsyncResult ar = (AsyncResult) msg.obj;
        Message onComplete;
        SS ss = null;
        if (ar != null && ar.userObj instanceof SS) {
            ss = (SS) ar.userObj;
        }

        if (DBG) logd("handleMessage what=" + msg.what);
        switch (msg.what) {
            case EVENT_SET_CALL_FORWARD_DONE:
                if (ar.exception == null && ss != null &&
                    (ss.mCfReason == CF_REASON_UNCONDITIONAL)) {
                    setVoiceCallForwardingFlag(getIccRecords(), 1, isCfEnable(ss.mCfAction),
                                               ss.mDialingNumber);
                }
                if (ss != null) {
                    sendResponseOrRetryOnCsfbSs(ss, msg.what, ar.exception, null);
                }
                break;

            case EVENT_GET_CALL_FORWARD_DONE:
                CallForwardInfo[] cfInfos = null;
                if (ar.exception == null) {
                    cfInfos = handleCfQueryResult((ImsCallForwardInfo[])ar.result);
                }
                if (ss != null) {
                    sendResponseOrRetryOnCsfbSs(ss, msg.what, ar.exception, cfInfos);
                }
                break;

            case EVENT_GET_CALL_BARRING_DONE:
            case EVENT_GET_CALL_WAITING_DONE:
                int[] ssInfos = null;
                if (ar.exception == null) {
                    if (msg.what == EVENT_GET_CALL_BARRING_DONE) {
                        ssInfos = handleCbQueryResult((ImsSsInfo[])ar.result);
                    } else if (msg.what == EVENT_GET_CALL_WAITING_DONE) {
                        ssInfos = handleCwQueryResult((ImsSsInfo[])ar.result);
                    }
                }
                if (ss != null) {
                    sendResponseOrRetryOnCsfbSs(ss, msg.what, ar.exception, ssInfos);
                }
                break;

            case EVENT_GET_CLIR_DONE:
                ImsSsInfo ssInfo = (ImsSsInfo) ar.result;
                int[] clirInfo = null;
                if (ssInfo != null) {
                    // Unfortunately callers still use the old {n,m} format of ImsSsInfo, so return
                    // that for compatibility
                    clirInfo = ssInfo.getCompatArray(ImsSsData.SS_CLIR);
                }
                if (ss != null) {
                    sendResponseOrRetryOnCsfbSs(ss, msg.what, ar.exception, clirInfo);
                }
                break;

            case EVENT_GET_CLIP_DONE:
                ImsSsInfo ssInfoResp = null;
                if (ar.exception == null && ar.result instanceof ImsSsInfo) {
                    ssInfoResp = (ImsSsInfo) ar.result;
                }
                if (ss != null) {
                    sendResponseOrRetryOnCsfbSs(ss, msg.what, ar.exception, ssInfoResp);
                }
                break;

            case EVENT_SET_CLIR_DONE:
                if (ar.exception == null) {
                    if (ss != null) {
                        saveClirSetting(ss.mClirMode);
                    }
                }
                 // (Intentional fallthrough)
            case EVENT_SET_CALL_BARRING_DONE:
            case EVENT_SET_CALL_WAITING_DONE:
                if (ss != null) {
                    sendResponseOrRetryOnCsfbSs(ss, msg.what, ar.exception, null);
                }
                break;

            case EVENT_DEFAULT_PHONE_DATA_STATE_CHANGED:
                if (DBG) logd("EVENT_DEFAULT_PHONE_DATA_STATE_CHANGED");
                updateDataServiceState();
                break;

            case EVENT_SERVICE_STATE_CHANGED:
                if (VDBG) logd("EVENT_SERVICE_STATE_CHANGED");
                ar = (AsyncResult) msg.obj;
                ServiceState newServiceState = (ServiceState) ar.result;
                updateRoamingState(newServiceState);
                break;
            case EVENT_VOICE_CALL_ENDED:
                if (DBG) logd("Voice call ended. Handle pending updateRoamingState.");
                mCT.unregisterForVoiceCallEnded(this);
                // Get the current unmodified ServiceState from the tracker, as it has more info
                // about the cell roaming state.
                ServiceStateTracker sst = getDefaultPhone().getServiceStateTracker();
                if (sst != null) {
                    updateRoamingState(sst.mSS);
                }
                break;
            case EVENT_INITIATE_VOLTE_SILENT_REDIAL: {
                // This is a CS -> IMS redial
                if (VDBG) logd("EVENT_INITIATE_VOLTE_SILENT_REDIAL");
                ar = (AsyncResult) msg.obj;
                if (ar.exception == null && ar.result != null) {
                    SilentRedialParam result = (SilentRedialParam) ar.result;
                    String dialString = result.dialString;
                    int causeCode = result.causeCode;
                    DialArgs dialArgs = result.dialArgs;
                    if (VDBG) logd("dialString=" + dialString + " causeCode=" + causeCode);

                    try {
                        Connection cn = dial(dialString,
                                updateDialArgsForVolteSilentRedial(dialArgs, causeCode));
                        // The GSM/CDMA Connection that is owned by the GsmCdmaPhone is currently
                        // the one with a callback registered to TelephonyConnection. Notify the
                        // redial happened over that Phone so that it can be replaced with the
                        // new ImsPhoneConnection.
                        Rlog.d(LOG_TAG, "Notify volte redial connection changed cn: " + cn);
                        if (mDefaultPhone != null) {
                            // don't care it is null or not.
                            mDefaultPhone.notifyRedialConnectionChanged(cn);
                        }
                    } catch (CallStateException e) {
                        Rlog.e(LOG_TAG, "volte silent redial failed: " + e);
                        if (mDefaultPhone != null) {
                            mDefaultPhone.notifyRedialConnectionChanged(null);
                        }
                    }
                } else {
                    if (VDBG) logd("EVENT_INITIATE_VOLTE_SILENT_REDIAL" +
                                   " has exception or empty result");
                }
                break;
            }

            default:
                super.handleMessage(msg);
                break;
        }
    }

    /**
     * Listen to the IMS ECBM state change
     */
    private ImsEcbmStateListener mImsEcbmStateListener =
            new ImsEcbmStateListener() {
                @Override
                public void onECBMEntered() {
                    if (DBG) logd("onECBMEntered");
                    handleEnterEmergencyCallbackMode();
                }

                @Override
                public void onECBMExited() {
                    if (DBG) logd("onECBMExited");
                    handleExitEmergencyCallbackMode();
                }
            };

    @VisibleForTesting
    public ImsEcbmStateListener getImsEcbmStateListener() {
        return mImsEcbmStateListener;
    }

    @Override
    public boolean isInEmergencyCall() {
        return mCT.isInEmergencyCall();
    }

    private void sendEmergencyCallbackModeChange() {
        // Send an Intent
        Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
        intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, isInEcm());
        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, getPhoneId());
        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
        if (DBG) logd("sendEmergencyCallbackModeChange: isInEcm=" + isInEcm());
    }

    @Override
    public void exitEmergencyCallbackMode() {
        if (mWakeLock.isHeld()) {
            mWakeLock.release();
        }
        if (DBG) logd("exitEmergencyCallbackMode()");

        // Send a message which will invoke handleExitEmergencyCallbackMode
        ImsEcbm ecbm;
        try {
            ecbm = mCT.getEcbmInterface();
            ecbm.exitEmergencyCallbackMode();
        } catch (ImsException e) {
            e.printStackTrace();
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private void handleEnterEmergencyCallbackMode() {
        if (DBG) logd("handleEnterEmergencyCallbackMode,mIsPhoneInEcmState= " + isInEcm());
        // if phone is not in Ecm mode, and it's changed to Ecm mode
        if (!isInEcm()) {
            setIsInEcm(true);
            // notify change
            sendEmergencyCallbackModeChange();
            ((GsmCdmaPhone) mDefaultPhone).notifyEmergencyCallRegistrants(true);

            // Post this runnable so we will automatically exit
            // if no one invokes exitEmergencyCallbackMode() directly.
            long delayInMillis = TelephonyProperties.ecm_exit_timer()
                    .orElse(DEFAULT_ECM_EXIT_TIMER_VALUE);
            postDelayed(mExitEcmRunnable, delayInMillis);
            // We don't want to go to sleep while in Ecm
            mWakeLock.acquire();
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    protected void handleExitEmergencyCallbackMode() {
        if (DBG) logd("handleExitEmergencyCallbackMode: mIsPhoneInEcmState = " + isInEcm());

        if (isInEcm()) {
            setIsInEcm(false);
        }

        // Remove pending exit Ecm runnable, if any
        removeCallbacks(mExitEcmRunnable);

        if (mEcmExitRespRegistrant != null) {
            mEcmExitRespRegistrant.notifyResult(Boolean.TRUE);
        }

        // release wakeLock
        if (mWakeLock.isHeld()) {
            mWakeLock.release();
        }

        // send an Intent
        sendEmergencyCallbackModeChange();
        ((GsmCdmaPhone) mDefaultPhone).notifyEmergencyCallRegistrants(false);
    }

    /**
     * Handle to cancel or restart Ecm timer in emergency call back mode if action is
     * CANCEL_ECM_TIMER, cancel Ecm timer and notify apps the timer is canceled; otherwise, restart
     * Ecm timer and notify apps the timer is restarted.
     */
    void handleTimerInEmergencyCallbackMode(int action) {
        switch (action) {
            case CANCEL_ECM_TIMER:
                removeCallbacks(mExitEcmRunnable);
                ((GsmCdmaPhone) mDefaultPhone).notifyEcbmTimerReset(Boolean.TRUE);
                setEcmCanceledForEmergency(true /*isCanceled*/);
                break;
            case RESTART_ECM_TIMER:
                long delayInMillis = TelephonyProperties.ecm_exit_timer()
                        .orElse(DEFAULT_ECM_EXIT_TIMER_VALUE);
                postDelayed(mExitEcmRunnable, delayInMillis);
                ((GsmCdmaPhone) mDefaultPhone).notifyEcbmTimerReset(Boolean.FALSE);
                setEcmCanceledForEmergency(false /*isCanceled*/);
                break;
            default:
                loge("handleTimerInEmergencyCallbackMode, unsupported action " + action);
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    public void setOnEcbModeExitResponse(Handler h, int what, Object obj) {
        mEcmExitRespRegistrant = new Registrant(h, what, obj);
    }

    @Override
    public void unsetOnEcbModeExitResponse(Handler h) {
        mEcmExitRespRegistrant.clear();
    }

    public void onFeatureCapabilityChanged() {
        mDefaultPhone.getServiceStateTracker().onImsCapabilityChanged();
    }

    @Override
    public boolean isImsCapabilityAvailable(int capability, int regTech) throws ImsException {
        return mCT.isImsCapabilityAvailable(capability, regTech);
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    public boolean isVolteEnabled() {
        return isVoiceOverCellularImsEnabled();
    }

    @Override
    public boolean isVoiceOverCellularImsEnabled() {
        return mCT.isVoiceOverCellularImsEnabled();
    }

    @Override
    public boolean isWifiCallingEnabled() {
        return mCT.isVowifiEnabled();
    }

    @Override
    public boolean isVideoEnabled() {
        return mCT.isVideoCallEnabled();
    }

    @Override
    public int getImsRegistrationTech() {
        return mCT.getImsRegistrationTech();
    }

    @Override
    public void getImsRegistrationTech(Consumer callback) {
        mCT.getImsRegistrationTech(callback);
    }

    @Override
    public void getImsRegistrationState(Consumer callback) {
        callback.accept(mImsMmTelRegistrationHelper.getImsRegistrationState());
    }

    @Override
    public Phone getDefaultPhone() {
        return mDefaultPhone;
    }

    @Override
    public boolean isImsRegistered() {
        return mImsMmTelRegistrationHelper.isImsRegistered();
    }

    // Not used, but not removed due to UnsupportedAppUsage tag.
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void setImsRegistered(boolean isRegistered) {
        mImsMmTelRegistrationHelper.updateRegistrationState(
                isRegistered ? RegistrationManager.REGISTRATION_STATE_REGISTERED :
                        RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
    }

    @Override
    public void callEndCleanupHandOverCallIfAny() {
        mCT.callEndCleanupHandOverCallIfAny();
    }

    private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // Add notification only if alert was not shown by WfcSettings
            if (getResultCode() == Activity.RESULT_OK) {
                // Default result code (as passed to sendOrderedBroadcast)
                // means that intent was not received by WfcSettings.

                CharSequence title =
                        intent.getCharSequenceExtra(EXTRA_WFC_REGISTRATION_FAILURE_TITLE);
                CharSequence messageAlert =
                        intent.getCharSequenceExtra(EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE);
                CharSequence messageNotification =
                        intent.getCharSequenceExtra(EXTRA_KEY_NOTIFICATION_MESSAGE);

                Intent resultIntent = new Intent(Intent.ACTION_MAIN);
                // Note: If the classname below is ever removed, the call to
                // PendingIntent.getActivity should also specify FLAG_IMMUTABLE to ensure the
                // pending intent cannot be tampered with.
                resultIntent.setClassName("com.android.settings",
                        "com.android.settings.Settings$WifiCallingSettingsActivity");
                resultIntent.putExtra(EXTRA_KEY_ALERT_SHOW, true);
                resultIntent.putExtra(EXTRA_WFC_REGISTRATION_FAILURE_TITLE, title);
                resultIntent.putExtra(EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE, messageAlert);
                PendingIntent resultPendingIntent =
                        PendingIntent.getActivity(
                                mContext,
                                0,
                                resultIntent,
                                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
                        );

                final Notification notification = new Notification.Builder(mContext)
                                .setSmallIcon(android.R.drawable.stat_sys_warning)
                                .setContentTitle(title)
                                .setContentText(messageNotification)
                                .setAutoCancel(true)
                                .setContentIntent(resultPendingIntent)
                                .setStyle(new Notification.BigTextStyle()
                                .bigText(messageNotification))
                                .setChannelId(NotificationChannelController.CHANNEL_ID_WFC)
                                .build();
                final String notificationTag = "wifi_calling";
                final int notificationId = 1;

                NotificationManager notificationManager =
                        (NotificationManager) mContext.getSystemService(
                                Context.NOTIFICATION_SERVICE);
                notificationManager.notify(notificationTag, notificationId,
                        notification);
            }
        }
    };

    /**
     * Show notification in case of some error codes.
     */
    public void processDisconnectReason(ImsReasonInfo imsReasonInfo) {
        if (imsReasonInfo.mCode == imsReasonInfo.CODE_REGISTRATION_ERROR
                && imsReasonInfo.mExtraMessage != null) {
            // Suppress WFC Registration notifications if WFC is not enabled by the user.
            if (mImsManagerFactory.create(mContext, mPhoneId).isWfcEnabledByUser()) {
                processWfcDisconnectForNotification(imsReasonInfo);
            }
        }
    }

    // Processes an IMS disconnect cause for possible WFC registration errors and optionally
    // disable WFC.
    private void processWfcDisconnectForNotification(ImsReasonInfo imsReasonInfo) {
        CarrierConfigManager configManager =
                (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
        if (configManager == null) {
            loge("processDisconnectReason: CarrierConfigManager is not ready");
            return;
        }
        PersistableBundle pb = configManager.getConfigForSubId(getSubId());
        if (pb == null) {
            loge("processDisconnectReason: no config for subId " + getSubId());
            return;
        }
        final String[] wfcOperatorErrorCodes =
                pb.getStringArray(
                        CarrierConfigManager.KEY_WFC_OPERATOR_ERROR_CODES_STRING_ARRAY);
        if (wfcOperatorErrorCodes == null) {
            // no operator-specific error codes
            return;
        }

        final String[] wfcOperatorErrorAlertMessages =
                mContext.getResources().getStringArray(
                        com.android.internal.R.array.wfcOperatorErrorAlertMessages);
        final String[] wfcOperatorErrorNotificationMessages =
                mContext.getResources().getStringArray(
                        com.android.internal.R.array.wfcOperatorErrorNotificationMessages);

        for (int i = 0; i < wfcOperatorErrorCodes.length; i++) {
            String[] codes = wfcOperatorErrorCodes[i].split("\\|");
            if (codes.length != 2) {
                loge("Invalid carrier config: " + wfcOperatorErrorCodes[i]);
                continue;
            }

            // Match error code.
            if (!imsReasonInfo.mExtraMessage.startsWith(
                    codes[0])) {
                continue;
            }
            // If there is no delimiter at the end of error code string
            // then we need to verify that we are not matching partial code.
            // EXAMPLE: "REG9" must not match "REG99".
            // NOTE: Error code must not be empty.
            int codeStringLength = codes[0].length();
            char lastChar = codes[0].charAt(codeStringLength - 1);
            if (Character.isLetterOrDigit(lastChar)) {
                if (imsReasonInfo.mExtraMessage.length() > codeStringLength) {
                    char nextChar = imsReasonInfo.mExtraMessage.charAt(codeStringLength);
                    if (Character.isLetterOrDigit(nextChar)) {
                        continue;
                    }
                }
            }

            final CharSequence title = mContext.getText(
                    com.android.internal.R.string.wfcRegErrorTitle);

            int idx = Integer.parseInt(codes[1]);
            if (idx < 0
                    || idx >= wfcOperatorErrorAlertMessages.length
                    || idx >= wfcOperatorErrorNotificationMessages.length) {
                loge("Invalid index: " + wfcOperatorErrorCodes[i]);
                continue;
            }
            String messageAlert = imsReasonInfo.mExtraMessage;
            String messageNotification = imsReasonInfo.mExtraMessage;
            if (!wfcOperatorErrorAlertMessages[idx].isEmpty()) {
                messageAlert = String.format(
                        wfcOperatorErrorAlertMessages[idx],
                        imsReasonInfo.mExtraMessage); // Fill IMS error code into alert message
            }
            if (!wfcOperatorErrorNotificationMessages[idx].isEmpty()) {
                messageNotification = String.format(
                        wfcOperatorErrorNotificationMessages[idx],
                        imsReasonInfo.mExtraMessage); // Fill IMS error code into notification
            }

            // If WfcSettings are active then alert will be shown
            // otherwise notification will be added.
            Intent intent = new Intent(
                    android.telephony.ims.ImsManager.ACTION_WFC_IMS_REGISTRATION_ERROR);
            intent.putExtra(EXTRA_WFC_REGISTRATION_FAILURE_TITLE, title);
            intent.putExtra(EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE, messageAlert);
            intent.putExtra(EXTRA_KEY_NOTIFICATION_MESSAGE, messageNotification);
            mContext.sendOrderedBroadcast(intent, null, mResultReceiver,
                    null, Activity.RESULT_OK, null, null);

            // We can only match a single error code
            // so should break the loop after a successful match.
            break;
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Override
    public boolean isUtEnabled() {
        return mCT.isUtEnabled();
    }

    @Override
    public void sendEmergencyCallStateChange(boolean callActive) {
        mDefaultPhone.sendEmergencyCallStateChange(callActive);
    }

    @Override
    public void setBroadcastEmergencyCallStateChanges(boolean broadcast) {
        mDefaultPhone.setBroadcastEmergencyCallStateChanges(broadcast);
    }

    @VisibleForTesting
    public PowerManager.WakeLock getWakeLock() {
        return mWakeLock;
    }

    /**
     * Update roaming state and WFC mode in the following situations:
     *     1) voice is in service.
     *     2) data is in service and it is not IWLAN (if in legacy mode).
     * @param ss non-null ServiceState
     */
    private void updateRoamingState(ServiceState ss) {
        if (ss == null) {
            loge("updateRoamingState: null ServiceState!");
            return;
        }
        boolean newRoamingState = ss.getRoaming();
        // Do not recalculate if there is no change to state.
        if (mLastKnownRoamingState == newRoamingState) {
            return;
        }
        boolean isInService = (ss.getState() == ServiceState.STATE_IN_SERVICE
                || ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE);
        // If we are not IN_SERVICE for voice or data, ignore change roaming state, as we always
        // move to home in this case.
        if (!isInService || !mDefaultPhone.isRadioOn()) {
            logi("updateRoamingState: we are not IN_SERVICE, ignoring roaming change.");
            return;
        }
        // We ignore roaming changes when moving to IWLAN because it always sets the roaming
        // mode to home and masks the actual cellular roaming status if voice is not registered. If
        // we just moved to IWLAN because WFC roaming mode is IWLAN preferred and WFC home mode is
        // cell preferred, we can get into a condition where the modem keeps bouncing between
        // IWLAN->cell->IWLAN->cell...
        if (isCsNotInServiceAndPsWwanReportingWlan(ss)) {
            logi("updateRoamingState: IWLAN masking roaming, ignore roaming change.");
            return;
        }
        if (mCT.getState() == PhoneConstants.State.IDLE) {
            if (DBG) logd("updateRoamingState now: " + newRoamingState);
            mLastKnownRoamingState = newRoamingState;
            CarrierConfigManager configManager = (CarrierConfigManager)
                    getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
            // Don't set wfc mode if carrierconfig has not loaded. It will be set by GsmCdmaPhone
            // when receives ACTION_CARRIER_CONFIG_CHANGED broadcast.
            if (configManager != null && CarrierConfigManager.isConfigForIdentifiedCarrier(
                    configManager.getConfigForSubId(getSubId()))) {
                ImsManager imsManager = mImsManagerFactory.create(mContext, mPhoneId);
                imsManager.setWfcMode(imsManager.getWfcMode(newRoamingState), newRoamingState);
            }
        } else {
            if (DBG) logd("updateRoamingState postponed: " + newRoamingState);
            mCT.registerForVoiceCallEnded(this, EVENT_VOICE_CALL_ENDED, null);
        }
    }

    /**
     * In legacy mode, data registration will report IWLAN when we are using WLAN for data,
     * effectively masking the true roaming state of the device if voice is not registered.
     *
     * @return true if we are reporting not in service for CS domain over WWAN transport and WLAN
     * for PS domain over WWAN transport.
     */
    private boolean isCsNotInServiceAndPsWwanReportingWlan(ServiceState ss) {
        TransportManager tm = mDefaultPhone.getTransportManager();
        // We can not get into this condition if we are in AP-Assisted mode.
        if (tm == null || !tm.isInLegacyMode()) {
            return false;
        }
        NetworkRegistrationInfo csInfo = ss.getNetworkRegistrationInfo(
                NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
        NetworkRegistrationInfo psInfo = ss.getNetworkRegistrationInfo(
                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
        // We will return roaming state correctly if the CS domain is in service because
        // ss.getRoaming() returns isVoiceRoaming||isDataRoaming result and isDataRoaming==false
        // when the modem reports IWLAN RAT.
        return psInfo != null && csInfo != null && !csInfo.isInService()
                && psInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_IWLAN;
    }

    public RegistrationManager.RegistrationCallback getImsMmTelRegistrationCallback() {
        return mImsMmTelRegistrationHelper.getCallback();
    }

    /**
     * Reset the IMS registration state.
     */
    public void resetImsRegistrationState() {
        if (DBG) logd("resetImsRegistrationState");
        mImsMmTelRegistrationHelper.reset();
    }

    private ImsRegistrationCallbackHelper.ImsRegistrationUpdate mMmTelRegistrationUpdate = new
            ImsRegistrationCallbackHelper.ImsRegistrationUpdate() {
        @Override
        public void handleImsRegistered(int imsRadioTech) {
            if (DBG) {
                logd("handleImsRegistered: onImsMmTelConnected imsRadioTech="
                        + AccessNetworkConstants.transportTypeToString(imsRadioTech));
            }
            mRegLocalLog.log("handleImsRegistered: onImsMmTelConnected imsRadioTech="
                    + AccessNetworkConstants.transportTypeToString(imsRadioTech));
            setServiceState(ServiceState.STATE_IN_SERVICE);
            getDefaultPhone().setImsRegistrationState(true);
            mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.CONNECTED, null);
            mImsStats.onImsRegistered(imsRadioTech);
        }

        @Override
        public void handleImsRegistering(int imsRadioTech) {
            if (DBG) {
                logd("handleImsRegistering: onImsMmTelProgressing imsRadioTech="
                        + AccessNetworkConstants.transportTypeToString(imsRadioTech));
            }
            mRegLocalLog.log("handleImsRegistering: onImsMmTelProgressing imsRadioTech="
                    + AccessNetworkConstants.transportTypeToString(imsRadioTech));
            setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
            getDefaultPhone().setImsRegistrationState(false);
            mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.PROGRESSING,
                    null);
            mImsStats.onImsRegistering(imsRadioTech);
        }

        @Override
        public void handleImsUnregistered(ImsReasonInfo imsReasonInfo) {
            if (DBG) {
                logd("handleImsUnregistered: onImsMmTelDisconnected imsReasonInfo="
                        + imsReasonInfo);
            }
            mRegLocalLog.log("handleImsUnregistered: onImsMmTelDisconnected imsRadioTech="
                    + imsReasonInfo);
            setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
            processDisconnectReason(imsReasonInfo);
            getDefaultPhone().setImsRegistrationState(false);
            mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.DISCONNECTED,
                    imsReasonInfo);
            mImsStats.onImsUnregistered(imsReasonInfo);
        }

        @Override
        public void handleImsSubscriberAssociatedUriChanged(Uri[] uris) {
            if (DBG) logd("handleImsSubscriberAssociatedUriChanged");
            setCurrentSubscriberUris(uris);
        }
    };

    public IccRecords getIccRecords() {
        return mDefaultPhone.getIccRecords();
    }

    public DialArgs updateDialArgsForVolteSilentRedial(DialArgs dialArgs, int causeCode) {
        if (dialArgs != null) {
            ImsPhone.ImsDialArgs.Builder imsDialArgsBuilder;
            imsDialArgsBuilder = ImsPhone.ImsDialArgs.Builder.from(dialArgs);

            Bundle extras = new Bundle(dialArgs.intentExtras);
            if (causeCode == CallFailCause.EMC_REDIAL_ON_VOWIFI && isWifiCallingEnabled()) {
                extras.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
                        String.valueOf(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN));
                logd("trigger VoWifi emergency call");
                imsDialArgsBuilder.setIntentExtras(extras);
            } else if (causeCode == CallFailCause.EMC_REDIAL_ON_IMS) {
                logd("trigger VoLte emergency call");
            }
            return imsDialArgsBuilder.build();
        }
        return new DialArgs.Builder<>().build();
    }

    @Override
    public VoiceCallSessionStats getVoiceCallSessionStats() {
        return mDefaultPhone.getVoiceCallSessionStats();
    }

    /** Returns the {@link ImsStats} for this IMS phone. */
    public ImsStats getImsStats() {
        return mImsStats;
    }

    /** Sets the {@link ImsStats} mock for this IMS phone during unit testing. */
    @VisibleForTesting
    public void setImsStats(ImsStats imsStats) {
        mImsStats = imsStats;
    }

    public boolean hasAliveCall() {
        return (getForegroundCall().getState() != Call.State.IDLE ||
                getBackgroundCall().getState() != Call.State.IDLE);
    }

    public boolean getLastKnownRoamingState() {
        return mLastKnownRoamingState;
    }

    @Override
    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
        pw.println("ImsPhone extends:");
        super.dump(fd, pw, args);
        pw.flush();

        pw.println("ImsPhone:");
        pw.println("  mDefaultPhone = " + mDefaultPhone);
        pw.println("  mPendingMMIs = " + mPendingMMIs);
        pw.println("  mPostDialHandler = " + mPostDialHandler);
        pw.println("  mSS = " + mSS);
        pw.println("  mWakeLock = " + mWakeLock);
        pw.println("  mIsPhoneInEcmState = " + isInEcm());
        pw.println("  mEcmExitRespRegistrant = " + mEcmExitRespRegistrant);
        pw.println("  mSilentRedialRegistrants = " + mSilentRedialRegistrants);
        pw.println("  mImsMmTelRegistrationState = "
                + mImsMmTelRegistrationHelper.getImsRegistrationState());
        pw.println("  mLastKnownRoamingState = " + mLastKnownRoamingState);
        pw.println("  mSsnRegistrants = " + mSsnRegistrants);
        pw.println(" Registration Log:");
        pw.increaseIndent();
        mRegLocalLog.dump(pw);
        pw.decreaseIndent();
        pw.flush();
    }

    private void logi(String s) {
        Rlog.i(LOG_TAG, "[" + mPhoneId + "] " + s);
    }

    private void logv(String s) {
        Rlog.v(LOG_TAG, "[" + mPhoneId + "] " + s);
    }

    private void logd(String s) {
        Rlog.d(LOG_TAG, "[" + mPhoneId + "] " + s);
    }

    private void loge(String s) {
        Rlog.e(LOG_TAG, "[" + mPhoneId + "] " + s);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy