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

src.com.android.internal.telephony.CallManager Maven / Gradle / Ivy

/*
 * Copyright (C) 2010 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;

import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.Registrant;
import android.os.RegistrantList;
import android.telephony.PhoneNumberUtils;
import android.telephony.PhoneStateListener;
import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;

import com.android.internal.telephony.sip.SipPhone;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;



/**
 * @hide
 *
 * CallManager class provides an abstract layer for PhoneApp to access
 * and control calls. It implements Phone interface.
 *
 * CallManager provides call and connection control as well as
 * channel capability.
 *
 * There are three categories of APIs CallManager provided
 *
 *  1. Call control and operation, such as dial() and hangup()
 *  2. Channel capabilities, such as CanConference()
 *  3. Register notification
 *
 *
 */
public class CallManager {

    private static final String LOG_TAG ="CallManager";
    private static final boolean DBG = true;
    private static final boolean VDBG = false;

    private static final int EVENT_DISCONNECT = 100;
    private static final int EVENT_PRECISE_CALL_STATE_CHANGED = 101;
    private static final int EVENT_NEW_RINGING_CONNECTION = 102;
    private static final int EVENT_UNKNOWN_CONNECTION = 103;
    private static final int EVENT_INCOMING_RING = 104;
    private static final int EVENT_RINGBACK_TONE = 105;
    private static final int EVENT_IN_CALL_VOICE_PRIVACY_ON = 106;
    private static final int EVENT_IN_CALL_VOICE_PRIVACY_OFF = 107;
    private static final int EVENT_CALL_WAITING = 108;
    private static final int EVENT_DISPLAY_INFO = 109;
    private static final int EVENT_SIGNAL_INFO = 110;
    private static final int EVENT_CDMA_OTA_STATUS_CHANGE = 111;
    private static final int EVENT_RESEND_INCALL_MUTE = 112;
    private static final int EVENT_MMI_INITIATE = 113;
    private static final int EVENT_MMI_COMPLETE = 114;
    private static final int EVENT_ECM_TIMER_RESET = 115;
    private static final int EVENT_SUBSCRIPTION_INFO_READY = 116;
    private static final int EVENT_SUPP_SERVICE_FAILED = 117;
    private static final int EVENT_SERVICE_STATE_CHANGED = 118;
    private static final int EVENT_POST_DIAL_CHARACTER = 119;
    private static final int EVENT_ONHOLD_TONE = 120;
    // FIXME Taken from klp-sprout-dev but setAudioMode was removed in L.
    //private static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 121;
    private static final int EVENT_TTY_MODE_RECEIVED = 122;

    // Singleton instance
    private static final CallManager INSTANCE = new CallManager();

    // list of registered phones, which are Phone objs
    @UnsupportedAppUsage
    private final ArrayList mPhones;

    // list of supported ringing calls
    @UnsupportedAppUsage
    private final ArrayList mRingingCalls;

    // list of supported background calls
    @UnsupportedAppUsage
    private final ArrayList mBackgroundCalls;

    // list of supported foreground calls
    @UnsupportedAppUsage
    private final ArrayList mForegroundCalls;

    // empty connection list
    @UnsupportedAppUsage
    private final ArrayList mEmptyConnections = new ArrayList();

    // mapping of phones to registered handler instances used for callbacks from RIL
    private final HashMap mHandlerMap = new HashMap<>();

    // default phone as the first phone registered, which is Phone obj
    private Phone mDefaultPhone;

    private boolean mSpeedUpAudioForMtCall = false;
    // FIXME Taken from klp-sprout-dev but setAudioMode was removed in L.
    //private boolean mIsEccDialing = false;

    private Object mRegistrantidentifier = new Object();

    // state registrants
    protected final RegistrantList mPreciseCallStateRegistrants
    = new RegistrantList();

    protected final RegistrantList mNewRingingConnectionRegistrants
    = new RegistrantList();

    protected final RegistrantList mIncomingRingRegistrants
    = new RegistrantList();

    protected final RegistrantList mDisconnectRegistrants
    = new RegistrantList();

    protected final RegistrantList mMmiRegistrants
    = new RegistrantList();

    protected final RegistrantList mUnknownConnectionRegistrants
    = new RegistrantList();

    protected final RegistrantList mRingbackToneRegistrants
    = new RegistrantList();

    protected final RegistrantList mOnHoldToneRegistrants
    = new RegistrantList();

    protected final RegistrantList mInCallVoicePrivacyOnRegistrants
    = new RegistrantList();

    protected final RegistrantList mInCallVoicePrivacyOffRegistrants
    = new RegistrantList();

    protected final RegistrantList mCallWaitingRegistrants
    = new RegistrantList();

    protected final RegistrantList mDisplayInfoRegistrants
    = new RegistrantList();

    protected final RegistrantList mSignalInfoRegistrants
    = new RegistrantList();

    protected final RegistrantList mCdmaOtaStatusChangeRegistrants
    = new RegistrantList();

    protected final RegistrantList mResendIncallMuteRegistrants
    = new RegistrantList();

    protected final RegistrantList mMmiInitiateRegistrants
    = new RegistrantList();

    protected final RegistrantList mMmiCompleteRegistrants
    = new RegistrantList();

    protected final RegistrantList mEcmTimerResetRegistrants
    = new RegistrantList();

    protected final RegistrantList mSubscriptionInfoReadyRegistrants
    = new RegistrantList();

    protected final RegistrantList mSuppServiceFailedRegistrants
    = new RegistrantList();

    protected final RegistrantList mServiceStateChangedRegistrants
    = new RegistrantList();

    protected final RegistrantList mPostDialCharacterRegistrants
    = new RegistrantList();

    protected final RegistrantList mTtyModeReceivedRegistrants
    = new RegistrantList();

    private CallManager() {
        mPhones = new ArrayList();
        mRingingCalls = new ArrayList();
        mBackgroundCalls = new ArrayList();
        mForegroundCalls = new ArrayList();
        mDefaultPhone = null;
    }

    /**
     * get singleton instance of CallManager
     * @return CallManager
     */
    @UnsupportedAppUsage
    public static CallManager getInstance() {
        return INSTANCE;
    }

    /**
     * get Phone object corresponds to subId
     * @return Phone
     */
    private Phone getPhone(int subId) {
        Phone p = null;
        for (Phone phone : mPhones) {
            if (phone.getSubId() == subId &&
                    phone.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) {
                p = phone;
                break;
            }
        }
        return p;
    }

    /**
     * Get current coarse-grained voice call state.
     * If the Call Manager has an active call and call waiting occurs,
     * then the phone state is RINGING not OFFHOOK
     *
     */
    @UnsupportedAppUsage
    public PhoneConstants.State getState() {
        PhoneConstants.State s = PhoneConstants.State.IDLE;

        for (Phone phone : mPhones) {
            if (phone.getState() == PhoneConstants.State.RINGING) {
                s = PhoneConstants.State.RINGING;
            } else if (phone.getState() == PhoneConstants.State.OFFHOOK) {
                if (s == PhoneConstants.State.IDLE) s = PhoneConstants.State.OFFHOOK;
            }
        }
        return s;
    }

    /**
     * Get current coarse-grained voice call state on a subId.
     * If the Call Manager has an active call and call waiting occurs,
     * then the phone state is RINGING not OFFHOOK
     *
     */
    @UnsupportedAppUsage
    public PhoneConstants.State getState(int subId) {
        PhoneConstants.State s = PhoneConstants.State.IDLE;

        for (Phone phone : mPhones) {
            if (phone.getSubId() == subId) {
                if (phone.getState() == PhoneConstants.State.RINGING) {
                    s = PhoneConstants.State.RINGING;
                } else if (phone.getState() == PhoneConstants.State.OFFHOOK) {
                    if (s == PhoneConstants.State.IDLE) s = PhoneConstants.State.OFFHOOK;
                }
            }
        }
        return s;
    }

    /**
     * @return the service state of CallManager, which represents the
     * highest priority state of all the service states of phones
     *
     * The priority is defined as
     *
     * STATE_IN_SERIVCE > STATE_OUT_OF_SERIVCE > STATE_EMERGENCY > STATE_POWER_OFF
     *
     */

