Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 91c714c0 authored by Tyler Gunn's avatar Tyler Gunn
Browse files

Finalizing Multiendpoint functionality.

1) Finish plumbing of PULLING call state.
2) b/29522023 - Handle case where the maximum number of calls across all
devices has been reached and a new call cannot be placed; report a new
Telephony disconnect cause which maps to an appropriate user friendly
message.
3) b/29633039 - Make the ImsExternalCallTracker keep track of the last
call pull state for each external call.  Also, track whether the device is
in a call, and whether video is enabled on the device.  Use this to enforce
the rules that an external video call cannot be pulled if video is not
enabled on the device, and that calls cannot be pulled when there is an
active call on the device.
4) Fixed a circular initialization dependency between ImsPhoneCallTracker
and ImsExternalCallTracker.  Previously, the ImsPhoneCallTracker would
spool up the ImsMultiEndpoint service and set the ImsExternalCallTracker
as a listener.  HOWEVER, the ImsEXternalCallTracker isn't actually
created until AFTER the ImsPhoneCallTracker is.  It just so happens the
ImsPhoneCallTracker init happens on a thread, so ImsExternalCallTracker
would normally be initialized before ImsPhoneCallTracker.  Whoops.

Bug: 29522023
Bug: 29633039
Change-Id: Iddabb61dcad79c93665c28e07b333e807cbc576d
parent b6629823
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
@@ -188,6 +188,12 @@ public abstract class Connection {
    private boolean mAnsweringDisconnectsActiveCall;
    private boolean mAllowAddCallDuringVideoCall;

    /**
     * Used to indicate that this originated from pulling a {@link android.telecom.Connection} with
     * {@link android.telecom.Connection#PROPERTY_IS_EXTERNAL_CALL}.
     */
    private boolean mIsPulledCall = false;

    protected Connection(int phoneType) {
        mPhoneType = phoneType;
    }
@@ -806,6 +812,21 @@ public abstract class Connection {
        mAllowAddCallDuringVideoCall = allowAddCallDuringVideoCall;
    }

    /**
     * Sets whether the connection is the result of an external call which was pulled to the local
     * device.
     *
     * @param isPulledCall {@code true} if this connection is the result of pulling an external call
     *      to the local device.
     */
    public void setIsPulledCall(boolean isPulledCall) {
        mIsPulledCall = isPulledCall;
    }

    public boolean isPulledCall() {
        return mIsPulledCall;
    }

    /**
     * Sets the call substate for the current connection and reports the changes to all listeners.
     * Valid call substates are defined in {@link android.telecom.Connection}.
+2 −3
Original line number Diff line number Diff line
@@ -118,10 +118,9 @@ public class TelephonyComponentFactory {
        return new ImsPhoneCallTracker(imsPhone);
    }

    public ImsExternalCallTracker makeImsExternalCallTracker(ImsPhone imsPhone,
            ImsPullCall callPuller) {
    public ImsExternalCallTracker makeImsExternalCallTracker(ImsPhone imsPhone) {

        return new ImsExternalCallTracker(imsPhone, callPuller);
        return new ImsExternalCallTracker(imsPhone);
    }

    public CdmaSubscriptionSourceManager
+192 −14
Original line number Diff line number Diff line
@@ -23,13 +23,18 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;

import android.os.AsyncResult;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.telecom.PhoneAccountHandle;
import android.telecom.VideoProfile;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Log;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -37,7 +42,7 @@ import java.util.Map;
/**
 * Responsible for tracking external calls known to the system.
 */
public class ImsExternalCallTracker {
public class ImsExternalCallTracker implements ImsPhoneCallTracker.PhoneStateListener {

    /**
     * Interface implemented by modules which are capable of notifying interested parties of new
@@ -47,7 +52,6 @@ public class ImsExternalCallTracker {
     *
     * @hide
     */

    public static interface ImsCallNotify {
        /**
         * Notifies that an unknown connection has been added.
@@ -80,12 +84,18 @@ public class ImsExternalCallTracker {
        @Override
        public void onPullExternalCall(ImsExternalConnection connection) {
            Log.d(TAG, "onPullExternalCall: connection = " + connection);
            if (mCallPuller == null) {
                Log.e(TAG, "onPullExternalCall : No call puller defined");
                return;
            }
            mCallPuller.pullExternalCall(connection.getAddress(), connection.getVideoState());
        }
    }

    public final static String TAG = "ImsExternalCallTracker";

    private static final int EVENT_VIDEO_CAPABILITIES_CHANGED = 1;

    /**
     * Extra key used when informing telecom of a new external call using the
     * {@link android.telecom.TelecomManager#addNewUnknownCall(PhoneAccountHandle, Bundle)} API.
@@ -97,18 +107,45 @@ public class ImsExternalCallTracker {
            "android.telephony.ImsExternalCallTracker.extra.EXTERNAL_CALL_ID";

    /**
     * Contains a list of the external connections known by the ImsPhoneCallTracker.  These are
     * Contains a list of the external connections known by the ImsExternalCallTracker.  These are
     * connections which originated from a dialog event package and reside on another device.
     * Used in multi-endpoint (VoLTE for internet connected endpoints) scenarios.
     */
    private Map<Integer, ImsExternalConnection> mExternalConnections =
            new ArrayMap<>();

    /**
     * Tracks whether each external connection tracked in
     * {@link #mExternalConnections} can be pulled, as reported by the latest dialog event package
     * received from the network.  We need to know this because the pull state of a call can be
     * overridden based on the following factors:
     * 1) An external video call cannot be pulled if the current device does not have video
     *    capability.
     * 2) If the device has any active or held calls locally, no external calls may be pulled to
     *    the local device.
     */
    private Map<Integer, Boolean> mExternalCallPullableState = new ArrayMap<>();
    private final ImsPhone mPhone;
    private final ImsCallNotify mCallStateNotifier;
    private final ExternalCallStateListener mExternalCallStateListener;
    private final ExternalConnectionListener mExternalConnectionListener =
            new ExternalConnectionListener();
    private final ImsPullCall mCallPuller;
    private ImsPullCall mCallPuller;
    private boolean mIsVideoCapable;
    private boolean mHasActiveCalls;

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case EVENT_VIDEO_CAPABILITIES_CHANGED:
                    handleVideoCapabilitiesChanged((AsyncResult) msg.obj);
                    break;
                default:
                    break;
            }
        }
    };

    @VisibleForTesting
    public ImsExternalCallTracker(ImsPhone phone, ImsPullCall callPuller,
@@ -118,9 +155,9 @@ public class ImsExternalCallTracker {
        mCallStateNotifier = callNotifier;
        mExternalCallStateListener = new ExternalCallStateListener();
        mCallPuller = callPuller;

    }
    public ImsExternalCallTracker(ImsPhone phone, ImsPullCall callPuller) {

    public ImsExternalCallTracker(ImsPhone phone) {
        mPhone = phone;
        mCallStateNotifier = new ImsCallNotify() {
            @Override
@@ -134,6 +171,22 @@ public class ImsExternalCallTracker {
            }
        };
        mExternalCallStateListener = new ExternalCallStateListener();
        registerForNotifications();
    }

    /**
     * Performs any cleanup required before the ImsExternalCallTracker is destroyed.
     */
    public void tearDown() {
        unregisterForNotifications();
    }

    /**
     * Sets the implementation of {@link ImsPullCall} which is responsible for pulling calls.
     *
     * @param callPuller The pull call implementation.
     */
    public void setCallPuller(ImsPullCall callPuller) {
       mCallPuller = callPuller;
    }

@@ -141,6 +194,42 @@ public class ImsExternalCallTracker {
        return mExternalCallStateListener;
    }

    /**
     * Handles changes to the phone state as notified by the {@link ImsPhoneCallTracker}.
     *
     * @param oldState The previous phone state.
     * @param newState The new phone state.
     */
    @Override
    public void onPhoneStateChanged(PhoneConstants.State oldState, PhoneConstants.State newState) {
        mHasActiveCalls = newState != PhoneConstants.State.IDLE;
        Log.i(TAG, "onPhoneStateChanged : hasActiveCalls = " + mHasActiveCalls);

        refreshCallPullState();
    }

    /**
     * Registers for video capability changes.
     */
    private void registerForNotifications() {
        if (mPhone != null) {
            Log.d(TAG, "Registering: " + mPhone);
            mPhone.getDefaultPhone().registerForVideoCapabilityChanged(mHandler,
                    EVENT_VIDEO_CAPABILITIES_CHANGED, null);
        }
    }

    /**
     * Unregisters for video capability changes.
     */
    private void unregisterForNotifications() {
        if (mPhone != null) {
            Log.d(TAG, "Unregistering: " + mPhone);
            mPhone.unregisterForVideoCapabilityChanged(mHandler);
        }
    }


    /**
     * Called when the IMS stack receives a new dialog event package.  Triggers the creation and
     * update of {@link ImsExternalConnection}s to represent the dialogs in the dialog event
@@ -212,17 +301,30 @@ public class ImsExternalCallTracker {
     * @param state External call state from a dialog event package.
     */
    private void createExternalConnection(ImsExternalCallState state) {
        Log.i(TAG, "createExternalConnection");
        Log.i(TAG, "createExternalConnection : state = " + state);

        int videoState = ImsCallProfile.getVideoStateFromCallType(state.getCallType());

        boolean isCallPullPermitted = isCallPullPermitted(state.isCallPullable(), videoState);
        ImsExternalConnection connection = new ImsExternalConnection(mPhone,
                state.getCallId(), /* Dialog event package call id */
                state.getAddress() /* phone number */,
                state.isCallPullable());
        connection.setVideoState(ImsCallProfile.getVideoStateFromCallType(state.getCallType()));
                isCallPullPermitted);
        connection.setVideoState(videoState);
        connection.addListener(mExternalConnectionListener);

        Log.d(TAG,
                "createExternalConnection - pullable state : externalCallId = "
                        + connection.getCallId()
                        + " ; isPullable = " + isCallPullPermitted
                        + " ; networkPullable = " + state.isCallPullable()
                        + " ; isVideo = " + VideoProfile.isVideo(videoState)
                        + " ; videoEnabled = " + mIsVideoCapable
                        + " ; hasActiveCalls = " + mHasActiveCalls);

        // Add to list of tracked connections.
        mExternalConnections.put(connection.getCallId(), connection);
        mExternalCallPullableState.put(connection.getCallId(), isCallPullPermitted);

        // Note: The notification of unknown connection is ultimately handled by
        // PstnIncomingCallNotifier#addNewUnknownCall.  That method will ensure that an extra is set
@@ -240,6 +342,8 @@ public class ImsExternalCallTracker {
     */
    private void updateExistingConnection(ImsExternalConnection connection,
            ImsExternalCallState state) {

        Log.i(TAG, "updateExistingConnection : state = " + state);
        Call.State existingState = connection.getState();
        Call.State newState = state.getCallState() == ImsExternalCallState.CALL_STATE_CONFIRMED ?
                Call.State.ACTIVE : Call.State.DISCONNECTED;
@@ -250,17 +354,53 @@ public class ImsExternalCallTracker {
            } else {
                connection.setTerminated();
                connection.removeListener(mExternalConnectionListener);
                mExternalConnections.remove(connection);
                mExternalConnections.remove(connection.getCallId());
                mExternalCallPullableState.remove(connection.getCallId());
                mCallStateNotifier.notifyPreciseCallStateChanged();
            }
        }

        connection.setIsPullable(state.isCallPullable());

        int newVideoState = ImsCallProfile.getVideoStateFromCallType(state.getCallType());
        if (newVideoState != connection.getVideoState()) {
            connection.setVideoState(newVideoState);
        }

        boolean isCallPullPermitted = isCallPullPermitted(state.isCallPullable(), newVideoState);
        Log.d(TAG,
                "updateExistingConnection - pullable state : externalCallId = " + connection.getCallId()
                        + " ; isPullable = " + isCallPullPermitted
                        + " ; networkPullable = " + state.isCallPullable()
                        + " ; isVideo = "
                        + VideoProfile.isVideo(connection.getVideoState())
                        + " ; videoEnabled = " + mIsVideoCapable
                        + " ; hasActiveCalls = " + mHasActiveCalls);

        connection.setIsPullable(isCallPullPermitted);
    }

    /**
     * Update whether the external calls known can be pulled.  Combines the last known network
     * pullable state with local device conditions to determine if each call can be pulled.
     */
    private void refreshCallPullState() {
        Log.d(TAG, "refreshCallPullState");

        for (ImsExternalConnection imsExternalConnection : mExternalConnections.values()) {
            boolean isNetworkPullable =
                    mExternalCallPullableState.get(imsExternalConnection.getCallId())
                            .booleanValue();
            boolean isCallPullPermitted =
                    isCallPullPermitted(isNetworkPullable, imsExternalConnection.getVideoState());
            Log.d(TAG,
                    "refreshCallPullState : externalCallId = " + imsExternalConnection.getCallId()
                            + " ; isPullable = " + isCallPullPermitted
                            + " ; networkPullable = " + isNetworkPullable
                            + " ; isVideo = "
                            + VideoProfile.isVideo(imsExternalConnection.getVideoState())
                            + " ; videoEnabled = " + mIsVideoCapable
                            + " ; hasActiveCalls = " + mHasActiveCalls);
            imsExternalConnection.setIsPullable(isCallPullPermitted);
        }
    }

    /**
@@ -284,4 +424,42 @@ public class ImsExternalCallTracker {

        return false;
    }

    /**
     * Handles a change to the video capabilities reported by
     * {@link Phone#notifyForVideoCapabilityChanged(boolean)}.
     *
     * @param ar The AsyncResult containing the new video capability of the device.
     */
    private void handleVideoCapabilitiesChanged(AsyncResult ar) {
        mIsVideoCapable = (Boolean) ar.result;
        Log.i(TAG, "handleVideoCapabilitiesChanged : isVideoCapable = " + mIsVideoCapable);

        // Refresh pullable state if video capability changed.
        refreshCallPullState();
    }

    /**
     * Determines whether an external call can be pulled based on the pullability state enforced
     * by the network, as well as local device rules.
     *
     * @param isNetworkPullable {@code true} if the network indicates the call can be pulled,
     *      {@code false} otherwise.
     * @param videoState the VideoState of the external call.
     * @return {@code true} if the external call can be pulled, {@code false} otherwise.
     */
    private boolean isCallPullPermitted(boolean isNetworkPullable, int videoState) {
        if (VideoProfile.isVideo(videoState) && !mIsVideoCapable) {
            // If the external call is a video call and the local device does not have video
            // capability at this time, it cannot be pulled.
            return false;
        }

        if (mHasActiveCalls) {
            // If there are active calls on the local device, the call cannot be pulled.
            return false;
        }

        return isNetworkPullable;
    }
}
+10 −2
Original line number Diff line number Diff line
@@ -183,9 +183,15 @@ public class ImsPhone extends ImsPhoneBase {
        super("ImsPhone", context, notifier, unitTestMode);

        mDefaultPhone = defaultPhone;
        mCT = TelephonyComponentFactory.getInstance().makeImsPhoneCallTracker(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().makeImsExternalCallTracker(this, mCT);
                TelephonyComponentFactory.getInstance().makeImsExternalCallTracker(this);
        mCT = TelephonyComponentFactory.getInstance().makeImsPhoneCallTracker(this);
        mCT.registerPhoneStateListener(mExternalCallTracker);
        mExternalCallTracker.setCallPuller(mCT);

        mSS.setStateOff();

@@ -215,6 +221,8 @@ public class ImsPhone extends ImsPhoneBase {
        // Nothing to dispose in Phone
        //super.dispose();
        mPendingMMIs.clear();
        mExternalCallTracker.tearDown();
        mCT.unregisterPhoneStateListener(mExternalCallTracker);
        mCT.dispose();

        //Force all referenced classes to unregister their former registered events
+107 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -49,6 +50,8 @@ import android.telephony.PhoneNumberUtils;
import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.util.ArrayMap;
import android.util.Pair;

import com.android.ims.ImsCall;
import com.android.ims.ImsCallProfile;
@@ -83,6 +86,10 @@ import com.android.internal.telephony.gsm.SuppServiceNotification;
public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
    static final String LOG_TAG = "ImsPhoneCallTracker";

    public interface PhoneStateListener {
        void onPhoneStateChanged(PhoneConstants.State oldState, PhoneConstants.State newState);
    }

    private static final boolean DBG = true;

    // When true, dumps the state of ImsPhoneCallTracker after changes to foreground and background
@@ -237,6 +244,12 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
    private ImsCall mCallExpectedToResume = null;
    private boolean mAllowEmergencyVideoCalls = false;

    /**
     * Listeners to changes in the phone state.  Intended for use by other interested IMS components
     * without the need to register a full blown {@link android.telephony.PhoneStateListener}.
     */
    private List<PhoneStateListener> mPhoneStateListeners = new ArrayList<>();

    /**
     * Carrier configuration option which determines if video calls which have been downgraded to an
     * audio call should be treated as if they are still video calls.
@@ -255,6 +268,15 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
     */
    private boolean mAllowAddCallDuringVideoCall = true;

    /**
     * Carrier configuration option which defines a mapping from pairs of
     * {@link ImsReasonInfo#getCode()} and {@link ImsReasonInfo#getExtraMessage()} values to a new
     * {@code ImsReasonInfo#CODE_*} value.
     *
     * See {@link CarrierConfigManager#KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY}.
     */
    private Map<Pair<Integer, String>, Integer> mImsReasonCodeMap = new ArrayMap<>();

    //***** Events


@@ -501,6 +523,31 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
        mAllowAddCallDuringVideoCall =
                carrierConfig.getBoolean(
                        CarrierConfigManager.KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL);

        String[] mappings = carrierConfig
                .getStringArray(CarrierConfigManager.KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY);
        if (mappings != null && mappings.length > 0) {
            for (String mapping : mappings) {
                String[] values = mapping.split(Pattern.quote("|"));
                if (values.length != 3) {
                    continue;
                }

                try {
                    int fromCode = Integer.parseInt(values[0]);
                    String message = values[1];
                    int toCode = Integer.parseInt(values[2]);

                    mImsReasonCodeMap.put(new Pair<>(fromCode, message), toCode);
                    log("Loaded ImsReasonInfo mapping : fromCode = " + fromCode + " ; message = " +
                            message + " ; toCode = " + toCode);
                } catch (NumberFormatException nfe) {
                    loge("Invalid ImsReasonInfo mapping found: " + mapping);
                }
            }
        } else {
            log("No carrier ImsReasonInfo mappings defined.");
        }
    }

    private void handleEcmTimer(int action) {
@@ -557,6 +604,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
                if (intentExtras.containsKey(ImsCallProfile.EXTRA_IS_CALL_PULL)) {
                    profile.mCallExtras.putBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL,
                            intentExtras.getBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL));
                    conn.setIsPulledCall(true);
                }

                // Pack the OEM-specific call extras.
@@ -831,6 +879,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
        if (mState != oldState) {
            mPhone.notifyPhoneStateChanged();
            mEventLog.writePhoneState(mState);
            notifyPhoneStateChanged(oldState, mState);
        }
    }

@@ -1196,11 +1245,35 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
        }
    }

    /**
     * Returns the {@link ImsReasonInfo#getCode()}, potentially remapping to a new value based on
     * the {@link ImsReasonInfo#getCode()} and {@link ImsReasonInfo#getExtraMessage()}.
     *
     * See {@link #mImsReasonCodeMap}.
     *
     * @param reasonInfo The {@link ImsReasonInfo}.
     * @return The remapped code.
     */
    private int maybeRemapReasonCode(ImsReasonInfo reasonInfo) {
        int code = reasonInfo.getCode();

        Pair<Integer, String> toCheck = new Pair<>(code, reasonInfo.getExtraMessage());

        if (mImsReasonCodeMap.containsKey(toCheck)) {
            int toCode = mImsReasonCodeMap.get(toCheck);

            log("maybeRemapReasonCode : fromCode = " + reasonInfo.getCode() + " ; message = "
                    + reasonInfo.getExtraMessage() + " ; toCode = " + toCode);
            return toCode;
        }
        return code;
    }

    private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
        int cause = DisconnectCause.ERROR_UNSPECIFIED;

        //int type = reasonInfo.getReasonType();
        int code = reasonInfo.getCode();
        int code = maybeRemapReasonCode(reasonInfo);
        switch (code) {
            case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
            case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
@@ -1213,14 +1286,19 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
                return DisconnectCause.LOCAL;

            case ImsReasonInfo.CODE_LOCAL_CALL_DECLINE:
            case ImsReasonInfo.CODE_REMOTE_CALL_DECLINE:
                // If the call has been declined locally (on this device), or on remotely (on
                // another device using multiendpoint functionality), mark it as rejected.
                return DisconnectCause.INCOMING_REJECTED;

            case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
                return DisconnectCause.NORMAL;

            case ImsReasonInfo.CODE_SIP_FORBIDDEN:
                return DisconnectCause.SERVER_ERROR;

            case ImsReasonInfo.CODE_SIP_REDIRECTED:
            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
            case ImsReasonInfo.CODE_SIP_FORBIDDEN:
            case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
            case ImsReasonInfo.CODE_SIP_USER_REJECTED:
            case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
@@ -1259,6 +1337,9 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {

            case ImsReasonInfo.CODE_CALL_END_CAUSE_CALL_PULL:
                return DisconnectCause.CALL_PULLED;

            case ImsReasonInfo.CODE_MAXIMUM_NUMBER_OF_CALLS_REACHED:
                return DisconnectCause.MAXIMUM_NUMBER_OF_CALLS_REACHED;
            default:
        }

@@ -2355,4 +2436,28 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {

        return mTotalVtDataUsage;
    }

    public void registerPhoneStateListener(PhoneStateListener listener) {
        mPhoneStateListeners.add(listener);
    }

    public void unregisterPhoneStateListener(PhoneStateListener listener) {
        mPhoneStateListeners.remove(listener);
    }

    /**
     * Notifies local telephony listeners of changes to the IMS phone state.
     *
     * @param oldState The old state.
     * @param newState The new state.
     */
    private void notifyPhoneStateChanged(PhoneConstants.State oldState,
            PhoneConstants.State newState) {

        for (PhoneStateListener listener : mPhoneStateListeners) {
            listener.onPhoneStateChanged(oldState, newState);
        }
    }


}