    public int getServiceState() {
        int resultState = ServiceState.STATE_OUT_OF_SERVICE;

        for (Phone phone : mPhones) {
            int serviceState = phone.getServiceState().getState();
            if (serviceState == ServiceState.STATE_IN_SERVICE) {
                // IN_SERVICE has the highest priority
                resultState = serviceState;
                break;
            } else if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
                // OUT_OF_SERVICE replaces EMERGENCY_ONLY and POWER_OFF
                // Note: EMERGENCY_ONLY is not in use at this moment
                if ( resultState == ServiceState.STATE_EMERGENCY_ONLY ||
                        resultState == ServiceState.STATE_POWER_OFF) {
                    resultState = serviceState;
                }
            } else if (serviceState == ServiceState.STATE_EMERGENCY_ONLY) {
                if (resultState == ServiceState.STATE_POWER_OFF) {
                    resultState = serviceState;
                }
            }
        }
        return resultState;
    }

    /**
     * @return the Phone service state corresponds to subId
     */
    public int getServiceState(int subId) {
        int resultState = ServiceState.STATE_OUT_OF_SERVICE;

        for (Phone phone : mPhones) {
            if (phone.getSubId() == subId) {
                int serviceState = phone.getServiceState().getState();
                if (serviceState == ServiceState.STATE_IN_SERVICE) {
                    // IN_SERVICE has the highest priority
                    resultState = serviceState;
                    break;
                } else if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
                    // OUT_OF_SERVICE replaces EMERGENCY_ONLY and POWER_OFF
                    // Note: EMERGENCY_ONLY is not in use at this moment
                    if ( resultState == ServiceState.STATE_EMERGENCY_ONLY ||
                            resultState == ServiceState.STATE_POWER_OFF) {
                        resultState = serviceState;
                    }
                } else if (serviceState == ServiceState.STATE_EMERGENCY_ONLY) {
                    if (resultState == ServiceState.STATE_POWER_OFF) {
                        resultState = serviceState;
                    }
                }
            }
        }
        return resultState;
    }

    /**
     * @return the phone associated with any call
     */
    @UnsupportedAppUsage
    public Phone getPhoneInCall() {
        Phone phone = null;
        if (!getFirstActiveRingingCall().isIdle()) {
            phone = getFirstActiveRingingCall().getPhone();
        } else if (!getActiveFgCall().isIdle()) {
            phone = getActiveFgCall().getPhone();
        } else {
            // If BG call is idle, we return default phone
            phone = getFirstActiveBgCall().getPhone();
        }
        return phone;
    }

    /**
     * Register phone to CallManager
     * @param phone to be registered
     * @return true if register successfully
     */
    @UnsupportedAppUsage
    public boolean registerPhone(Phone phone) {
        if (phone != null && !mPhones.contains(phone)) {

            if (DBG) {
                Rlog.d(LOG_TAG, "registerPhone(" +
                        phone.getPhoneName() + " " + phone + ")");
            }

            if (mPhones.isEmpty()) {
                mDefaultPhone = phone;
            }
            mPhones.add(phone);
            mRingingCalls.add(phone.getRingingCall());
            mBackgroundCalls.add(phone.getBackgroundCall());
            mForegroundCalls.add(phone.getForegroundCall());
            registerForPhoneStates(phone);
            return true;
        }
        return false;
    }

    /**
     * unregister phone from CallManager
     * @param phone to be unregistered
     */
    @UnsupportedAppUsage
    public void unregisterPhone(Phone phone) {
        if (phone != null && mPhones.contains(phone)) {

            if (DBG) {
                Rlog.d(LOG_TAG, "unregisterPhone(" +
                        phone.getPhoneName() + " " + phone + ")");
            }

            Phone imsPhone = phone.getImsPhone();
            if (imsPhone != null) {
                unregisterPhone(imsPhone);
            }

            mPhones.remove(phone);
            mRingingCalls.remove(phone.getRingingCall());
            mBackgroundCalls.remove(phone.getBackgroundCall());
            mForegroundCalls.remove(phone.getForegroundCall());
            unregisterForPhoneStates(phone);
            if (phone == mDefaultPhone) {
                if (mPhones.isEmpty()) {
                    mDefaultPhone = null;
                } else {
                    mDefaultPhone = mPhones.get(0);
                }
            }
        }
    }

    /**
     * return the default phone or null if no phone available
     */
    @UnsupportedAppUsage
    public Phone getDefaultPhone() {
        return mDefaultPhone;
    }

    /**
     * @return the phone associated with the foreground call
     */
    @UnsupportedAppUsage
    public Phone getFgPhone() {
        return getActiveFgCall().getPhone();
    }

    /**
     * @return the phone associated with the foreground call
     * of a particular subId
     */
    @UnsupportedAppUsage
    public Phone getFgPhone(int subId) {
        return getActiveFgCall(subId).getPhone();
    }

    /**
     * @return the phone associated with the background call
     */
    @UnsupportedAppUsage
    public Phone getBgPhone() {
        return getFirstActiveBgCall().getPhone();
    }

    /**
     * @return the phone associated with the ringing call
     */
    @UnsupportedAppUsage
    public Phone getRingingPhone() {
        return getFirstActiveRingingCall().getPhone();
    }

    /**
     * @return the phone associated with the ringing call
     * of a particular subId
     */
    public Phone getRingingPhone(int subId) {
        return getFirstActiveRingingCall(subId).getPhone();
    }

    /* FIXME Taken from klp-sprout-dev but setAudioMode was removed in L.
    public void setAudioMode() {
        Context context = getContext();
        if (context == null) return;
        AudioManager audioManager = (AudioManager)
                context.getSystemService(Context.AUDIO_SERVICE);

        if (!isServiceStateInService() && !mIsEccDialing) {
            if (audioManager.getMode() != AudioManager.MODE_NORMAL) {
                if (VDBG) Rlog.d(LOG_TAG, "abandonAudioFocus");
                // abandon audio focus after the mode has been set back to normal
                audioManager.abandonAudioFocusForCall();
                audioManager.setMode(AudioManager.MODE_NORMAL);
            }
            return;
        }

        // change the audio mode and request/abandon audio focus according to phone state,
        // but only on audio mode transitions
        switch (getState()) {
            case RINGING:
                int curAudioMode = audioManager.getMode();
                if (curAudioMode != AudioManager.MODE_RINGTONE) {
                    if (VDBG) Rlog.d(LOG_TAG, "requestAudioFocus on STREAM_RING");
                    audioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
                            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                    if(!mSpeedUpAudioForMtCall) {
                        audioManager.setMode(AudioManager.MODE_RINGTONE);
                    }
                }

                if (mSpeedUpAudioForMtCall && (curAudioMode != AudioManager.MODE_IN_CALL)) {
                    audioManager.setMode(AudioManager.MODE_IN_CALL);
                }
                break;
            case OFFHOOK:
                Phone offhookPhone = getFgPhone();
                if (getActiveFgCallState() == Call.State.IDLE) {
                    // There is no active Fg calls, the OFFHOOK state
                    // is set by the Bg call. So set the phone to bgPhone.
                    offhookPhone = getBgPhone();
                }

                int newAudioMode = AudioManager.MODE_IN_CALL;
                if (offhookPhone instanceof SipPhone) {
                    Rlog.d(LOG_TAG, "setAudioMode Set audio mode for SIP call!");
                    // enable IN_COMMUNICATION audio mode instead for sipPhone
                    newAudioMode = AudioManager.MODE_IN_COMMUNICATION;
                }
                int currMode = audioManager.getMode();
                if (currMode != newAudioMode || mSpeedUpAudioForMtCall) {
                    // request audio focus before setting the new mode
                    if (VDBG) Rlog.d(LOG_TAG, "requestAudioFocus on STREAM_VOICE_CALL");
                    audioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
                            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                    Rlog.d(LOG_TAG, "setAudioMode Setting audio mode from "
                            + currMode + " to " + newAudioMode);
                    audioManager.setMode(newAudioMode);
                }
                mSpeedUpAudioForMtCall = false;
                break;
            case IDLE:
                if (audioManager.getMode() != AudioManager.MODE_NORMAL) {
                    audioManager.setMode(AudioManager.MODE_NORMAL);
                    if (VDBG) Rlog.d(LOG_TAG, "abandonAudioFocus");
                    // abandon audio focus after the mode has been set back to normal
                    audioManager.abandonAudioFocusForCall();
                }
                mSpeedUpAudioForMtCall = false;
                break;
        }
        Rlog.d(LOG_TAG, "setAudioMode state = " + getState());
    }
    */

    @UnsupportedAppUsage
    private Context getContext() {
        Phone defaultPhone = getDefaultPhone();
        return ((defaultPhone == null) ? null : defaultPhone.getContext());
    }

    public Object getRegistrantIdentifier() {
        return mRegistrantidentifier;
    }

    private void registerForPhoneStates(Phone phone) {
        // We need to keep a mapping of handler to Phone for proper unregistration.
        // TODO: Clean up this solution as it is just a work around for each Phone instance
        // using the same Handler to register with the RIL. When time permits, we should consider
        // moving the handler (or the reference ot the handler) into the Phone object.
        // See b/17414427.
        CallManagerHandler handler = mHandlerMap.get(phone);
        if (handler != null) {
            Rlog.d(LOG_TAG, "This phone has already been registered.");
            return;
        }

        // New registration, create a new handler instance and register the phone.
        handler = new CallManagerHandler();
        mHandlerMap.put(phone, handler);

        // for common events supported by all phones
        // The mRegistrantIdentifier passed here, is to identify in the Phone
        // that the registrants are coming from the CallManager.
        phone.registerForPreciseCallStateChanged(handler, EVENT_PRECISE_CALL_STATE_CHANGED,
                mRegistrantidentifier);
        phone.registerForDisconnect(handler, EVENT_DISCONNECT,
                mRegistrantidentifier);
        phone.registerForNewRingingConnection(handler, EVENT_NEW_RINGING_CONNECTION,
                mRegistrantidentifier);
        phone.registerForUnknownConnection(handler, EVENT_UNKNOWN_CONNECTION,
                mRegistrantidentifier);
        phone.registerForIncomingRing(handler, EVENT_INCOMING_RING,
                mRegistrantidentifier);
        phone.registerForRingbackTone(handler, EVENT_RINGBACK_TONE,
                mRegistrantidentifier);
        phone.registerForInCallVoicePrivacyOn(handler, EVENT_IN_CALL_VOICE_PRIVACY_ON,
                mRegistrantidentifier);
        phone.registerForInCallVoicePrivacyOff(handler, EVENT_IN_CALL_VOICE_PRIVACY_OFF,
                mRegistrantidentifier);
        phone.registerForDisplayInfo(handler, EVENT_DISPLAY_INFO,
                mRegistrantidentifier);
        phone.registerForSignalInfo(handler, EVENT_SIGNAL_INFO,
                mRegistrantidentifier);
        phone.registerForResendIncallMute(handler, EVENT_RESEND_INCALL_MUTE,
                mRegistrantidentifier);
        phone.registerForMmiInitiate(handler, EVENT_MMI_INITIATE,
                mRegistrantidentifier);
        phone.registerForMmiComplete(handler, EVENT_MMI_COMPLETE,
                mRegistrantidentifier);
        phone.registerForSuppServiceFailed(handler, EVENT_SUPP_SERVICE_FAILED,
                mRegistrantidentifier);
        phone.registerForServiceStateChanged(handler, EVENT_SERVICE_STATE_CHANGED,
                mRegistrantidentifier);

        // FIXME Taken from klp-sprout-dev but setAudioMode was removed in L.
        //phone.registerForRadioOffOrNotAvailable(handler, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);

        // for events supported only by GSM, CDMA and IMS phone
        phone.setOnPostDialCharacter(handler, EVENT_POST_DIAL_CHARACTER, null);

        // for events supported only by CDMA phone
        phone.registerForCdmaOtaStatusChange(handler, EVENT_CDMA_OTA_STATUS_CHANGE, null);
        phone.registerForSubscriptionInfoReady(handler, EVENT_SUBSCRIPTION_INFO_READY, null);
        phone.registerForCallWaiting(handler, EVENT_CALL_WAITING, null);
        phone.registerForEcmTimerReset(handler, EVENT_ECM_TIMER_RESET, null);

        // for events supported only by IMS phone
        phone.registerForOnHoldTone(handler, EVENT_ONHOLD_TONE, null);
        phone.registerForSuppServiceFailed(handler, EVENT_SUPP_SERVICE_FAILED, null);
        phone.registerForTtyModeReceived(handler, EVENT_TTY_MODE_RECEIVED, null);
    }

    private void unregisterForPhoneStates(Phone phone) {
        // Make sure that we clean up our map of handlers to Phones.
        CallManagerHandler handler = mHandlerMap.get(phone);
        if (handler == null) {
            Rlog.e(LOG_TAG, "Could not find Phone handler for unregistration");
            return;
        }
        mHandlerMap.remove(phone);

        //  for common events supported by all phones
        phone.unregisterForPreciseCallStateChanged(handler);
        phone.unregisterForDisconnect(handler);
        phone.unregisterForNewRingingConnection(handler);
        phone.unregisterForUnknownConnection(handler);
        phone.unregisterForIncomingRing(handler);
        phone.unregisterForRingbackTone(handler);
        phone.unregisterForInCallVoicePrivacyOn(handler);
        phone.unregisterForInCallVoicePrivacyOff(handler);
        phone.unregisterForDisplayInfo(handler);
        phone.unregisterForSignalInfo(handler);
        phone.unregisterForResendIncallMute(handler);
        phone.unregisterForMmiInitiate(handler);
        phone.unregisterForMmiComplete(handler);
        phone.unregisterForSuppServiceFailed(handler);
        phone.unregisterForServiceStateChanged(handler);
        phone.unregisterForTtyModeReceived(handler);
        // FIXME Taken from klp-sprout-dev but setAudioMode was removed in L.
        //phone.unregisterForRadioOffOrNotAvailable(handler);

        // for events supported only by GSM, CDMA and IMS phone
        phone.setOnPostDialCharacter(null, EVENT_POST_DIAL_CHARACTER, null);

        // for events supported only by CDMA phone
        phone.unregisterForCdmaOtaStatusChange(handler);
        phone.unregisterForSubscriptionInfoReady(handler);
        phone.unregisterForCallWaiting(handler);
        phone.unregisterForEcmTimerReset(handler);

        // for events supported only by IMS phone
        phone.unregisterForOnHoldTone(handler);
        phone.unregisterForSuppServiceFailed(handler);
    }

    /**
     * Reject (ignore) a ringing call. In GSM, this means UDUB
     * (User Determined User Busy). Reject occurs asynchronously,
     * and final notification occurs via
     * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
     * java.lang.Object) registerForPreciseCallStateChanged()}.
     *
     * @exception CallStateException when no call is ringing or waiting
     */
    public void rejectCall(Call ringingCall) throws CallStateException {
        if (VDBG) {
            Rlog.d(LOG_TAG, toString());
        }

        Phone ringingPhone = ringingCall.getPhone();

        ringingPhone.rejectCall();

        if (VDBG) {
            Rlog.d(LOG_TAG, "End rejectCall(" +ringingCall + ")");
            Rlog.d(LOG_TAG, toString());
        }
    }

    /**
     * Whether or not the phone can conference in the current phone
     * state--that is, one call holding and one call active.
     * @return true if the phone can conference; false otherwise.
     */
    public boolean canConference(Call heldCall) {
        Phone activePhone = null;
        Phone heldPhone = null;

        if (hasActiveFgCall()) {
            activePhone = getActiveFgCall().getPhone();
        }

        if (heldCall != null) {
            heldPhone = heldCall.getPhone();
        }

        return heldPhone.getClass().equals(activePhone.getClass());
    }

    /**
     * Whether or not the phone can conference in the current phone
     * state--that is, one call holding and one call active.
     * This method consider the phone object which is specific
     * to the provided subId.
     * @return true if the phone can conference; false otherwise.
     */
    @UnsupportedAppUsage
    public boolean canConference(Call heldCall, int subId) {
        Phone activePhone = null;
        Phone heldPhone = null;

        if (hasActiveFgCall(subId)) {
            activePhone = getActiveFgCall(subId).getPhone();
        }

        if (heldCall != null) {
            heldPhone = heldCall.getPhone();
        }

        return heldPhone.getClass().equals(activePhone.getClass());
    }

    /**
     * Conferences holding and active. Conference occurs asynchronously
     * and may fail. Final notification occurs via
     * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
     * java.lang.Object) registerForPreciseCallStateChanged()}.
     *
     * @exception CallStateException if canConference() would return false.
     * In these cases, this operation may not be performed.
     */
    @UnsupportedAppUsage
    public void conference(Call heldCall) throws CallStateException {
        int subId  = heldCall.getPhone().getSubId();

        if (VDBG) {
            Rlog.d(LOG_TAG, "conference(" +heldCall + ")");
            Rlog.d(LOG_TAG, toString());
        }

        Phone fgPhone = getFgPhone(subId);
        if (fgPhone != null) {
            if (fgPhone instanceof SipPhone) {
                ((SipPhone) fgPhone).conference(heldCall);
            } else if (canConference(heldCall)) {
                fgPhone.conference();
            } else {
                throw(new CallStateException("Can't conference foreground and selected background call"));
            }
        } else {
            Rlog.d(LOG_TAG, "conference: fgPhone=null");
        }

        if (VDBG) {
            Rlog.d(LOG_TAG, "End conference(" +heldCall + ")");
            Rlog.d(LOG_TAG, toString());
        }

    }

    /**
     * Initiate a new voice connection. This happens asynchronously, so you
     * cannot assume the audio path is connected (or a call index has been
     * assigned) until PhoneStateChanged notification has occurred.
     *
     * @exception CallStateException if a new outgoing call is not currently
     * possible because no more call slots exist or a call exists that is
     * dialing, alerting, ringing, or waiting.  Other errors are
     * handled asynchronously.
     */
    public Connection dial(Phone phone, String dialString, int videoState)
            throws CallStateException {
        int subId = phone.getSubId();
        Connection result;

        if (VDBG) {
            Rlog.d(LOG_TAG, " dial(" + phone + ", "+ dialString + ")" +
                    " subId = " + subId);
            Rlog.d(LOG_TAG, toString());
        }

        if (!canDial(phone)) {
            /*
             * canDial function only checks whether the phone can make a new call.
             * InCall MMI commmands are basically supplementary services
             * within a call eg: call hold, call deflection, explicit call transfer etc.
             */
            String newDialString = PhoneNumberUtils.stripSeparators(dialString);
            if (phone.handleInCallMmiCommands(newDialString)) {
                return null;
            } else {
                throw new CallStateException("cannot dial in current state");
            }
        }

        if ( hasActiveFgCall(subId) ) {
            Phone activePhone = getActiveFgCall(subId).getPhone();
            boolean hasBgCall = !(activePhone.getBackgroundCall().isIdle());

            if (DBG) {
                Rlog.d(LOG_TAG, "hasBgCall: "+ hasBgCall + " sameChannel:" + (activePhone == phone));
            }

            // Manipulation between IMS phone and its owner
            // will be treated in GSM/CDMA phone.
            Phone imsPhone = phone.getImsPhone();
            if (activePhone != phone
                    && (imsPhone == null || imsPhone != activePhone)) {
                if (hasBgCall) {
                    Rlog.d(LOG_TAG, "Hangup");
                    getActiveFgCall(subId).hangup();
                } else {
                    Rlog.d(LOG_TAG, "Switch");
                    activePhone.switchHoldingAndActive();
                }
            }
        }

        // FIXME Taken from klp-sprout-dev but setAudioMode was removed in L.
        //mIsEccDialing = PhoneNumberUtils.isEmergencyNumber(dialString);

        result = phone.dial(dialString, new PhoneInternalInterface.DialArgs.Builder<>()
                .setVideoState(videoState).build());

        if (VDBG) {
            Rlog.d(LOG_TAG, "End dial(" + phone + ", "+ dialString + ")");
            Rlog.d(LOG_TAG, toString());
        }

        return result;
    }

    /**
     * Initiate a new voice connection. This happens asynchronously, so you
     * cannot assume the audio path is connected (or a call index has been
     * assigned) until PhoneStateChanged notification has occurred.
     *
     * @exception CallStateException if a new outgoing call is not currently
     * possible because no more call slots exist or a call exists that is
     * dialing, alerting, ringing, or waiting.  Other errors are
     * handled asynchronously.
     */
    public Connection dial(Phone phone, String dialString, UUSInfo uusInfo, int videoState)
            throws CallStateException {
        return phone.dial(dialString,
                new PhoneInternalInterface.DialArgs.Builder<>()
                        .setUusInfo(uusInfo)
                        .setVideoState(videoState).build());
    }

    /**
     * clear disconnect connection for each phone
     */
    public void clearDisconnected() {
        for(Phone phone : mPhones) {
            phone.clearDisconnected();
        }
    }

    /**
     * clear disconnect connection for a phone specific
     * to the provided subId
     */
    public void clearDisconnected(int subId) {
        for(Phone phone : mPhones) {
            if (phone.getSubId() == subId) {
                phone.clearDisconnected();
            }
        }
    }

    /**
     * Phone can make a call only if ALL of the following are true:
     *        - Phone is not powered off
     *        - There's no incoming or waiting call
     *        - The foreground call is ACTIVE or IDLE or DISCONNECTED.
     *          (We mainly need to make sure it *isn't* DIALING or ALERTING.)
     * @param phone
     * @return true if the phone can make a new call
     */
    @UnsupportedAppUsage
    private boolean canDial(Phone phone) {
        int serviceState = phone.getServiceState().getState();
        int subId = phone.getSubId();
        boolean hasRingingCall = hasActiveRingingCall();
        Call.State fgCallState = getActiveFgCallState(subId);

        boolean result = (serviceState != ServiceState.STATE_POWER_OFF
                && !hasRingingCall
                && ((fgCallState == Call.State.ACTIVE)
                    || (fgCallState == Call.State.IDLE)
                    || (fgCallState == Call.State.DISCONNECTED)
                    /*As per 3GPP TS 51.010-1 section 31.13.1.4
                    call should be alowed when the foreground
                    call is in ALERTING state*/
                    || (fgCallState == Call.State.ALERTING)));

        if (result == false) {
            Rlog.d(LOG_TAG, "canDial serviceState=" + serviceState
                            + " hasRingingCall=" + hasRingingCall
                            + " fgCallState=" + fgCallState);
        }
        return result;
    }

    /**
     * Whether or not the phone can do explicit call transfer in the current
     * phone state--that is, one call holding and one call active.
     * @return true if the phone can do explicit call transfer; false otherwise.
     */
    public boolean canTransfer(Call heldCall) {
        Phone activePhone = null;
        Phone heldPhone = null;

        if (hasActiveFgCall()) {
            activePhone = getActiveFgCall().getPhone();
        }

        if (heldCall != null) {
            heldPhone = heldCall.getPhone();
        }

        return (heldPhone == activePhone && activePhone.canTransfer());
    }

    /**
     * Whether or not the phone specific to subId can do explicit call transfer
     * in the current phone state--that is, one call holding and one call active.
     * @return true if the phone can do explicit call transfer; false otherwise.
     */
    public boolean canTransfer(Call heldCall, int subId) {
        Phone activePhone = null;
        Phone heldPhone = null;

        if (hasActiveFgCall(subId)) {
            activePhone = getActiveFgCall(subId).getPhone();
        }

        if (heldCall != null) {
            heldPhone = heldCall.getPhone();
        }

        return (heldPhone == activePhone && activePhone.canTransfer());
    }

    /**
     * Connects the held call and active call
     * Disconnects the subscriber from both calls
     *
     * Explicit Call Transfer occurs asynchronously
     * and may fail. Final notification occurs via
     * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
     * java.lang.Object) registerForPreciseCallStateChanged()}.
     *
     * @exception CallStateException if canTransfer() would return false.
     * In these cases, this operation may not be performed.
     */
    public void explicitCallTransfer(Call heldCall) throws CallStateException {
        if (VDBG) {
            Rlog.d(LOG_TAG, " explicitCallTransfer(" + heldCall + ")");
            Rlog.d(LOG_TAG, toString());
        }

        if (canTransfer(heldCall)) {
            heldCall.getPhone().explicitCallTransfer();
        }

        if (VDBG) {
            Rlog.d(LOG_TAG, "End explicitCallTransfer(" + heldCall + ")");
            Rlog.d(LOG_TAG, toString());
        }

    }

    /**
     * Returns a list of MMI codes that are pending for a phone. (They have initiated
     * but have not yet completed).
     * Presently there is only ever one.
     *
     * Use registerForMmiInitiate
     * and registerForMmiComplete for change notification.
     * @return null if phone doesn't have or support mmi code
     */
    public List getPendingMmiCodes(Phone phone) {
        Rlog.e(LOG_TAG, "getPendingMmiCodes not implemented");
        return null;
    }

    /**
     * Sends user response to a USSD REQUEST message.  An MmiCode instance
     * representing this response is sent to handlers registered with
     * registerForMmiInitiate.
     *
     * @param ussdMessge    Message to send in the response.
     * @return false if phone doesn't support ussd service
     */
    public boolean sendUssdResponse(Phone phone, String ussdMessge) {
        Rlog.e(LOG_TAG, "sendUssdResponse not implemented");
        return false;
    }

    /**
     * Mutes or unmutes the microphone for the active call. The microphone
     * is automatically unmuted if a call is answered, dialed, or resumed
     * from a holding state.
     *
     * @param muted true to mute the microphone,
     * false to activate the microphone.
     */

    public void setMute(boolean muted) {
        if (VDBG) {
            Rlog.d(LOG_TAG, " setMute(" + muted + ")");
            Rlog.d(LOG_TAG, toString());
        }

        if (hasActiveFgCall()) {
            getActiveFgCall().getPhone().setMute(muted);
        }

        if (VDBG) {
            Rlog.d(LOG_TAG, "End setMute(" + muted + ")");
            Rlog.d(LOG_TAG, toString());
        }
    }

    /**
     * Gets current mute status. Use
     * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
     * java.lang.Object) registerForPreciseCallStateChanged()}
     * as a change notifcation, although presently phone state changed is not
     * fired when setMute() is called.
     *
     * @return true is muting, false is unmuting
     */
    public boolean getMute() {
        if (hasActiveFgCall()) {
            return getActiveFgCall().getPhone().getMute();
        } else if (hasActiveBgCall()) {
            return getFirstActiveBgCall().getPhone().getMute();
        }
        return false;
    }

    /**
     * Enables or disables echo suppression.
     */
    public void setEchoSuppressionEnabled() {
        if (VDBG) {
            Rlog.d(LOG_TAG, " setEchoSuppression()");
            Rlog.d(LOG_TAG, toString());
        }

        if (hasActiveFgCall()) {
            getActiveFgCall().getPhone().setEchoSuppressionEnabled();
        }

        if (VDBG) {
            Rlog.d(LOG_TAG, "End setEchoSuppression()");
            Rlog.d(LOG_TAG, toString());
        }
    }

    /**
     * Play a DTMF tone on the active call.
     *
     * @param c should be one of 0-9, '*' or '#'. Other values will be
     * silently ignored.
     * @return false if no active call or the active call doesn't support
     *         dtmf tone
     */
    public boolean sendDtmf(char c) {
        boolean result = false;

        if (VDBG) {
            Rlog.d(LOG_TAG, " sendDtmf(" + c + ")");
            Rlog.d(LOG_TAG, toString());
        }

        if (hasActiveFgCall()) {
            getActiveFgCall().getPhone().sendDtmf(c);
            result = true;
        }

        if (VDBG) {
            Rlog.d(LOG_TAG, "End sendDtmf(" + c + ")");
            Rlog.d(LOG_TAG, toString());
        }
        return result;
    }

    /**
     * Start to paly a DTMF tone on the active call.
     * or there is a playing DTMF tone.
     * @param c should be one of 0-9, '*' or '#'. Other values will be
     * silently ignored.
     *
     * @return false if no active call or the active call doesn't support
     *         dtmf tone
     */
    public boolean startDtmf(char c) {
        boolean result = false;

        if (VDBG) {
            Rlog.d(LOG_TAG, " startDtmf(" + c + ")");
            Rlog.d(LOG_TAG, toString());
        }

        if (hasActiveFgCall()) {
            getActiveFgCall().getPhone().startDtmf(c);
            result = true;
        }

        if (VDBG) {
            Rlog.d(LOG_TAG, "End startDtmf(" + c + ")");
            Rlog.d(LOG_TAG, toString());
        }

        return result;
    }

    /**
     * Stop the playing DTMF tone. Ignored if there is no playing DTMF
     * tone or no active call.
     */
    public void stopDtmf() {
        if (VDBG) {
            Rlog.d(LOG_TAG, " stopDtmf()" );
            Rlog.d(LOG_TAG, toString());
        }

        if (hasActiveFgCall()) getFgPhone().stopDtmf();

        if (VDBG) {
            Rlog.d(LOG_TAG, "End stopDtmf()");
            Rlog.d(LOG_TAG, toString());
        }
    }

    /**
     * send burst DTMF tone, it can send the string as single character or multiple character
     * ignore if there is no active call or not valid digits string.
     * Valid digit means only includes characters ISO-LATIN characters 0-9, *, #
     * The difference between sendDtmf and sendBurstDtmf is sendDtmf only sends one character,
     * this api can send single character and multiple character, also, this api has response
     * back to caller.
     *
     * @param dtmfString is string representing the dialing digit(s) in the active call
     * @param on the DTMF ON length in milliseconds, or 0 for default
     * @param off the DTMF OFF length in milliseconds, or 0 for default
     * @param onComplete is the callback message when the action is processed by BP
     *
     */
    public boolean sendBurstDtmf(String dtmfString, int on, int off, Message onComplete) {
        if (hasActiveFgCall()) {
            getActiveFgCall().getPhone().sendBurstDtmf(dtmfString, on, off, onComplete);
            return true;
        }
        return false;
    }

    /**
     * Notifies when a voice connection has disconnected, either due to local
     * or remote hangup or error.
     *
     *  Messages received from this will have the following members:

*

  • Message.obj will be an AsyncResult
  • *
  • AsyncResult.userObj = obj
  • *
  • AsyncResult.result = a Connection object that is * no longer connected.
*/ @UnsupportedAppUsage public void registerForDisconnect(Handler h, int what, Object obj) { mDisconnectRegistrants.addUnique(h, what, obj); } /** * Unregisters for voice disconnection notification. * Extraneous calls are tolerated silently */ @UnsupportedAppUsage public void unregisterForDisconnect(Handler h){ mDisconnectRegistrants.remove(h); } /** * Register for getting notifications for change in the Call State {@link Call.State} * This is called PreciseCallState because the call state is more precise than what * can be obtained using the {@link PhoneStateListener} * * Resulting events will have an AsyncResult in Message.obj. * AsyncResult.userData will be set to the obj argument here. * The h parameter is held only by a weak reference. */ @UnsupportedAppUsage public void registerForPreciseCallStateChanged(Handler h, int what, Object obj){ mPreciseCallStateRegistrants.addUnique(h, what, obj); } /** * Unregisters for voice call state change notifications. * Extraneous calls are tolerated silently. */ @UnsupportedAppUsage public void unregisterForPreciseCallStateChanged(Handler h){ mPreciseCallStateRegistrants.remove(h); } /** * Notifies when a previously untracked non-ringing/waiting connection has appeared. * This is likely due to some other entity (eg, SIM card application) initiating a call. */ public void registerForUnknownConnection(Handler h, int what, Object obj){ mUnknownConnectionRegistrants.addUnique(h, what, obj); } /** * Unregisters for unknown connection notifications. */ public void unregisterForUnknownConnection(Handler h){ mUnknownConnectionRegistrants.remove(h); } /** * Notifies when a new ringing or waiting connection has appeared.

* * Messages received from this: * Message.obj will be an AsyncResult * AsyncResult.userObj = obj * AsyncResult.result = a Connection.

* Please check Connection.isRinging() to make sure the Connection * has not dropped since this message was posted. * If Connection.isRinging() is true, then * Connection.getCall() == Phone.getRingingCall() */ @UnsupportedAppUsage public void registerForNewRingingConnection(Handler h, int what, Object obj){ mNewRingingConnectionRegistrants.addUnique(h, what, obj); } /** * Unregisters for new ringing connection notification. * Extraneous calls are tolerated silently */ @UnsupportedAppUsage public void unregisterForNewRingingConnection(Handler h){ mNewRingingConnectionRegistrants.remove(h); } /** * Notifies when an incoming call rings.

* * Messages received from this: * Message.obj will be an AsyncResult * AsyncResult.userObj = obj * AsyncResult.result = a Connection.

*/ public void registerForIncomingRing(Handler h, int what, Object obj){ mIncomingRingRegistrants.addUnique(h, what, obj); } /** * Unregisters for ring notification. * Extraneous calls are tolerated silently */ public void unregisterForIncomingRing(Handler h){ mIncomingRingRegistrants.remove(h); } /** * Notifies when out-band ringback tone is needed.

* * Messages received from this: * Message.obj will be an AsyncResult * AsyncResult.userObj = obj * AsyncResult.result = boolean, true to start play ringback tone * and false to stop.

*/ public void registerForRingbackTone(Handler h, int what, Object obj){ mRingbackToneRegistrants.addUnique(h, what, obj); } /** * Unregisters for ringback tone notification. */ public void unregisterForRingbackTone(Handler h){ mRingbackToneRegistrants.remove(h); } /** * Notifies when out-band on-hold tone is needed.

* * Messages received from this: * Message.obj will be an AsyncResult * AsyncResult.userObj = obj * AsyncResult.result = boolean, true to start play on-hold tone * and false to stop.

*/ public void registerForOnHoldTone(Handler h, int what, Object obj){ mOnHoldToneRegistrants.addUnique(h, what, obj); } /** * Unregisters for on-hold tone notification. */ public void unregisterForOnHoldTone(Handler h){ mOnHoldToneRegistrants.remove(h); } /** * Registers the handler to reset the uplink mute state to get * uplink audio. */ public void registerForResendIncallMute(Handler h, int what, Object obj){ mResendIncallMuteRegistrants.addUnique(h, what, obj); } /** * Unregisters for resend incall mute notifications. */ public void unregisterForResendIncallMute(Handler h){ mResendIncallMuteRegistrants.remove(h); } /** * Register for notifications of initiation of a new MMI code request. * MMI codes for GSM are discussed in 3GPP TS 22.030.

* * Example: If Phone.dial is called with "*#31#", then the app will * be notified here.

* * The returned Message.obj will contain an AsyncResult. * * obj.result will be an "MmiCode" object. */ public void registerForMmiInitiate(Handler h, int what, Object obj){ mMmiInitiateRegistrants.addUnique(h, what, obj); } /** * Unregisters for new MMI initiate notification. * Extraneous calls are tolerated silently */ public void unregisterForMmiInitiate(Handler h){ mMmiInitiateRegistrants.remove(h); } /** * Register for notifications that an MMI request has completed * its network activity and is in its final state. This may mean a state * of COMPLETE, FAILED, or CANCELLED. * * Message.obj will contain an AsyncResult. * obj.result will be an "MmiCode" object */ public void registerForMmiComplete(Handler h, int what, Object obj){ Rlog.d(LOG_TAG, "registerForMmiComplete"); mMmiCompleteRegistrants.addUnique(h, what, obj); } /** * Unregisters for MMI complete notification. * Extraneous calls are tolerated silently */ public void unregisterForMmiComplete(Handler h){ mMmiCompleteRegistrants.remove(h); } /** * Registration point for Ecm timer reset * @param h handler to notify * @param what user-defined message code * @param obj placed in Message.obj */ public void registerForEcmTimerReset(Handler h, int what, Object obj){ mEcmTimerResetRegistrants.addUnique(h, what, obj); } /** * Unregister for notification for Ecm timer reset * @param h Handler to be removed from the registrant list. */ public void unregisterForEcmTimerReset(Handler h){ mEcmTimerResetRegistrants.remove(h); } /** * Register for ServiceState changed. * Message.obj will contain an AsyncResult. * AsyncResult.result will be a ServiceState instance */ public void registerForServiceStateChanged(Handler h, int what, Object obj){ mServiceStateChangedRegistrants.addUnique(h, what, obj); } /** * Unregisters for ServiceStateChange notification. * Extraneous calls are tolerated silently */ public void unregisterForServiceStateChanged(Handler h){ mServiceStateChangedRegistrants.remove(h); } /** * Register for notifications when a supplementary service attempt fails. * Message.obj will contain an AsyncResult. * * @param h Handler that receives the notification message. * @param what User-defined message code. * @param obj User object. */ public void registerForSuppServiceFailed(Handler h, int what, Object obj){ mSuppServiceFailedRegistrants.addUnique(h, what, obj); } /** * Unregister for notifications when a supplementary service attempt fails. * Extraneous calls are tolerated silently * * @param h Handler to be removed from the registrant list. */ public void unregisterForSuppServiceFailed(Handler h){ mSuppServiceFailedRegistrants.remove(h); } /** * Register for notifications when a sInCall VoicePrivacy is enabled * * @param h Handler that receives the notification message. * @param what User-defined message code. * @param obj User object. */ public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj){ mInCallVoicePrivacyOnRegistrants.addUnique(h, what, obj); } /** * Unregister for notifications when a sInCall VoicePrivacy is enabled * * @param h Handler to be removed from the registrant list. */ public void unregisterForInCallVoicePrivacyOn(Handler h){ mInCallVoicePrivacyOnRegistrants.remove(h); } /** * Register for notifications when a sInCall VoicePrivacy is disabled * * @param h Handler that receives the notification message. * @param what User-defined message code. * @param obj User object. */ public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj){ mInCallVoicePrivacyOffRegistrants.addUnique(h, what, obj); } /** * Unregister for notifications when a sInCall VoicePrivacy is disabled * * @param h Handler to be removed from the registrant list. */ public void unregisterForInCallVoicePrivacyOff(Handler h){ mInCallVoicePrivacyOffRegistrants.remove(h); } /** * Register for notifications when CDMA call waiting comes * * @param h Handler that receives the notification message. * @param what User-defined message code. * @param obj User object. */ public void registerForCallWaiting(Handler h, int what, Object obj){ mCallWaitingRegistrants.addUnique(h, what, obj); } /** * Unregister for notifications when CDMA Call waiting comes * @param h Handler to be removed from the registrant list. */ public void unregisterForCallWaiting(Handler h){ mCallWaitingRegistrants.remove(h); } /** * Register for signal information notifications from the network. * Message.obj will contain an AsyncResult. * AsyncResult.result will be a SuppServiceNotification instance. * * @param h Handler that receives the notification message. * @param what User-defined message code. * @param obj User object. */ public void registerForSignalInfo(Handler h, int what, Object obj){ mSignalInfoRegistrants.addUnique(h, what, obj); } /** * Unregisters for signal information notifications. * Extraneous calls are tolerated silently * * @param h Handler to be removed from the registrant list. */ public void unregisterForSignalInfo(Handler h){ mSignalInfoRegistrants.remove(h); } /** * Register for display information notifications from the network. * Message.obj will contain an AsyncResult. * AsyncResult.result will be a SuppServiceNotification instance. * * @param h Handler that receives the notification message. * @param what User-defined message code. * @param obj User object. */ public void registerForDisplayInfo(Handler h, int what, Object obj){ mDisplayInfoRegistrants.addUnique(h, what, obj); } /** * Unregisters for display information notifications. * Extraneous calls are tolerated silently * * @param h Handler to be removed from the registrant list. */ public void unregisterForDisplayInfo(Handler h) { mDisplayInfoRegistrants.remove(h); } /** * Register for notifications when CDMA OTA Provision status change * * @param h Handler that receives the notification message. * @param what User-defined message code. * @param obj User object. */ public void registerForCdmaOtaStatusChange(Handler h, int what, Object obj){ mCdmaOtaStatusChangeRegistrants.addUnique(h, what, obj); } /** * Unregister for notifications when CDMA OTA Provision status change * @param h Handler to be removed from the registrant list. */ public void unregisterForCdmaOtaStatusChange(Handler h){ mCdmaOtaStatusChangeRegistrants.remove(h); } /** * Registration point for subscription info ready * @param h handler to notify * @param what what code of message when delivered * @param obj placed in Message.obj */ public void registerForSubscriptionInfoReady(Handler h, int what, Object obj){ mSubscriptionInfoReadyRegistrants.addUnique(h, what, obj); } /** * Unregister for notifications for subscription info * @param h Handler to be removed from the registrant list. */ public void unregisterForSubscriptionInfoReady(Handler h){ mSubscriptionInfoReadyRegistrants.remove(h); } /** * Sets an event to be fired when the telephony system processes * a post-dial character on an outgoing call.

* * Messages of type what will be sent to h. * The obj field of these Message's will be instances of * AsyncResult. Message.obj.result will be * a Connection object.

* * Message.arg1 will be the post dial character being processed, * or 0 ('\0') if end of string.

* * If Connection.getPostDialState() == WAIT, * the application must call * {@link com.android.internal.telephony.Connection#proceedAfterWaitChar() * Connection.proceedAfterWaitChar()} or * {@link com.android.internal.telephony.Connection#cancelPostDial() * Connection.cancelPostDial()} * for the telephony system to continue playing the post-dial * DTMF sequence.

* * If Connection.getPostDialState() == WILD, * the application must call * {@link com.android.internal.telephony.Connection#proceedAfterWildChar * Connection.proceedAfterWildChar()} * or * {@link com.android.internal.telephony.Connection#cancelPostDial() * Connection.cancelPostDial()} * for the telephony system to continue playing the * post-dial DTMF sequence.

* */ public void registerForPostDialCharacter(Handler h, int what, Object obj){ mPostDialCharacterRegistrants.addUnique(h, what, obj); } public void unregisterForPostDialCharacter(Handler h){ mPostDialCharacterRegistrants.remove(h); } /** * Register for TTY mode change notifications from the network. * Message.obj will contain an AsyncResult. * AsyncResult.result will be an Integer containing new mode. * * @param h Handler that receives the notification message. * @param what User-defined message code. * @param obj User object. */ public void registerForTtyModeReceived(Handler h, int what, Object obj){ mTtyModeReceivedRegistrants.addUnique(h, what, obj); } /** * Unregisters for TTY mode change notifications. * Extraneous calls are tolerated silently * * @param h Handler to be removed from the registrant list. */ public void unregisterForTtyModeReceived(Handler h) { mTtyModeReceivedRegistrants.remove(h); } /* APIs to access foregroudCalls, backgroudCalls, and ringingCalls * 1. APIs to access list of calls * 2. APIs to check if any active call, which has connection other than * disconnected ones, pleaser refer to Call.isIdle() * 3. APIs to return first active call * 4. APIs to return the connections of first active call * 5. APIs to return other property of first active call */ /** * @return list of all ringing calls */ @UnsupportedAppUsage public List getRingingCalls() { return Collections.unmodifiableList(mRingingCalls); } /** * @return list of all foreground calls */ public List getForegroundCalls() { return Collections.unmodifiableList(mForegroundCalls); } /** * @return list of all background calls */ @UnsupportedAppUsage public List getBackgroundCalls() { return Collections.unmodifiableList(mBackgroundCalls); } /** * Return true if there is at least one active foreground call */ @UnsupportedAppUsage public boolean hasActiveFgCall() { return (getFirstActiveCall(mForegroundCalls) != null); } /** * Return true if there is at least one active foreground call * on a particular subId or an active sip call */ @UnsupportedAppUsage public boolean hasActiveFgCall(int subId) { return (getFirstActiveCall(mForegroundCalls, subId) != null); } /** * Return true if there is at least one active background call */ @UnsupportedAppUsage public boolean hasActiveBgCall() { // TODO since hasActiveBgCall may get called often // better to cache it to improve performance return (getFirstActiveCall(mBackgroundCalls) != null); } /** * Return true if there is at least one active background call * on a particular subId or an active sip call */ @UnsupportedAppUsage public boolean hasActiveBgCall(int subId) { // TODO since hasActiveBgCall may get called often // better to cache it to improve performance return (getFirstActiveCall(mBackgroundCalls, subId) != null); } /** * Return true if there is at least one active ringing call * */ public boolean hasActiveRingingCall() { return (getFirstActiveCall(mRingingCalls) != null); } /** * Return true if there is at least one active ringing call */ @UnsupportedAppUsage public boolean hasActiveRingingCall(int subId) { return (getFirstActiveCall(mRingingCalls, subId) != null); } /** * return the active foreground call from foreground calls * * Active call means the call is NOT in Call.State.IDLE * * 1. If there is active foreground call, return it * 2. If there is no active foreground call, return the * foreground call associated with default phone, which state is IDLE. * 3. If there is no phone registered at all, return null. * */ public Call getActiveFgCall() { Call call = getFirstNonIdleCall(mForegroundCalls); if (call == null) { call = (mDefaultPhone == null) ? null : mDefaultPhone.getForegroundCall(); } return call; } @UnsupportedAppUsage public Call getActiveFgCall(int subId) { Call call = getFirstNonIdleCall(mForegroundCalls, subId); if (call == null) { Phone phone = getPhone(subId); call = (phone == null) ? null : phone.getForegroundCall(); } return call; } // Returns the first call that is not in IDLE state. If both active calls // and disconnecting/disconnected calls exist, return the first active call. private Call getFirstNonIdleCall(List calls) { Call result = null; for (Call call : calls) { if (!call.isIdle()) { return call; } else if (call.getState() != Call.State.IDLE) { if (result == null) result = call; } } return result; } // Returns the first call that is not in IDLE state. If both active calls // and disconnecting/disconnected calls exist, return the first active call. private Call getFirstNonIdleCall(List calls, int subId) { Call result = null; for (Call call : calls) { if ((call.getPhone().getSubId() == subId) || (call.getPhone() instanceof SipPhone)) { if (!call.isIdle()) { return call; } else if (call.getState() != Call.State.IDLE) { if (result == null) result = call; } } } return result; } /** * return one active background call from background calls * * Active call means the call is NOT idle defined by Call.isIdle() * * 1. If there is only one active background call, return it * 2. If there is more than one active background call, return the first one * 3. If there is no active background call, return the background call * associated with default phone, which state is IDLE. * 4. If there is no background call at all, return null. * * Complete background calls list can be get by getBackgroundCalls() */ @UnsupportedAppUsage public Call getFirstActiveBgCall() { Call call = getFirstNonIdleCall(mBackgroundCalls); if (call == null) { call = (mDefaultPhone == null) ? null : mDefaultPhone.getBackgroundCall(); } return call; } /** * return one active background call from background calls of the * requested subId. * * Active call means the call is NOT idle defined by Call.isIdle() * * 1. If there is only one active background call on given sub or * on SIP Phone, return it * 2. If there is more than one active background call, return the background call * associated with the active sub. * 3. If there is no background call at all, return null. * * Complete background calls list can be get by getBackgroundCalls() */ @UnsupportedAppUsage public Call getFirstActiveBgCall(int subId) { Phone phone = getPhone(subId); if (hasMoreThanOneHoldingCall(subId)) { return phone.getBackgroundCall(); } else { Call call = getFirstNonIdleCall(mBackgroundCalls, subId); if (call == null) { call = (phone == null) ? null : phone.getBackgroundCall(); } return call; } } /** * return one active ringing call from ringing calls * * Active call means the call is NOT idle defined by Call.isIdle() * * 1. If there is only one active ringing call, return it * 2. If there is more than one active ringing call, return the first one * 3. If there is no active ringing call, return the ringing call * associated with default phone, which state is IDLE. * 4. If there is no ringing call at all, return null. * * Complete ringing calls list can be get by getRingingCalls() */ @UnsupportedAppUsage public Call getFirstActiveRingingCall() { Call call = getFirstNonIdleCall(mRingingCalls); if (call == null) { call = (mDefaultPhone == null) ? null : mDefaultPhone.getRingingCall(); } return call; } @UnsupportedAppUsage public Call getFirstActiveRingingCall(int subId) { Phone phone = getPhone(subId); Call call = getFirstNonIdleCall(mRingingCalls, subId); if (call == null) { call = (phone == null) ? null : phone.getRingingCall(); } return call; } /** * @return the state of active foreground call * return IDLE if there is no active foreground call */ public Call.State getActiveFgCallState() { Call fgCall = getActiveFgCall(); if (fgCall != null) { return fgCall.getState(); } return Call.State.IDLE; } @UnsupportedAppUsage public Call.State getActiveFgCallState(int subId) { Call fgCall = getActiveFgCall(subId); if (fgCall != null) { return fgCall.getState(); } return Call.State.IDLE; } /** * @return the connections of active foreground call * return empty list if there is no active foreground call */ @UnsupportedAppUsage public List getFgCallConnections() { Call fgCall = getActiveFgCall(); if ( fgCall != null) { return fgCall.getConnections(); } return mEmptyConnections; } /** * @return the connections of active foreground call * return empty list if there is no active foreground call */ public List getFgCallConnections(int subId) { Call fgCall = getActiveFgCall(subId); if ( fgCall != null) { return fgCall.getConnections(); } return mEmptyConnections; } /** * @return the connections of active background call * return empty list if there is no active background call */ @UnsupportedAppUsage public List getBgCallConnections() { Call bgCall = getFirstActiveBgCall(); if ( bgCall != null) { return bgCall.getConnections(); } return mEmptyConnections; } /** * @return true if there is at least one Foreground call in disconnected state */ public boolean hasDisconnectedFgCall() { return (getFirstCallOfState(mForegroundCalls, Call.State.DISCONNECTED) != null); } /** * @return true if there is at least one Foreground call in disconnected state */ public boolean hasDisconnectedFgCall(int subId) { return (getFirstCallOfState(mForegroundCalls, Call.State.DISCONNECTED, subId) != null); } /** * @return true if there is at least one background call in disconnected state */ public boolean hasDisconnectedBgCall() { return (getFirstCallOfState(mBackgroundCalls, Call.State.DISCONNECTED) != null); } /** * @return true if there is at least one background call in disconnected state */ public boolean hasDisconnectedBgCall(int subId) { return (getFirstCallOfState(mBackgroundCalls, Call.State.DISCONNECTED, subId) != null); } /** * @return the first active call from a call list */ private Call getFirstActiveCall(ArrayList calls) { for (Call call : calls) { if (!call.isIdle()) { return call; } } return null; } /** * @return the first active call from a call list */ private Call getFirstActiveCall(ArrayList calls, int subId) { for (Call call : calls) { if ((!call.isIdle()) && ((call.getPhone().getSubId() == subId) || (call.getPhone() instanceof SipPhone))) { return call; } } return null; } /** * @return the first call in a the Call.state from a call list */ private Call getFirstCallOfState(ArrayList calls, Call.State state) { for (Call call : calls) { if (call.getState() == state) { return call; } } return null; } /** * @return the first call in a the Call.state from a call list */ private Call getFirstCallOfState(ArrayList calls, Call.State state, int subId) { for (Call call : calls) { if ((call.getState() == state) || ((call.getPhone().getSubId() == subId) || (call.getPhone() instanceof SipPhone))) { return call; } } return null; } @UnsupportedAppUsage private boolean hasMoreThanOneRingingCall() { int count = 0; for (Call call : mRingingCalls) { if (call.getState().isRinging()) { if (++count > 1) return true; } } return false; } /** * @return true if more than one active ringing call exists on * the active subId. * This checks for the active calls on provided * subId and also active calls on SIP Phone. * */ @UnsupportedAppUsage private boolean hasMoreThanOneRingingCall(int subId) { int count = 0; for (Call call : mRingingCalls) { if ((call.getState().isRinging()) && ((call.getPhone().getSubId() == subId) || (call.getPhone() instanceof SipPhone))) { if (++count > 1) return true; } } return false; } /** * @return true if more than one active background call exists on * the provided subId. * This checks for the background calls on provided * subId and also background calls on SIP Phone. * */ private boolean hasMoreThanOneHoldingCall(int subId) { int count = 0; for (Call call : mBackgroundCalls) { if ((call.getState() == Call.State.HOLDING) && ((call.getPhone().getSubId() == subId) || (call.getPhone() instanceof SipPhone))) { if (++count > 1) return true; } } return false; } private class CallManagerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case EVENT_DISCONNECT: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_DISCONNECT)"); mDisconnectRegistrants.notifyRegistrants((AsyncResult) msg.obj); // FIXME Taken from klp-sprout-dev but setAudioMode was removed in L. //mIsEccDialing = false; break; case EVENT_PRECISE_CALL_STATE_CHANGED: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_PRECISE_CALL_STATE_CHANGED)"); mPreciseCallStateRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_NEW_RINGING_CONNECTION: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_NEW_RINGING_CONNECTION)"); Connection c = (Connection) ((AsyncResult) msg.obj).result; int subId = c.getCall().getPhone().getSubId(); if (getActiveFgCallState(subId).isDialing() || hasMoreThanOneRingingCall()) { try { Rlog.d(LOG_TAG, "silently drop incoming call: " + c.getCall()); c.getCall().hangup(); } catch (CallStateException e) { Rlog.w(LOG_TAG, "new ringing connection", e); } } else { mNewRingingConnectionRegistrants.notifyRegistrants((AsyncResult) msg.obj); } break; case EVENT_UNKNOWN_CONNECTION: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_UNKNOWN_CONNECTION)"); mUnknownConnectionRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_INCOMING_RING: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_INCOMING_RING)"); // The event may come from RIL who's not aware of an ongoing fg call if (!hasActiveFgCall()) { mIncomingRingRegistrants.notifyRegistrants((AsyncResult) msg.obj); } break; case EVENT_RINGBACK_TONE: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_RINGBACK_TONE)"); mRingbackToneRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_IN_CALL_VOICE_PRIVACY_ON: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_IN_CALL_VOICE_PRIVACY_ON)"); mInCallVoicePrivacyOnRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_IN_CALL_VOICE_PRIVACY_OFF: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_IN_CALL_VOICE_PRIVACY_OFF)"); mInCallVoicePrivacyOffRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_CALL_WAITING: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_CALL_WAITING)"); mCallWaitingRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_DISPLAY_INFO: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_DISPLAY_INFO)"); mDisplayInfoRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_SIGNAL_INFO: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_SIGNAL_INFO)"); mSignalInfoRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_CDMA_OTA_STATUS_CHANGE: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_CDMA_OTA_STATUS_CHANGE)"); mCdmaOtaStatusChangeRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_RESEND_INCALL_MUTE: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_RESEND_INCALL_MUTE)"); mResendIncallMuteRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_MMI_INITIATE: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_MMI_INITIATE)"); mMmiInitiateRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_MMI_COMPLETE: Rlog.d(LOG_TAG, "CallManager: handleMessage (EVENT_MMI_COMPLETE)"); mMmiCompleteRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_ECM_TIMER_RESET: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_ECM_TIMER_RESET)"); mEcmTimerResetRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_SUBSCRIPTION_INFO_READY: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_SUBSCRIPTION_INFO_READY)"); mSubscriptionInfoReadyRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_SUPP_SERVICE_FAILED: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_SUPP_SERVICE_FAILED)"); mSuppServiceFailedRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_SERVICE_STATE_CHANGED: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_SERVICE_STATE_CHANGED)"); mServiceStateChangedRegistrants.notifyRegistrants((AsyncResult) msg.obj); // FIXME Taken from klp-sprout-dev but setAudioMode was removed in L. //setAudioMode(); break; case EVENT_POST_DIAL_CHARACTER: // we need send the character that is being processed in msg.arg1 // so can't use notifyRegistrants() if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_POST_DIAL_CHARACTER)"); for(int i=0; i < mPostDialCharacterRegistrants.size(); i++) { Message notifyMsg; notifyMsg = ((Registrant)mPostDialCharacterRegistrants.get(i)).messageForRegistrant(); notifyMsg.obj = msg.obj; notifyMsg.arg1 = msg.arg1; notifyMsg.sendToTarget(); } break; case EVENT_ONHOLD_TONE: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_ONHOLD_TONE)"); mOnHoldToneRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; case EVENT_TTY_MODE_RECEIVED: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_TTY_MODE_RECEIVED)"); mTtyModeReceivedRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; /* FIXME Taken from klp-sprout-dev but setAudioMode was removed in L. case EVENT_RADIO_OFF_OR_NOT_AVAILABLE: if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_RADIO_OFF_OR_NOT_AVAILABLE)"); setAudioMode(); break; */ } } }; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy