Merge kwd to master
Wink Saville [Wed, 11 Jun 2014 15:39:38 +0000 (08:39 -0700)]
Change-Id: Idb607c0aa32f80fe4fe1539aedea7a221e9e7f04

36 files changed:
Android.mk [new file with mode: 0644]
CleanSpec.mk [new file with mode: 0644]
README.txt [new file with mode: 0644]
src/java/com/android/ims/ImsCall.java [new file with mode: 0644]
src/java/com/android/ims/ImsCallForwardInfo.aidl [new file with mode: 0644]
src/java/com/android/ims/ImsCallForwardInfo.java [new file with mode: 0644]
src/java/com/android/ims/ImsCallGroup.java [new file with mode: 0644]
src/java/com/android/ims/ImsCallProfile.aidl [new file with mode: 0644]
src/java/com/android/ims/ImsCallProfile.java [new file with mode: 0644]
src/java/com/android/ims/ImsConferenceState.aidl [new file with mode: 0644]
src/java/com/android/ims/ImsConferenceState.java [new file with mode: 0644]
src/java/com/android/ims/ImsConnectionStateListener.java [new file with mode: 0644]
src/java/com/android/ims/ImsException.java [new file with mode: 0644]
src/java/com/android/ims/ImsManager.java [new file with mode: 0644]
src/java/com/android/ims/ImsReasonInfo.aidl [new file with mode: 0644]
src/java/com/android/ims/ImsReasonInfo.java [new file with mode: 0644]
src/java/com/android/ims/ImsServiceClass.java [new file with mode: 0644]
src/java/com/android/ims/ImsSsInfo.aidl [new file with mode: 0644]
src/java/com/android/ims/ImsSsInfo.java [new file with mode: 0644]
src/java/com/android/ims/ImsStreamMediaProfile.aidl [new file with mode: 0644]
src/java/com/android/ims/ImsStreamMediaProfile.java [new file with mode: 0644]
src/java/com/android/ims/ImsUt.java [new file with mode: 0644]
src/java/com/android/ims/ImsUtInterface.java [new file with mode: 0644]
src/java/com/android/ims/internal/CallGroup.java [new file with mode: 0644]
src/java/com/android/ims/internal/CallGroupManager.java [new file with mode: 0644]
src/java/com/android/ims/internal/ICall.java [new file with mode: 0644]
src/java/com/android/ims/internal/ICallGroup.java [new file with mode: 0644]
src/java/com/android/ims/internal/IImsCallSession.aidl [new file with mode: 0644]
src/java/com/android/ims/internal/IImsCallSessionListener.aidl [new file with mode: 0644]
src/java/com/android/ims/internal/IImsRegistrationListener.aidl [new file with mode: 0644]
src/java/com/android/ims/internal/IImsService.aidl [new file with mode: 0644]
src/java/com/android/ims/internal/IImsStreamMediaSession.aidl [new file with mode: 0644]
src/java/com/android/ims/internal/IImsUt.aidl [new file with mode: 0644]
src/java/com/android/ims/internal/IImsUtListener.aidl [new file with mode: 0644]
src/java/com/android/ims/internal/ImsCallSession.java [new file with mode: 0644]
src/java/com/android/ims/internal/ImsStreamMediaSession.java [new file with mode: 0644]

diff --git a/Android.mk b/Android.mk
new file mode 100644 (file)
index 0000000..658b091
--- /dev/null
@@ -0,0 +1,35 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/src/java
+LOCAL_SRC_FILES := \
+    src/java/com/android/ims/internal/IImsRegistrationListener.aidl \
+    src/java/com/android/ims/internal/IImsStreamMediaSession.aidl \
+    src/java/com/android/ims/internal/IImsCallSession.aidl \
+    src/java/com/android/ims/internal/IImsCallSessionListener.aidl \
+    src/java/com/android/ims/internal/IImsService.aidl \
+    src/java/com/android/ims/internal/IImsUt.aidl \
+    src/java/com/android/ims/internal/IImsUtListener.aidl \
+    $(call all-java-files-under, src/java)
+
+#LOCAL_JAVA_LIBRARIES := telephony-common
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := ims-common
+
+include $(BUILD_JAVA_LIBRARY)
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644 (file)
index 0000000..ab62621
--- /dev/null
@@ -0,0 +1,45 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list.  These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list.  E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/ims_intermediates)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/README.txt b/README.txt
new file mode 100644 (file)
index 0000000..f7271f6
--- /dev/null
@@ -0,0 +1,113 @@
+This package provides access to IP Multimedia Subsystem (IMS) functionality,
+especially making and taking VoLTE calls using IMS.
+At the moment, this only supports VoLTE calls which is compliant to GSMA IR.92 specification.
+
+For the additional information, you can refer the below standard specifications.
+    - GSMA IR.92 : features for voice and sms profile
+    - GSMA IR.94 : video calling feature
+    - 3GPP TS 24.229 : IMS call control (SIP and SDP)
+    - 3GPP TS 26.114 : IMS media handling and interaction
+    - 3GPP TS 26.111 : Codec for CS multimedia telephony service (H.324)
+    - 3GPP TS 24.623 : XCAP over the Ut interface for manipulating supplementary services
+      (XCAP : XML Configuration Access Protocol)
+
+To get started, you need to get an instance of the ImsManager by calling ImsManager#getInstance().
+
+With the ImsManager, you can initiate VoLTE calls with ImsManager#makeCall()
+and ImsManager#takeCall(). Both methods require a ImsCall#Listener that
+receives callbacks when the state of the call changes, such as
+when the call is ringing, established, or ended.
+
+ImsManager#makeCall() requires an ImsCallProfile objects, representing the call properties
+of the local device. ImsCallProfile can creates by ImsManager
+using the specified service and call type.
+ImsCallProfile is created by referring GSMA IR.92, GSMA IR.94, 3GPP TS 24.229,
+3GPP TS 26.114 and 3GPP TS 26.111.
+
+To receive calls, an IMS application MUST provide a BroadcastReceiver that
+has the ability to respond to an intent indicating that there is an incoming call.
+The default action for the incoming call intent is ImsManager#ACTION_IMS_INCOMING_CALL.
+And, the application frees to define the action for the incoming call intent.
+
+There are two packages for IMS APIs.
+
+    - com.android.ims
+        It provides the functionalities for the upper layer of IMS APIs.
+        In this moment, it is used by the VoLTE Phone application.
+
+    - com.android.ims.internal
+        It provides the functionalities for the internal usage or lower layer of IMS APIs.
+        It needs to be implemented by the IMS protocol solution vendor.
+
+
+
+== Classes for com.android.ims ==
+
+ImsManager
+    Provides APIs for IMS services, such as initiating IMS calls, and provides access to
+    the mobile operator's IMS network. This class is the starting point for any IMS actions.
+
+ImsCall
+    Provides IMS voice / video calls over LTE network.
+
+ImsCallGroup
+    Manages all IMS calls which are established hereafter the initial 1-to-1 call is established.
+    It's for providing the dummy calls which are disconnected with the IMS network after
+    merged or extended to the conference.
+
+ImsCallProfile
+    Parcelable object to handle IMS call profile
+    It provides the service and call type, the additional information related to the call.
+
+ImsConferenceState
+    It provides the conference information (defined in RFC 4575) for IMS conference call.
+
+ImsConnectionStateListener
+    It is a listener type for receiving notifications about changes to the IMS connection.
+    It provides a state of IMS registration between UE and network, the service availability of
+    the local device during IMS registered.
+
+ImsException
+    It provides a general IMS-related exception.
+
+ImsReasonInfo
+    It enables an application to get details on why a method call failed.
+
+ImsServiceClass
+    It defines an identifier for each IMS service category.
+
+ImsStreamMediaProfile
+    Parcelable object to handle IMS stream media profile.
+    It provides the media direction, quality of audio and/or video.
+
+ImsUtInterface
+    It provides APIs for the supplementary service settings using IMS (Ut interface, XCAP).
+
+
+
+== Classes for com.android.ims.internal ==
+
+CallGroup
+    Wrapper class which has an ICallGroup interface.
+
+CallGroupBase
+    Implements ICallGroup interface.
+    Manages all calls which are established hereafter the initial 1-to-1 call is established.
+    It's for providing the dummy calls which are disconnected with the IMS network after
+    merged or extended to the conference.
+
+CallGroupManager
+    Manages CallGroup objects.
+
+ICallGroup
+    Provides the interface to manage all calls which are established hereafter the initial
+    1-to-1 call is established. It's for providing the dummy calls which are disconnected with
+    the IMS network after merged or extended to the conference.
+
+ImsCallSession
+    Provides the call initiation/termination, and media exchange between two IMS endpoints.
+    It directly communicates with IMS service which implements the IMS protocol behavior.
+
+ImsStreamMediaSession
+    Provides the APIs to control the media session, such as passing the surface object,
+    controlling the camera (front/rear selection, zoom, brightness, ...) for a video calling.
\ No newline at end of file
diff --git a/src/java/com/android/ims/ImsCall.java b/src/java/com/android/ims/ImsCall.java
new file mode 100644 (file)
index 0000000..b7adfb1
--- /dev/null
@@ -0,0 +1,2154 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Message;
+import android.telephony.Rlog;
+
+import com.android.ims.internal.CallGroup;
+import com.android.ims.internal.CallGroupManager;
+import com.android.ims.internal.ICall;
+import com.android.ims.internal.ImsCallSession;
+import com.android.ims.internal.ImsStreamMediaSession;
+
+/**
+ * Handles an IMS voice / video call over LTE. You can instantiate this class with
+ * {@link ImsManager}.
+ *
+ * @hide
+ */
+public class ImsCall implements ICall {
+    public static final int CALL_STATE_ACTIVE_TO_HOLD = 1;
+    public static final int CALL_STATE_HOLD_TO_ACTIVE = 2;
+
+    // Mode of USSD message
+    public static final int USSD_MODE_NOTIFY = 0;
+    public static final int USSD_MODE_REQUEST = 1;
+
+    private static final String TAG = "ImsCall";
+    private static final boolean DBG = true;
+
+    /**
+     * Listener for events relating to an IMS call, such as when a call is being
+     * recieved ("on ringing") or a call is outgoing ("on calling").
+     * <p>Many of these events are also received by {@link ImsCallSession.Listener}.</p>
+     */
+    public static class Listener {
+        /**
+         * Called when a request is sent out to initiate a new call
+         * and 1xx response is received from the network.
+         * The default implementation calls {@link #onCallStateChanged}.
+         *
+         * @param call the call object that carries out the IMS call
+         */
+        public void onCallProgressing(ImsCall call) {
+            onCallStateChanged(call);
+        }
+
+        /**
+         * Called when the call is established.
+         * The default implementation calls {@link #onCallStateChanged}.
+         *
+         * @param call the call object that carries out the IMS call
+         */
+        public void onCallStarted(ImsCall call) {
+            onCallStateChanged(call);
+        }
+
+        /**
+         * Called when the call setup is failed.
+         * The default implementation calls {@link #onCallError}.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param reasonInfo detailed reason of the call setup failure
+         */
+        public void onCallStartFailed(ImsCall call, ImsReasonInfo reasonInfo) {
+            onCallError(call, reasonInfo);
+        }
+
+        /**
+         * Called when the call is terminated.
+         * The default implementation calls {@link #onCallStateChanged}.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param reasonInfo detailed reason of the call termination
+         */
+        public void onCallTerminated(ImsCall call, ImsReasonInfo reasonInfo) {
+            // Store the call termination reason
+
+            onCallStateChanged(call);
+        }
+
+        /**
+         * Called when the call is in hold.
+         * The default implementation calls {@link #onCallStateChanged}.
+         *
+         * @param call the call object that carries out the IMS call
+         */
+        public void onCallHeld(ImsCall call) {
+            onCallStateChanged(call);
+        }
+
+        /**
+         * Called when the call hold is failed.
+         * The default implementation calls {@link #onCallError}.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param reasonInfo detailed reason of the call hold failure
+         */
+        public void onCallHoldFailed(ImsCall call, ImsReasonInfo reasonInfo) {
+            onCallError(call, reasonInfo);
+        }
+
+        /**
+         * Called when the call hold is received from the remote user.
+         * The default implementation calls {@link #onCallStateChanged}.
+         *
+         * @param call the call object that carries out the IMS call
+         */
+        public void onCallHoldReceived(ImsCall call) {
+            onCallStateChanged(call);
+        }
+
+        /**
+         * Called when the call is in call.
+         * The default implementation calls {@link #onCallStateChanged}.
+         *
+         * @param call the call object that carries out the IMS call
+         */
+        public void onCallResumed(ImsCall call) {
+            onCallStateChanged(call);
+        }
+
+        /**
+         * Called when the call resume is failed.
+         * The default implementation calls {@link #onCallError}.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param reasonInfo detailed reason of the call resume failure
+         */
+        public void onCallResumeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
+            onCallError(call, reasonInfo);
+        }
+
+        /**
+         * Called when the call resume is received from the remote user.
+         * The default implementation calls {@link #onCallStateChanged}.
+         *
+         * @param call the call object that carries out the IMS call
+         */
+        public void onCallResumeReceived(ImsCall call) {
+            onCallStateChanged(call);
+        }
+
+        /**
+         * Called when the call is in call.
+         * The default implementation calls {@link #onCallStateChanged}.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param newCall the call object that is merged with an active & hold call
+         */
+        public void onCallMerged(ImsCall call, ImsCall newCall) {
+            onCallStateChanged(call, newCall);
+        }
+
+        /**
+         * Called when the call merge is failed.
+         * The default implementation calls {@link #onCallError}.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param reasonInfo detailed reason of the call merge failure
+         */
+        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
+            onCallError(call, reasonInfo);
+        }
+
+        /**
+         * Called when the call is updated (except for hold/unhold).
+         * The default implementation calls {@link #onCallStateChanged}.
+         *
+         * @param call the call object that carries out the IMS call
+         */
+        public void onCallUpdated(ImsCall call) {
+            onCallStateChanged(call);
+        }
+
+        /**
+         * Called when the call update is failed.
+         * The default implementation calls {@link #onCallError}.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param reasonInfo detailed reason of the call update failure
+         */
+        public void onCallUpdateFailed(ImsCall call, ImsReasonInfo reasonInfo) {
+            onCallError(call, reasonInfo);
+        }
+
+        /**
+         * Called when the call update is received from the remote user.
+         *
+         * @param call the call object that carries out the IMS call
+         */
+        public void onCallUpdateReceived(ImsCall call) {
+            // no-op
+        }
+
+        /**
+         * Called when the call is extended to the conference call.
+         * The default implementation calls {@link #onCallStateChanged}.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param newCall the call object that is extended to the conference from the active call
+         */
+        public void onCallConferenceExtended(ImsCall call, ImsCall newCall) {
+            onCallStateChanged(call, newCall);
+        }
+
+        /**
+         * Called when the conference extension is failed.
+         * The default implementation calls {@link #onCallError}.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param reasonInfo detailed reason of the conference extension failure
+         */
+        public void onCallConferenceExtendFailed(ImsCall call,
+                ImsReasonInfo reasonInfo) {
+            onCallError(call, reasonInfo);
+        }
+
+        /**
+         * Called when the conference extension is received from the remote user.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param newCall the call object that is extended to the conference from the active call
+         */
+        public void onCallConferenceExtendReceived(ImsCall call, ImsCall newCall) {
+            onCallStateChanged(call, newCall);
+        }
+
+        /**
+         * Called when the invitation request of the participants is delivered to
+         * the conference server.
+         *
+         * @param call the call object that carries out the IMS call
+         */
+        public void onCallInviteParticipantsRequestDelivered(ImsCall call) {
+            // no-op
+        }
+
+        /**
+         * Called when the invitation request of the participants is failed.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param reasonInfo detailed reason of the conference invitation failure
+         */
+        public void onCallInviteParticipantsRequestFailed(ImsCall call,
+                ImsReasonInfo reasonInfo) {
+            // no-op
+        }
+
+        /**
+         * Called when the removal request of the participants is delivered to
+         * the conference server.
+         *
+         * @param call the call object that carries out the IMS call
+         */
+        public void onCallRemoveParticipantsRequestDelivered(ImsCall call) {
+            // no-op
+        }
+
+        /**
+         * Called when the removal request of the participants is failed.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param reasonInfo detailed reason of the conference removal failure
+         */
+        public void onCallRemoveParticipantsRequestFailed(ImsCall call,
+                ImsReasonInfo reasonInfo) {
+            // no-op
+        }
+
+        /**
+         * Called when the conference state is updated.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param state state of the participant who is participated in the conference call
+         */
+        public void onCallConferenceStateUpdated(ImsCall call, ImsConferenceState state) {
+            // no-op
+        }
+
+        /**
+         * Called when the USSD message is received from the network.
+         *
+         * @param mode mode of the USSD message (REQUEST / NOTIFY)
+         * @param ussdMessage USSD message
+         */
+        public void onCallUssdMessageReceived(ImsCall call,
+                int mode, String ussdMessage) {
+            // no-op
+        }
+
+        /**
+         * Called when an error occurs. The default implementation is no op.
+         * overridden. The default implementation is no op. Error events are
+         * not re-directed to this callback and are handled in {@link #onCallError}.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param reasonInfo detailed reason of this error
+         * @see ImsReasonInfo
+         */
+        public void onCallError(ImsCall call, ImsReasonInfo reasonInfo) {
+            // no-op
+        }
+
+        /**
+         * Called when an event occurs and the corresponding callback is not
+         * overridden. The default implementation is no op. Error events are
+         * not re-directed to this callback and are handled in {@link #onCallError}.
+         *
+         * @param call the call object that carries out the IMS call
+         */
+        public void onCallStateChanged(ImsCall call) {
+            // no-op
+        }
+
+        /**
+         * Called when an event occurs and the corresponding callback is not
+         * overridden. The default implementation is no op. Error events are
+         * not re-directed to this callback and are handled in {@link #onCallError}.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param newCall the call object that will be replaced by the previous call
+         */
+        public void onCallStateChanged(ImsCall call, ImsCall newCall) {
+            // no-op
+        }
+
+        /**
+         * Called when the call moves the hold state to the conversation state.
+         * For example, when merging the active & hold call, the state of all the hold call
+         * will be changed from hold state to conversation state.
+         * This callback method can be invoked even though the application does not trigger
+         * any operations.
+         *
+         * @param call the call object that carries out the IMS call
+         * @param state the detailed state of call state changes;
+         *      Refer to CALL_STATE_* in {@link ImsCall}
+         */
+        public void onCallStateChanged(ImsCall call, int state) {
+            // no-op
+        }
+    }
+
+
+
+    // List of update operation for IMS call control
+    private static final int UPDATE_NONE = 0;
+    private static final int UPDATE_HOLD = 1;
+    private static final int UPDATE_HOLD_MERGE = 2;
+    private static final int UPDATE_RESUME = 3;
+    private static final int UPDATE_MERGE = 4;
+    private static final int UPDATE_EXTEND_TO_CONFERENCE = 5;
+    private static final int UPDATE_UNSPECIFIED = 6;
+
+    // For synchronization of private variables
+    private Object mLockObj = new Object();
+    private Context mContext;
+
+    // true if the call is established & in the conversation state
+    private boolean mInCall = false;
+    // true if the call is on hold
+    // If it is triggered by the local, mute the call. Otherwise, play local hold tone
+    // or network generated media.
+    private boolean mHold = false;
+    // true if the call is on mute
+    private boolean mMute = false;
+    // It contains the exclusive call update request. Refer to UPDATE_*.
+    private int mUpdateRequest = UPDATE_NONE;
+
+    private ImsCall.Listener mListener = null;
+    // It is for managing the multiple calls
+    // when the multiparty call is extended to the conference.
+    private CallGroup mCallGroup = null;
+
+    // Wrapper call session to interworking the IMS service (server).
+    private ImsCallSession mSession = null;
+    // Call profile of the current session.
+    // It can be changed at anytime when the call is updated.
+    private ImsCallProfile mCallProfile = null;
+    // Call profile to be updated after the application's action (accept/reject)
+    // to the call update. After the application's action (accept/reject) is done,
+    // it will be set to null.
+    private ImsCallProfile mProposedCallProfile = null;
+    private ImsReasonInfo mLastReasonInfo = null;
+
+    // Media session to control media (audio/video) operations for an IMS call
+    private ImsStreamMediaSession mMediaSession = null;
+
+    /**
+     * Create an IMS call object.
+     *
+     * @param context the context for accessing system services
+     * @param profile the call profile to make/take a call
+     */
+    public ImsCall(Context context, ImsCallProfile profile) {
+        mContext = context;
+        mCallProfile = profile;
+    }
+
+    /**
+     * Closes this object. This object is not usable after being closed.
+     */
+    @Override
+    public void close() {
+        synchronized(mLockObj) {
+            destroyCallGroup();
+
+            if (mSession != null) {
+                mSession.close();
+                mSession = null;
+            }
+
+            mCallProfile = null;
+            mProposedCallProfile = null;
+            mLastReasonInfo = null;
+            mMediaSession = null;
+        }
+    }
+
+    /**
+     * Checks if the call has a same remote user identity or not.
+     *
+     * @param userId the remote user identity
+     * @return true if the remote user identity is equal; otherwise, false
+     */
+    @Override
+    public boolean checkIfRemoteUserIsSame(String userId) {
+        if (userId == null) {
+            return false;
+        }
+
+        return userId.equals(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_REMOTE_URI, ""));
+    }
+
+    /**
+     * Checks if the call is equal or not.
+     *
+     * @param call the call to be compared
+     * @return true if the call is equal; otherwise, false
+     */
+    @Override
+    public boolean equalsTo(ICall call) {
+        if (call == null) {
+            return false;
+        }
+
+        if (call instanceof ImsCall) {
+            return this.equals((ImsCall)call);
+        }
+
+        return false;
+    }
+
+    /**
+     * Gets the negotiated (local & remote) call profile.
+     *
+     * @return a {@link ImsCallProfile} object that has the negotiated call profile
+     */
+    public ImsCallProfile getCallProfile() {
+        synchronized(mLockObj) {
+            return mCallProfile;
+        }
+    }
+
+    /**
+     * Gets the local call profile (local capabilities).
+     *
+     * @return a {@link ImsCallProfile} object that has the local call profile
+     */
+    public ImsCallProfile getLocalCallProfile() throws ImsException {
+        synchronized(mLockObj) {
+            if (mSession == null) {
+                throw new ImsException("No call session",
+                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
+            }
+
+            try {
+                return mSession.getLocalCallProfile();
+            } catch (Throwable t) {
+                loge("getLocalCallProfile :: ", t);
+                throw new ImsException("getLocalCallProfile()", t, 0);
+            }
+        }
+    }
+
+    /**
+     * Gets the call profile proposed by the local/remote user.
+     *
+     * @return a {@link ImsCallProfile} object that has the proposed call profile
+     */
+    public ImsCallProfile getProposedCallProfile() {
+        synchronized(mLockObj) {
+            if (!isInCall()) {
+                return null;
+            }
+
+            return mProposedCallProfile;
+        }
+    }
+
+    /**
+     * Gets the state of the {@link ImsCallSession} that carries this call.
+     * The value returned must be one of the states in {@link ImsCallSession#State}.
+     *
+     * @return the session state
+     */
+    public int getState() {
+        synchronized(mLockObj) {
+            if (mSession == null) {
+                return ImsCallSession.State.IDLE;
+            }
+
+            return mSession.getState();
+        }
+    }
+
+    /**
+     * Gets the {@link ImsCallSession} that carries this call.
+     *
+     * @return the session object that carries this call
+     * @hide
+     */
+    public ImsCallSession getCallSession() {
+        synchronized(mLockObj) {
+            return mSession;
+        }
+    }
+
+    /**
+     * Gets the {@link ImsStreamMediaSession} that handles the media operation of this call.
+     * Almost interface APIs are for the VT (Video Telephony).
+     *
+     * @return the media session object that handles the media operation of this call
+     * @hide
+     */
+    public ImsStreamMediaSession getMediaSession() {
+        synchronized(mLockObj) {
+            return mMediaSession;
+        }
+    }
+
+    /**
+     * Gets the specified property of this call.
+     *
+     * @param name key to get the extra call information defined in {@link ImsCallProfile}
+     * @return the extra call information as string
+     */
+    public String getCallExtra(String name) throws ImsException {
+        // Lookup the cache
+
+        synchronized(mLockObj) {
+            // If not found, try to get the property from the remote
+            if (mSession == null) {
+                throw new ImsException("No call session",
+                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
+            }
+
+            try {
+                return mSession.getProperty(name);
+            } catch (Throwable t) {
+                loge("getCallExtra :: ", t);
+                throw new ImsException("getCallExtra()", t, 0);
+            }
+        }
+    }
+
+    /**
+     * Gets the last reason information when the call is not established, cancelled or terminated.
+     *
+     * @return the last reason information
+     */
+    public ImsReasonInfo getLastReasonInfo() {
+        synchronized(mLockObj) {
+            return mLastReasonInfo;
+        }
+    }
+
+    /**
+     * Checks if the call has a pending update operation.
+     *
+     * @return true if the call has a pending update operation
+     */
+    public boolean hasPendingUpdate() {
+        synchronized(mLockObj) {
+            return (mUpdateRequest != UPDATE_NONE);
+        }
+    }
+
+    /**
+     * Checks if the call is established.
+     *
+     * @return true if the call is established
+     */
+    public boolean isInCall() {
+        synchronized(mLockObj) {
+            return mInCall;
+        }
+    }
+
+    /**
+     * Checks if the call is muted.
+     *
+     * @return true if the call is muted
+     */
+    public boolean isMuted() {
+        synchronized(mLockObj) {
+            return mMute;
+        }
+    }
+
+    /**
+     * Checks if the call is on hold.
+     *
+     * @return true if the call is on hold
+     */
+    public boolean isOnHold() {
+        synchronized(mLockObj) {
+            return mHold;
+        }
+    }
+
+    /**
+     * Sets the listener to listen to the IMS call events.
+     * The method calls {@link #setListener setListener(listener, false)}.
+     *
+     * @param listener to listen to the IMS call events of this object; null to remove listener
+     * @see #setListener(Listener, boolean)
+     */
+    public void setListener(ImsCall.Listener listener) {
+        setListener(listener, false);
+    }
+
+    /**
+     * Sets the listener to listen to the IMS call events.
+     * A {@link ImsCall} can only hold one listener at a time. Subsequent calls
+     * to this method override the previous listener.
+     *
+     * @param listener to listen to the IMS call events of this object; null to remove listener
+     * @param callbackImmediately set to true if the caller wants to be called
+     *        back immediately on the current state
+     */
+    public void setListener(ImsCall.Listener listener, boolean callbackImmediately) {
+        boolean inCall;
+        boolean onHold;
+        int state;
+        ImsReasonInfo lastReasonInfo;
+
+        synchronized(mLockObj) {
+            mListener = listener;
+
+            if ((listener == null) || !callbackImmediately) {
+                return;
+            }
+
+            inCall = mInCall;
+            onHold = mHold;
+            state = getState();
+            lastReasonInfo = mLastReasonInfo;
+        }
+
+        try {
+            if (lastReasonInfo != null) {
+                listener.onCallError(this, lastReasonInfo);
+            } else if (inCall) {
+                if (onHold) {
+                    listener.onCallHeld(this);
+                } else {
+                    listener.onCallStarted(this);
+                }
+            } else {
+                switch (state) {
+                    case ImsCallSession.State.ESTABLISHING:
+                        listener.onCallProgressing(this);
+                        break;
+                    case ImsCallSession.State.TERMINATED:
+                        listener.onCallTerminated(this, lastReasonInfo);
+                        break;
+                    default:
+                        // Ignore it. There is no action in the other state.
+                        break;
+                }
+            }
+        } catch (Throwable t) {
+            loge("setListener()", t);
+        }
+    }
+
+    /**
+     * Mutes or unmutes the mic for the active call.
+     *
+     * @param muted true if the call is muted, false otherwise
+     */
+    public void setMute(boolean muted) throws ImsException {
+        synchronized(mLockObj) {
+            if (mMute != muted) {
+                mMute = muted;
+
+                try {
+                    mSession.setMute(muted);
+                } catch (Throwable t) {
+                    loge("setMute :: ", t);
+                    throwImsException(t, 0);
+                }
+            }
+        }
+    }
+
+     /**
+      * Attaches an incoming call to this call object.
+      *
+      * @param session the session that receives the incoming call
+      * @throws ImsException if the IMS service fails to attach this object to the session
+      */
+     public void attachSession(ImsCallSession session) throws ImsException {
+         if (DBG) {
+             log("attachSession :: session=" + session);
+         }
+
+         synchronized(mLockObj) {
+             mSession = session;
+
+             try {
+                 mSession.setListener(createCallSessionListener());
+             } catch (Throwable t) {
+                 loge("attachSession :: ", t);
+                 throwImsException(t, 0);
+             }
+         }
+     }
+
+    /**
+     * Initiates an IMS call with the call profile which is provided
+     * when creating a {@link ImsCall}.
+     *
+     * @param session the {@link ImsCallSession} for carrying out the call
+     * @param callee callee information to initiate an IMS call
+     * @throws ImsException if the IMS service fails to initiate the call
+     */
+    public void start(ImsCallSession session, String callee)
+            throws ImsException {
+        if (DBG) {
+            log("start(1) :: session=" + session + ", callee=" + callee);
+        }
+
+        synchronized(mLockObj) {
+            mSession = session;
+
+            try {
+                session.setListener(createCallSessionListener());
+                session.start(callee, mCallProfile);
+            } catch (Throwable t) {
+                loge("start(1) :: ", t);
+                throw new ImsException("start(1)", t, 0);
+            }
+        }
+    }
+
+    /**
+     * Initiates an IMS conferenca call with the call profile which is provided
+     * when creating a {@link ImsCall}.
+     *
+     * @param session the {@link ImsCallSession} for carrying out the call
+     * @param participants participant list to initiate an IMS conference call
+     * @throws ImsException if the IMS service fails to initiate the call
+     */
+    public void start(ImsCallSession session, String[] participants)
+            throws ImsException {
+        if (DBG) {
+            log("start(n) :: session=" + session + ", callee=" + participants);
+        }
+
+        synchronized(mLockObj) {
+            mSession = session;
+
+            try {
+                session.setListener(createCallSessionListener());
+                session.start(participants, mCallProfile);
+            } catch (Throwable t) {
+                loge("start(n) :: ", t);
+                throw new ImsException("start(n)", t, 0);
+            }
+        }
+    }
+
+    /**
+     * Accepts a call.
+     *
+     * @see Listener#onCallStarted
+     * @throws ImsException if the IMS service fails to accept the call
+     */
+    public void accept() throws ImsException {
+        if (DBG) {
+            log("accept :: session=" + mSession);
+        }
+
+        accept(ImsCallProfile.CALL_TYPE_VOICE, new ImsStreamMediaProfile());
+    }
+
+    /**
+     * Accepts a call.
+     *
+     * @param callType call type to be answered in {@link ImsCallProfile}
+     * @param profile a media profile to be answered (audio/audio & video, direction, ...)
+     * @see Listener#onCallStarted
+     * @throws ImsException if the IMS service fails to accept the call
+     */
+    public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException {
+        if (DBG) {
+            log("accept :: session=" + mSession
+                    + ", callType=" + callType + ", profile=" + profile);
+        }
+
+        synchronized(mLockObj) {
+            if (mSession == null) {
+                throw new ImsException("No call to answer",
+                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
+            }
+
+            try {
+                mSession.accept(callType, profile);
+            } catch (Throwable t) {
+                loge("accept :: ", t);
+                throw new ImsException("accept()", t, 0);
+            }
+
+            if (mInCall && (mProposedCallProfile != null)) {
+                if (DBG) {
+                    log("accept :: call profile will be updated");
+                }
+
+                mCallProfile = mProposedCallProfile;
+                mProposedCallProfile = null;
+            }
+
+            // Other call update received
+            if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
+                mUpdateRequest = UPDATE_NONE;
+            }
+        }
+    }
+
+    /**
+     * Rejects a call.
+     *
+     * @param reason reason code to reject an incoming call
+     * @see Listener#onCallStartFailed
+     * @throws ImsException if the IMS service fails to accept the call
+     */
+    public void reject(int reason) throws ImsException {
+        if (DBG) {
+            log("reject :: session=" + mSession + ", reason=" + reason);
+        }
+
+        synchronized(mLockObj) {
+            if (mSession != null) {
+                mSession.reject(reason);
+            }
+
+            if (mInCall && (mProposedCallProfile != null)) {
+                if (DBG) {
+                    log("reject :: call profile is not updated; destroy it...");
+                }
+
+                mProposedCallProfile = null;
+            }
+
+            // Other call update received
+            if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
+                mUpdateRequest = UPDATE_NONE;
+            }
+        }
+    }
+
+    /**
+     * Terminates an IMS call.
+     *
+     * @param reason reason code to terminate a call
+     * @throws ImsException if the IMS service fails to terminate the call
+     */
+    public void terminate(int reason) throws ImsException {
+        if (DBG) {
+            log("terminate :: session=" + mSession + ", reason=" + reason);
+        }
+
+        synchronized(mLockObj) {
+            mHold = false;
+            mInCall = false;
+
+            if (mSession != null) {
+                mSession.terminate(reason);
+            }
+        }
+    }
+
+    /**
+     * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called.
+     *
+     * @see Listener#onCallHeld, Listener#onCallHoldFailed
+     * @throws ImsException if the IMS service fails to hold the call
+     */
+    public void hold() throws ImsException {
+        if (DBG) {
+            log("hold :: session=" + mSession);
+        }
+
+        if (isOnHold()) {
+            if (DBG) {
+                log("hold :: call is already on hold");
+            }
+            return;
+        }
+
+        synchronized(mLockObj) {
+            if (mUpdateRequest != UPDATE_NONE) {
+                loge("hold :: update is in progress; request=" + mUpdateRequest);
+                throw new ImsException("Call update is in progress",
+                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
+            }
+
+            if (mSession == null) {
+                loge("hold :: ");
+                throw new ImsException("No call session",
+                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
+            }
+
+            mSession.hold(createHoldMediaProfile());
+            // FIXME: update the state on the callback?
+            mHold = true;
+            mUpdateRequest = UPDATE_HOLD;
+        }
+    }
+
+    /**
+     * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called.
+     *
+     * @see Listener#onCallResumed, Listener#onCallResumeFailed
+     * @throws ImsException if the IMS service fails to resume the call
+     */
+    public void resume() throws ImsException {
+        if (DBG) {
+            log("resume :: session=" + mSession);
+        }
+
+        if (!isOnHold()) {
+            if (DBG) {
+                log("resume :: call is in conversation");
+            }
+            return;
+        }
+
+        synchronized(mLockObj) {
+            if (mUpdateRequest != UPDATE_NONE) {
+                loge("resume :: update is in progress; request=" + mUpdateRequest);
+                throw new ImsException("Call update is in progress",
+                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
+            }
+
+            if (mSession == null) {
+                loge("resume :: ");
+                throw new ImsException("No call session",
+                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
+            }
+
+            mSession.resume(createResumeMediaProfile());
+            // FIXME: update the state on the callback?
+            mHold = false;
+            mUpdateRequest = UPDATE_RESUME;
+        }
+    }
+
+    /**
+     * Merges the active & hold call.
+     *
+     * @see Listener#onCallMerged, Listener#onCallMergeFailed
+     * @throws ImsException if the IMS service fails to merge the call
+     */
+    public void merge() throws ImsException {
+        if (DBG) {
+            log("merge :: session=" + mSession);
+        }
+
+        synchronized(mLockObj) {
+            if (mUpdateRequest != UPDATE_NONE) {
+                loge("merge :: update is in progress; request=" + mUpdateRequest);
+                throw new ImsException("Call update is in progress",
+                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
+            }
+
+            if (mSession == null) {
+                loge("merge :: ");
+                throw new ImsException("No call session",
+                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
+            }
+
+            if (mHold) {
+                mSession.merge();
+                mUpdateRequest = UPDATE_MERGE;
+            } else {
+                mSession.hold(createHoldMediaProfile());
+                // FIXME: ?
+                mHold = true;
+                mUpdateRequest = UPDATE_HOLD_MERGE;
+            }
+        }
+    }
+
+    /**
+     * Merges the active & hold call.
+     *
+     * @param bgCall the background (holding) call
+     * @see Listener#onCallMerged, Listener#onCallMergeFailed
+     * @throws ImsException if the IMS service fails to merge the call
+     */
+    public void merge(ImsCall bgCall) throws ImsException {
+        if (DBG) {
+            log("merge(1) :: session=" + mSession);
+        }
+
+        if (bgCall == null) {
+            throw new ImsException("No background call",
+                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
+        }
+
+        synchronized(mLockObj) {
+            createCallGroup(bgCall);
+        }
+
+        merge();
+    }
+
+    /**
+     * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
+     */
+    public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException {
+        if (DBG) {
+            log("update :: session=" + mSession);
+        }
+
+        if (isOnHold()) {
+            if (DBG) {
+                log("update :: call is on hold");
+            }
+            throw new ImsException("Not in a call to update call",
+                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
+        }
+
+        synchronized(mLockObj) {
+            if (mUpdateRequest != UPDATE_NONE) {
+                if (DBG) {
+                    log("update :: update is in progress; request=" + mUpdateRequest);
+                }
+                throw new ImsException("Call update is in progress",
+                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
+            }
+
+            if (mSession == null) {
+                loge("update :: ");
+                throw new ImsException("No call session",
+                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
+            }
+
+            mSession.update(callType, mediaProfile);
+            mUpdateRequest = UPDATE_UNSPECIFIED;
+        }
+    }
+
+    /**
+     * Extends this call (1-to-1 call) to the conference call
+     * inviting the specified participants to.
+     *
+     */
+    public void extendToConference(String[] participants) throws ImsException {
+        if (DBG) {
+            log("extendToConference :: session=" + mSession);
+        }
+
+        if (isOnHold()) {
+            if (DBG) {
+                log("extendToConference :: call is on hold");
+            }
+            throw new ImsException("Not in a call to extend a call to conference",
+                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
+        }
+
+        synchronized(mLockObj) {
+            if (mUpdateRequest != UPDATE_NONE) {
+                if (DBG) {
+                    log("extendToConference :: update is in progress; request=" + mUpdateRequest);
+                }
+                throw new ImsException("Call update is in progress",
+                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
+            }
+
+            if (mSession == null) {
+                loge("extendToConference :: ");
+                throw new ImsException("No call session",
+                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
+            }
+
+            mSession.extendToConference(participants);
+            mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE;
+        }
+    }
+
+    /**
+     * Requests the conference server to invite an additional participants to the conference.
+     *
+     */
+    public void inviteParticipants(String[] participants) throws ImsException {
+        if (DBG) {
+            log("inviteParticipants :: session=" + mSession);
+        }
+
+        synchronized(mLockObj) {
+            if (mSession == null) {
+                loge("inviteParticipants :: ");
+                throw new ImsException("No call session",
+                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
+            }
+
+            mSession.inviteParticipants(participants);
+        }
+    }
+
+    /**
+     * Requests the conference server to remove the specified participants from the conference.
+     *
+     */
+    public void removeParticipants(String[] participants) throws ImsException {
+        if (DBG) {
+            log("removeParticipants :: session=" + mSession);
+        }
+
+        synchronized(mLockObj) {
+            if (mSession == null) {
+                loge("removeParticipants :: ");
+                throw new ImsException("No call session",
+                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
+            }
+
+            mSession.removeParticipants(participants);
+        }
+    }
+
+
+    /**
+     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
+     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
+     * and event flash to 16. Currently, event flash is not supported.
+     *
+     * @param code the DTMF to send. Value 0 to 15 (inclusive) are valid inputs.
+     */
+    public void sendDtmf(int code) {
+        sendDtmf(code, null);
+    }
+
+    /**
+     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
+     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
+     * and event flash to 16. Currently, event flash is not supported.
+     *
+     * @param code the DTMF to send. Value 0 to 15 (inclusive) are valid inputs.
+     * @param result the result message to send when done
+     */
+    public void sendDtmf(int code, Message result) {
+        if (DBG) {
+            log("sendDtmf :: session=" + mSession + ", code=" + code);
+        }
+
+        synchronized(mLockObj) {
+            if (mSession != null) {
+                mSession.sendDtmf(code);
+            }
+        }
+
+        if (result != null) {
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Sends an USSD message.
+     *
+     * @param ussdMessage USSD message to send
+     */
+    public void sendUssd(String ussdMessage) throws ImsException {
+        if (DBG) {
+            log("sendUssd :: session=" + mSession + ", ussdMessage=" + ussdMessage);
+        }
+
+        synchronized(mLockObj) {
+            if (mSession == null) {
+                loge("sendUssd :: ");
+                throw new ImsException("No call session",
+                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
+            }
+
+            mSession.sendUssd(ussdMessage);
+        }
+    }
+
+    private void clear(ImsReasonInfo lastReasonInfo) {
+        mInCall = false;
+        mHold = false;
+        mUpdateRequest = UPDATE_NONE;
+        mLastReasonInfo = lastReasonInfo;
+        destroyCallGroup();
+    }
+
+    private void createCallGroup(ImsCall neutralReferrer) {
+        CallGroup referrerCallGroup = neutralReferrer.getCallGroup();
+
+        if (mCallGroup == null) {
+            if (referrerCallGroup == null) {
+                mCallGroup = CallGroupManager.getInstance().createCallGroup(new ImsCallGroup());
+            } else {
+                mCallGroup = referrerCallGroup;
+            }
+
+            if (mCallGroup != null) {
+                mCallGroup.setNeutralReferrer(neutralReferrer);
+            }
+        } else {
+            mCallGroup.setNeutralReferrer(neutralReferrer);
+
+            if ((referrerCallGroup != null)
+                    && (mCallGroup != referrerCallGroup)) {
+                loge("fatal :: call group is mismatched; call is corrupted...");
+            }
+        }
+    }
+
+    private void updateCallGroup(ImsCall owner) {
+        if (mCallGroup == null) {
+            return;
+        }
+
+        ImsCall neutralReferrer = (ImsCall)mCallGroup.getNeutralReferrer();
+
+        mCallGroup.setNeutralReferrer(null);
+
+        if (owner == null) {
+            // Maintain the call group if the current call has been merged in the past.
+            if (!mCallGroup.hasReferrer()) {
+                CallGroupManager.getInstance().destroyCallGroup(mCallGroup);
+                mCallGroup = null;
+            }
+        } else {
+            mCallGroup.addReferrer(this);
+
+            if (neutralReferrer != null) {
+                if (neutralReferrer.isInCall() && (neutralReferrer.getCallGroup() == null)) {
+                    neutralReferrer.setCallGroup(mCallGroup);
+                    mCallGroup.addReferrer(neutralReferrer);
+                }
+
+                neutralReferrer.enforceConversationMode();
+            }
+
+            // Close the existing owner call if present
+            ImsCall exOwner = (ImsCall)mCallGroup.getOwner();
+
+            mCallGroup.setOwner(owner);
+
+            if (exOwner != null) {
+                exOwner.close();
+            }
+        }
+    }
+
+    private void destroyCallGroup() {
+        if (mCallGroup == null) {
+            return;
+        }
+
+        mCallGroup.removeReferrer(this);
+
+        if (!mCallGroup.hasReferrer()) {
+            CallGroupManager.getInstance().destroyCallGroup(mCallGroup);
+        }
+
+        mCallGroup = null;
+    }
+
+    private CallGroup getCallGroup() {
+        synchronized(mLockObj) {
+            return mCallGroup;
+        }
+    }
+
+    private void setCallGroup(CallGroup callGroup) {
+        synchronized(mLockObj) {
+            mCallGroup = callGroup;
+        }
+    }
+
+    /**
+     * Creates an IMS call session listener.
+     */
+    private ImsCallSession.Listener createCallSessionListener() {
+        return new ImsCallSessionListenerProxy();
+    }
+
+    private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) {
+        ImsCall call = new ImsCall(mContext, profile);
+
+        try {
+            call.attachSession(session);
+        } catch (ImsException e) {
+            if (call != null) {
+                call.close();
+                call = null;
+            }
+        }
+
+        // Do additional operations...
+
+        return call;
+    }
+
+    private ImsStreamMediaProfile createHoldMediaProfile() {
+        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
+
+        if (mCallProfile == null) {
+            return mediaProfile;
+        }
+
+        mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
+        mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
+        mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND;
+
+        if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
+            mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND;
+        }
+
+        return mediaProfile;
+    }
+
+    private ImsStreamMediaProfile createResumeMediaProfile() {
+        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
+
+        if (mCallProfile == null) {
+            return mediaProfile;
+        }
+
+        mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
+        mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
+        mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
+
+        if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
+            mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
+        }
+
+        return mediaProfile;
+    }
+
+    private void enforceConversationMode() {
+        if (mInCall) {
+            mHold = false;
+            mUpdateRequest = UPDATE_NONE;
+        }
+    }
+
+    private void mergeInternal() {
+        if (DBG) {
+            log("mergeInternal :: session=" + mSession);
+        }
+
+        mSession.merge();
+        mUpdateRequest = UPDATE_MERGE;
+    }
+
+    private void notifyCallStateChanged() {
+        int state = 0;
+
+        if (mInCall && (mUpdateRequest == UPDATE_HOLD_MERGE)) {
+            state = CALL_STATE_ACTIVE_TO_HOLD;
+            mHold = true;
+        } else if (mInCall && ((mUpdateRequest == UPDATE_MERGE)
+                || (mUpdateRequest == UPDATE_EXTEND_TO_CONFERENCE))) {
+            state = CALL_STATE_HOLD_TO_ACTIVE;
+            mHold = false;
+            mMute = false;
+        }
+
+        if (state != 0) {
+            if (mListener != null) {
+                try {
+                    mListener.onCallStateChanged(ImsCall.this, state);
+                } catch (Throwable t) {
+                    loge("notifyCallStateChanged :: ", t);
+                }
+            }
+        }
+    }
+
+    private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) {
+        ImsCall.Listener listener;
+
+        if (mCallGroup.isOwner(ImsCall.this)) {
+            ArrayList<ICall> referrers = mCallGroup.getReferrers();
+
+            if (referrers != null) {
+                for (int i = 0; i < referrers.size(); ++i) {
+                    ImsCall call = (ImsCall)referrers.get(i);
+
+                    if (call == null) {
+                        continue;
+                    }
+
+                    listener = call.mListener;
+                    call.clear(reasonInfo);
+
+                    if (listener != null) {
+                        try {
+                            listener.onCallTerminated(call, reasonInfo);
+                        } catch (Throwable t) {
+                            loge("notifyConferenceSessionTerminated :: ", t);
+                        }
+                    }
+                }
+            }
+        } else if (!mCallGroup.isReferrer(ImsCall.this)) {
+            return;
+        }
+
+        listener = mListener;
+        clear(reasonInfo);
+
+        if (listener != null) {
+            try {
+                listener.onCallTerminated(this, reasonInfo);
+            } catch (Throwable t) {
+                loge("notifyConferenceSessionTerminated :: ", t);
+            }
+        }
+    }
+
+    private void notifyConferenceStateUpdated(ImsConferenceState state) {
+        Set<Entry<String, Bundle>> paticipants = state.mParticipants.entrySet();
+
+        if (paticipants == null) {
+            return;
+        }
+
+        Iterator<Entry<String, Bundle>> iterator = paticipants.iterator();
+
+        while (iterator.hasNext()) {
+            Entry<String, Bundle> entry = iterator.next();
+
+            String key = entry.getKey();
+            Bundle confInfo = entry.getValue();
+            String status = confInfo.getString(ImsConferenceState.STATUS);
+            String user = confInfo.getString(ImsConferenceState.USER);
+            String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT);
+
+            if (DBG) {
+                log("notifyConferenceStateUpdated :: key=" + key +
+                        ", status=" + status +
+                        ", user=" + user +
+                        ", endpoint=" + endpoint);
+            }
+
+            if ((mCallGroup != null) && (!mCallGroup.isOwner(ImsCall.this))) {
+                continue;
+            }
+
+            ImsCall referrer = (ImsCall)mCallGroup.getReferrer(endpoint);
+
+            if (referrer == null) {
+                continue;
+            }
+
+            if (referrer.mListener == null) {
+                continue;
+            }
+
+            try {
+                if (status.equals(ImsConferenceState.STATUS_ALERTING)) {
+                    referrer.mListener.onCallProgressing(referrer);
+                }
+                else if (status.equals(ImsConferenceState.STATUS_CONNECT_FAIL)) {
+                    referrer.mListener.onCallStartFailed(referrer, new ImsReasonInfo());
+                }
+                else if (status.equals(ImsConferenceState.STATUS_ON_HOLD)) {
+                    referrer.mListener.onCallHoldReceived(referrer);
+                }
+                else if (status.equals(ImsConferenceState.STATUS_CONNECTED)) {
+                    referrer.mListener.onCallStarted(referrer);
+                }
+                else if (status.equals(ImsConferenceState.STATUS_DISCONNECTED)) {
+                    referrer.clear(new ImsReasonInfo());
+                    referrer.mListener.onCallTerminated(referrer, referrer.mLastReasonInfo);
+                }
+            } catch (Throwable t) {
+                loge("notifyConferenceStateUpdated :: ", t);
+            }
+        }
+    }
+
+    private void notifyError(int reason, int statusCode, String message) {
+    }
+
+    private void throwImsException(Throwable t, int code) throws ImsException {
+        if (t instanceof ImsException) {
+            throw (ImsException) t;
+        } else {
+            throw new ImsException(String.valueOf(code), t, code);
+        }
+    }
+
+    private void log(String s) {
+        Rlog.d(TAG, s);
+    }
+
+    private void loge(String s) {
+        Rlog.e(TAG, s);
+    }
+
+    private void loge(String s, Throwable t) {
+        Rlog.e(TAG, s, t);
+    }
+
+    private class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
+        @Override
+        public void callSessionProgressing(ImsCallSession session,
+                ImsStreamMediaProfile profile) {
+            if (DBG) {
+                log("callSessionProgressing :: session=" + session + ", profile=" + profile);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                mCallProfile.mMediaProfile.copyFrom(profile);
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallProgressing(ImsCall.this);
+                } catch (Throwable t) {
+                    loge("callSessionProgressing :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionStarted(ImsCallSession session,
+                ImsCallProfile profile) {
+            if (DBG) {
+                log("callSessionStarted :: session=" + session + ", profile=" + profile);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                mCallProfile = profile;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallStarted(ImsCall.this);
+                } catch (Throwable t) {
+                    loge("callSessionStarted :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionStartFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (DBG) {
+                log("callSessionStartFailed :: session=" + session +
+                        ", reasonInfo=" + reasonInfo);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                mLastReasonInfo = reasonInfo;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallStartFailed(ImsCall.this, reasonInfo);
+                } catch (Throwable t) {
+                    loge("callSessionStarted :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionTerminated(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (DBG) {
+                log("callSessionTerminated :: session=" + session +
+                        ", reasonInfo=" + reasonInfo);
+            }
+
+            ImsCall.Listener listener = null;
+
+            synchronized(ImsCall.this) {
+                if (mCallGroup != null) {
+                    notifyConferenceSessionTerminated(reasonInfo);
+                } else {
+                    listener = mListener;
+                    clear(reasonInfo);
+                }
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallTerminated(ImsCall.this, reasonInfo);
+                } catch (Throwable t) {
+                    loge("callSessionTerminated :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionHeld(ImsCallSession session,
+                ImsCallProfile profile) {
+            if (DBG) {
+                log("callSessionHeld :: session=" + session + ", profile=" + profile);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                mCallProfile = profile;
+
+                if (mUpdateRequest == UPDATE_HOLD_MERGE) {
+                    mergeInternal();
+                    return;
+                }
+
+                mUpdateRequest = UPDATE_NONE;
+                listener = mListener;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallHeld(ImsCall.this);
+                } catch (Throwable t) {
+                    loge("callSessionHeld :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionHoldFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (DBG) {
+                log("callSessionHoldFailed :: session=" + session +
+                        ", reasonInfo=" + reasonInfo);
+            }
+
+            boolean isHoldForMerge = false;
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                if (mUpdateRequest == UPDATE_HOLD_MERGE) {
+                    isHoldForMerge = true;
+                }
+
+                mUpdateRequest = UPDATE_NONE;
+                listener = mListener;
+            }
+
+            if (isHoldForMerge) {
+                callSessionMergeFailed(session, reasonInfo);
+                return;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallHoldFailed(ImsCall.this, reasonInfo);
+                } catch (Throwable t) {
+                    loge("callSessionHoldFailed :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionHoldReceived(ImsCallSession session,
+                ImsCallProfile profile) {
+            if (DBG) {
+                log("callSessionHoldReceived :: session=" + session + ", profile=" + profile);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                mCallProfile = profile;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallHoldReceived(ImsCall.this);
+                } catch (Throwable t) {
+                    loge("callSessionHoldReceived :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionResumed(ImsCallSession session,
+                ImsCallProfile profile) {
+            if (DBG) {
+                log("callSessionResumed :: session=" + session + ", profile=" + profile);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                mCallProfile = profile;
+                mUpdateRequest = UPDATE_NONE;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallResumed(ImsCall.this);
+                } catch (Throwable t) {
+                    loge("callSessionResumed :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionResumeFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (DBG) {
+                log("callSessionResumeFailed :: session=" + session +
+                        ", reasonInfo=" + reasonInfo);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                mUpdateRequest = UPDATE_NONE;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallResumeFailed(ImsCall.this, reasonInfo);
+                } catch (Throwable t) {
+                    loge("callSessionResumeFailed :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionResumeReceived(ImsCallSession session,
+                ImsCallProfile profile) {
+            if (DBG) {
+                log("callSessionResumeReceived :: session=" + session +
+                        ", profile=" + profile);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                mCallProfile = profile;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallResumeReceived(ImsCall.this);
+                } catch (Throwable t) {
+                    loge("callSessionResumeReceived :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionMerged(ImsCallSession session,
+                ImsCallSession newSession, ImsCallProfile profile) {
+            if (DBG) {
+                log("callSessionMerged :: session=" + session
+                        + ", newSession=" + newSession + ", profile=" + profile);
+            }
+
+            ImsCall newCall = createNewCall(newSession, profile);
+
+            if (newCall == null) {
+                callSessionMergeFailed(session, new ImsReasonInfo());
+                return;
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                updateCallGroup(newCall);
+                mUpdateRequest = UPDATE_NONE;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallMerged(ImsCall.this, newCall);
+                } catch (Throwable t) {
+                    loge("callSessionMerged :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionMergeFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (DBG) {
+                log("callSessionMergeFailed :: session=" + session +
+                        ", reasonInfo=" + reasonInfo);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                updateCallGroup(null);
+                mUpdateRequest = UPDATE_NONE;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallMergeFailed(ImsCall.this, reasonInfo);
+                } catch (Throwable t) {
+                    loge("callSessionMergeFailed :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionUpdated(ImsCallSession session,
+                ImsCallProfile profile) {
+            if (DBG) {
+                log("callSessionUpdated :: session=" + session + ", profile=" + profile);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                mCallProfile = profile;
+                mUpdateRequest = UPDATE_NONE;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallUpdated(ImsCall.this);
+                } catch (Throwable t) {
+                    loge("callSessionUpdated :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionUpdateFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (DBG) {
+                log("callSessionUpdateFailed :: session=" + session +
+                        ", reasonInfo=" + reasonInfo);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                mUpdateRequest = UPDATE_NONE;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
+                } catch (Throwable t) {
+                    loge("callSessionUpdateFailed :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionUpdateReceived(ImsCallSession session,
+                ImsCallProfile profile) {
+            if (DBG) {
+                log("callSessionUpdateReceived :: session=" + session +
+                        ", profile=" + profile);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                mProposedCallProfile = profile;
+                mUpdateRequest = UPDATE_UNSPECIFIED;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallUpdateReceived(ImsCall.this);
+                } catch (Throwable t) {
+                    loge("callSessionUpdateReceived :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionConferenceExtended(ImsCallSession session,
+                ImsCallSession newSession, ImsCallProfile profile) {
+            if (DBG) {
+                log("callSessionConferenceExtended :: session=" + session
+                        + ", newSession=" + newSession + ", profile=" + profile);
+            }
+
+            ImsCall newCall = createNewCall(newSession, profile);
+
+            if (newCall == null) {
+                callSessionConferenceExtendFailed(session, new ImsReasonInfo());
+                return;
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                mUpdateRequest = UPDATE_NONE;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallConferenceExtended(ImsCall.this, newCall);
+                } catch (Throwable t) {
+                    loge("callSessionConferenceExtended :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionConferenceExtendFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (DBG) {
+                log("callSessionConferenceExtendFailed :: session=" + session +
+                        ", reasonInfo=" + reasonInfo);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                mUpdateRequest = UPDATE_NONE;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
+                } catch (Throwable t) {
+                    loge("callSessionConferenceExtendFailed :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionConferenceExtendReceived(ImsCallSession session,
+                ImsCallSession newSession, ImsCallProfile profile) {
+            if (DBG) {
+                log("callSessionConferenceExtendReceived :: session=" + session
+                        + ", newSession=" + newSession + ", profile=" + profile);
+            }
+
+            ImsCall newCall = createNewCall(newSession, profile);
+
+            if (newCall == null) {
+                // Should all the calls be terminated...???
+                return;
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
+                } catch (Throwable t) {
+                    loge("callSessionConferenceExtendReceived :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
+            if (DBG) {
+                log("callSessionInviteParticipantsRequestDelivered :: session=" + session);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
+                } catch (Throwable t) {
+                    loge("callSessionInviteParticipantsRequestDelivered :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (DBG) {
+                log("callSessionInviteParticipantsRequestFailed :: session=" + session
+                        + ", reasonInfo=" + reasonInfo);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
+                } catch (Throwable t) {
+                    loge("callSessionInviteParticipantsRequestFailed :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
+            if (DBG) {
+                log("callSessionRemoveParticipantsRequestDelivered :: session=" + session);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
+                } catch (Throwable t) {
+                    loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (DBG) {
+                log("callSessionRemoveParticipantsRequestFailed :: session=" + session
+                        + ", reasonInfo=" + reasonInfo);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
+                } catch (Throwable t) {
+                    loge("callSessionRemoveParticipantsRequestFailed :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionConferenceStateUpdated(ImsCallSession session,
+                ImsConferenceState state) {
+            if (DBG) {
+                log("callSessionConferenceStateUpdated :: session=" + session
+                        + ", state=" + state);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                notifyConferenceStateUpdated(state);
+                listener = mListener;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallConferenceStateUpdated(ImsCall.this, state);
+                } catch (Throwable t) {
+                    loge("callSessionConferenceStateUpdated :: ", t);
+                }
+            }
+        }
+
+        @Override
+        public void callSessionUssdMessageReceived(ImsCallSession session,
+                int mode, String ussdMessage) {
+            if (DBG) {
+                log("callSessionUssdMessageReceived :: session=" + session
+                        + ", mode=" + mode + ", ussdMessage=" + ussdMessage);
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
+                } catch (Throwable t) {
+                    loge("callSessionUssdMessageReceived :: ", t);
+                }
+            }
+        }
+    }
+}
diff --git a/src/java/com/android/ims/ImsCallForwardInfo.aidl b/src/java/com/android/ims/ImsCallForwardInfo.aidl
new file mode 100644 (file)
index 0000000..a7c3f9a
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+parcelable ImsCallForwardInfo;
diff --git a/src/java/com/android/ims/ImsCallForwardInfo.java b/src/java/com/android/ims/ImsCallForwardInfo.java
new file mode 100644 (file)
index 0000000..3f8fd19
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Provides the call forward information for the supplementary service configuration.
+ *
+ * @hide
+ */
+public class ImsCallForwardInfo implements Parcelable {
+    // Refer to ImsUtInterface#CDIV_CF_XXX
+    public int mCondition;
+    // 0: disabled, 1: enabled
+    public int mStatus;
+    // 0x91: International, 0x81: Unknown
+    public int mToA;
+    // Number (it will not include the "sip" or "tel" URI scheme)
+    public String mNumber;
+    // No reply timer for CF
+    public int mTimeSeconds;
+
+    public ImsCallForwardInfo() {
+    }
+
+    public ImsCallForwardInfo(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mCondition);
+        out.writeInt(mStatus);
+        out.writeInt(mToA);
+        out.writeString(mNumber);
+        out.writeInt(mTimeSeconds);
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + ", Condition: " + mCondition
+            + ", Status: " + ((mStatus == 0) ? "disabled" : "enabled")
+            + ", ToA: " + mToA + ", Number=" + mNumber
+            + ", Time (seconds): " + mTimeSeconds;
+    }
+
+    private void readFromParcel(Parcel in) {
+        mCondition = in.readInt();
+        mStatus = in.readInt();
+        mToA = in.readInt();
+        mNumber = in.readString();
+        mTimeSeconds = in.readInt();
+    }
+
+    public static final Creator<ImsCallForwardInfo> CREATOR =
+            new Creator<ImsCallForwardInfo>() {
+        @Override
+        public ImsCallForwardInfo createFromParcel(Parcel in) {
+            return new ImsCallForwardInfo(in);
+        }
+
+        @Override
+        public ImsCallForwardInfo[] newArray(int size) {
+            return new ImsCallForwardInfo[size];
+        }
+    };
+}
diff --git a/src/java/com/android/ims/ImsCallGroup.java b/src/java/com/android/ims/ImsCallGroup.java
new file mode 100644 (file)
index 0000000..2e1a749
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+import java.util.ArrayList;
+
+import com.android.ims.internal.ICallGroup;
+import com.android.ims.internal.ICall;
+
+/**
+ * Manages all IMS calls which are established hereafter the initial 1-to-1 call is established.
+ * It's for providing the dummy calls which are disconnected with the IMS network after
+ * merged or extended to the conference.
+ *
+ * @hide
+ */
+public class ImsCallGroup implements ICallGroup {
+    private Object mLockObj = new Object();
+    private ImsCall mOwner;
+    private ImsCall mNeutralReferrer;
+    private ArrayList<ICall> mReferrers = new ArrayList<ICall>();
+
+    public ImsCallGroup() {
+    }
+
+    @Override
+    public ICall getNeutralReferrer() {
+        synchronized(mLockObj) {
+            return mNeutralReferrer;
+        }
+    }
+
+    @Override
+    public ICall getOwner() {
+        synchronized(mLockObj) {
+            return mOwner;
+        }
+    }
+
+    @Override
+    public ArrayList<ICall> getReferrers() {
+        synchronized(mLockObj) {
+            return mReferrers;
+        }
+    }
+
+    @Override
+    public boolean hasReferrer() {
+        synchronized(mLockObj) {
+            return !mReferrers.isEmpty();
+        }
+    }
+
+    @Override
+    public boolean isOwner(ICall call) {
+        ImsCall owner;
+
+        synchronized(mLockObj) {
+            owner = mOwner;
+        }
+
+        if ((call == null) || (owner == null)) {
+            return false;
+        }
+
+        if (!(call instanceof ImsCall)) {
+            return false;
+        }
+
+        return isSameCall(owner, (ImsCall)call);
+    }
+
+    @Override
+    public boolean isReferrer(ICall call) {
+        if (call == null) {
+            return false;
+        }
+
+        if (!(call instanceof ImsCall)) {
+            return false;
+        }
+
+        synchronized(mLockObj) {
+            for (ICall c : mReferrers) {
+                if ((c != null) && isSameCall((ImsCall)c, (ImsCall)call)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public void addReferrer(ICall call) {
+        if (call == null) {
+            return;
+        }
+
+        if (!(call instanceof ImsCall)) {
+            return;
+        }
+
+        // If the call is already present, ignore it
+        if (isReferrer(call)) {
+            return;
+        }
+
+        synchronized(mLockObj) {
+            mReferrers.add(call);
+        }
+    }
+
+    @Override
+    public void removeReferrer(ICall call) {
+        if (call == null) {
+            return;
+        }
+
+        if (!(call instanceof ImsCall)) {
+            return;
+        }
+
+        synchronized(mLockObj) {
+            mReferrers.remove(call);
+        }
+    }
+
+    @Override
+    public void setNeutralReferrer(ICall call) {
+        if ((call != null) && !(call instanceof ImsCall)) {
+            return;
+        }
+
+        synchronized(mLockObj) {
+            mNeutralReferrer = (ImsCall)call;
+        }
+    }
+
+    @Override
+    public void setOwner(ICall call) {
+        if ((call != null) && !(call instanceof ImsCall)) {
+            return;
+        }
+
+        synchronized(mLockObj) {
+            mOwner = (ImsCall)call;
+        }
+    }
+
+    @Override
+    public ICall getReferrer(String name) {
+        if ((name == null) || (name.isEmpty())) {
+            return null;
+        }
+
+        ArrayList<ICall> referrers = getReferrers();
+
+        if (referrers == null) {
+            return null;
+        }
+
+        for (ICall call : referrers) {
+            if ((call != null) && call.checkIfRemoteUserIsSame(name)) {
+                return call;
+            }
+        }
+
+        return null;
+    }
+
+    private boolean isSameCall(ImsCall call1, ImsCall call2) {
+        if ((call1 == null) || (call2 == null)) {
+            return false;
+        }
+
+        return call1.equalsTo(call2);
+    }
+}
diff --git a/src/java/com/android/ims/ImsCallProfile.aidl b/src/java/com/android/ims/ImsCallProfile.aidl
new file mode 100644 (file)
index 0000000..a356d13
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+parcelable ImsCallProfile;
diff --git a/src/java/com/android/ims/ImsCallProfile.java b/src/java/com/android/ims/ImsCallProfile.java
new file mode 100644 (file)
index 0000000..208f467
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parcelable object to handle IMS call profile.
+ * It is created from GSMA IR.92/IR.94, 3GPP TS 24.229/TS 26.114/TS26.111.
+ * It provides the service and call type, the additional information related to the call.
+ *
+ * @hide
+ */
+public class ImsCallProfile implements Parcelable {
+    private static final String TAG = "ImsCallProfile";
+
+    /**
+     * Service types
+     */
+    /**
+     * It is for a special case. It helps that the application can make a call
+     * without IMS connection (not registered).
+     * In the moment of the call initiation, the device try to connect to the IMS network
+     * and initiates the call.
+     */
+    public static final int SERVICE_TYPE_NONE = 0;
+    /**
+     * It is a default type and can be selected when the device is connected to the IMS network.
+     */
+    public static final int SERVICE_TYPE_NORMAL = 1;
+    /**
+     * It is for an emergency call.
+     */
+    public static final int SERVICE_TYPE_EMERGENCY = 2;
+
+    /**
+     * Call types
+     */
+    /**
+     * IMSPhone to support IR.92 & IR.94 (voice + video upgrade/downgrade)
+     */
+    public static final int CALL_TYPE_VOICE_N_VIDEO = 1;
+    /**
+     * IR.92 (Voice only)
+     */
+    public static final int CALL_TYPE_VOICE = 2;
+    /**
+     * VT to support IR.92 & IR.94 (voice + video upgrade/downgrade)
+     */
+    public static final int CALL_TYPE_VIDEO_N_VOICE = 3;
+    /**
+     * Video Telephony (audio / video two way)
+     */
+    public static final int CALL_TYPE_VT = 4;
+    /**
+     * Video Telephony (audio two way / video TX one way)
+     */
+    public static final int CALL_TYPE_VT_TX = 5;
+    /**
+     * Video Telephony (audio two way / video RX one way)
+     */
+    public static final int CALL_TYPE_VT_RX = 6;
+    /**
+     * Video Telephony (audio two way / video inactive)
+     */
+    public static final int CALL_TYPE_VT_NODIR = 7;
+    /**
+     * VideoShare (video two way)
+     */
+    public static final int CALL_TYPE_VS = 8;
+    /**
+     * VideoShare (video TX one way)
+     */
+    public static final int CALL_TYPE_VS_TX = 9;
+    /**
+     * VideoShare (video RX one way)
+     */
+    public static final int CALL_TYPE_VS_RX = 10;
+
+    /**
+     * Extra properties for IMS call.
+     */
+    /**
+     * Boolean extra properties - "true" / "false"
+     *  conference : Indicates if the session is for the conference call or not.
+     *  e_call : Indicates if the session is for the emergency call or not.
+     *  vms : Indicates if the session is connected to the voice mail system or not.
+     *  call_mode_changeable : Indicates if the session is able to upgrade/downgrade
+     *      the video during voice call.
+     *  conference_avail : Indicates if the session can be extended to the conference.
+     */
+    public static final String EXTRA_CONFERENCE = "conference";
+    public static final String EXTRA_E_CALL = "e_call";
+    public static final String EXTRA_VMS = "vms";
+    public static final String EXTRA_CALL_MODE_CHANGEABLE = "call_mode_changeable";
+    public static final String EXTRA_CONFERENCE_AVAIL = "conference_avail";
+
+    /**
+     * Integer extra properties
+     *  oir : Rule for originating identity (number) presentation, MO/MT.
+     *      {@link ImsCallProfile#OIR_DEFAULT}
+     *      {@link ImsCallProfile#OIR_PRESENTATION_RESTRICTED}
+     *      {@link ImsCallProfile#OIR_PRESENTATION_NOT_RESTRICTED}
+     *  cnap : Rule for calling name presentation
+     *      {@link ImsCallProfile#OIR_DEFAULT}
+     *      {@link ImsCallProfile#OIR_PRESENTATION_RESTRICTED}
+     *      {@link ImsCallProfile#OIR_PRESENTATION_NOT_RESTRICTED}
+     *  dialstring : To identify the Ims call type, MO
+     *      {@link ImsCallProfile#DIALSTRING_NORMAL_CALL}
+     *      {@link ImsCallProfile#DIALSTRING_SS_CONF}
+     *      {@link ImsCallProfile#DIALSTRING_USSD}
+     */
+    public static final String EXTRA_OIR = "oir";
+    public static final String EXTRA_CNAP = "cnap";
+    public static final String EXTRA_DIALSTRING = "dialstring";
+
+    /**
+     * Values for EXTRA_OIR / EXTRA_CNAP
+     */
+    public static final int OIR_DEFAULT = 0;    // "user subscription default value"
+    public static final int OIR_PRESENTATION_RESTRICTED = 1;
+    public static final int OIR_PRESENTATION_NOT_RESTRICTED = 2;
+
+    /**
+     * Values for EXTRA_DIALSTRING
+     */
+    // default (normal call)
+    public static final int DIALSTRING_NORMAL = 0;
+    // Call for SIP-based user configuration
+    public static final int DIALSTRING_SS_CONF = 1;
+    // Call for USSD message
+    public static final int DIALSTRING_USSD = 2;
+
+    /**
+     * String extra properties
+     *  oi : Originating identity (number), MT only
+     *  cna : Calling name
+     *  ussd : For network-initiated USSD, MT only
+     *  remote_uri : Connected user identity (it can be used for the conference)
+     */
+    public static final String EXTRA_OI = "oi";
+    public static final String EXTRA_CNA = "cna";
+    public static final String EXTRA_USSD = "ussd";
+    public static final String EXTRA_REMOTE_URI = "remote_uri";
+
+    public int mServiceType;
+    public int mCallType;
+    public Bundle mCallExtras;
+    public ImsStreamMediaProfile mMediaProfile;
+
+
+
+    public ImsCallProfile(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public ImsCallProfile() {
+        mServiceType = SERVICE_TYPE_NORMAL;
+        mCallType = CALL_TYPE_VOICE_N_VIDEO;
+        mCallExtras = new Bundle();
+        mMediaProfile = new ImsStreamMediaProfile();
+    }
+
+    public ImsCallProfile(int serviceType, int callType) {
+        mServiceType = serviceType;
+        mCallType = callType;
+        mCallExtras = new Bundle();
+        mMediaProfile = new ImsStreamMediaProfile();
+    }
+
+    public String getCallExtra(String name) {
+        return getCallExtra(name, "");
+    }
+
+    public String getCallExtra(String name, String defaultValue) {
+        if (mCallExtras == null) {
+            return defaultValue;
+        }
+
+        return mCallExtras.getString(name, defaultValue);
+    }
+
+    public boolean getCallExtraBoolean(String name) {
+        return getCallExtraBoolean(name, false);
+    }
+
+    public boolean getCallExtraBoolean(String name, boolean defaultValue) {
+        if (mCallExtras == null) {
+            return defaultValue;
+        }
+
+        return mCallExtras.getBoolean(name, defaultValue);
+    }
+
+    public int getCallExtraInt(String name) {
+        return getCallExtraInt(name, -1);
+    }
+
+    public int getCallExtraInt(String name, int defaultValue) {
+        if (mCallExtras == null) {
+            return defaultValue;
+        }
+
+        return mCallExtras.getInt(name, defaultValue);
+    }
+
+    public void setCallExtra(String name, String value) {
+        if (mCallExtras != null) {
+            mCallExtras.putString(name, value);
+        }
+    }
+
+    public void setCallExtraBoolean(String name, boolean value) {
+        if (mCallExtras != null) {
+            mCallExtras.putBoolean(name, value);
+        }
+    }
+
+    public void setCallExtraInt(String name, int value) {
+        if (mCallExtras != null) {
+            mCallExtras.putInt(name, value);
+        }
+    }
+
+    public void updateCallType(ImsCallProfile profile) {
+        mCallType = profile.mCallType;
+    }
+
+    public void updateCallExtras(ImsCallProfile profile) {
+        mCallExtras.clear();
+        mCallExtras = (Bundle) profile.mCallExtras.clone();
+    }
+
+    @Override
+    public String toString() {
+        return "{ serviceType=" + mServiceType +
+                ", callType=" + mCallType +
+                ", callExtras=" + mCallExtras.toString() +
+                ", mediaProfile=" + mMediaProfile.toString() + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mServiceType);
+        out.writeInt(mCallType);
+        out.writeParcelable(mCallExtras, 0);
+        out.writeParcelable(mMediaProfile, 0);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mServiceType = in.readInt();
+        mCallType = in.readInt();
+        mCallExtras = in.readParcelable(null);
+        mMediaProfile = in.readParcelable(null);
+    }
+
+    public static final Creator<ImsCallProfile> CREATOR = new Creator<ImsCallProfile>() {
+        @Override
+        public ImsCallProfile createFromParcel(Parcel in) {
+            return new ImsCallProfile(in);
+        }
+
+        @Override
+        public ImsCallProfile[] newArray(int size) {
+            return new ImsCallProfile[size];
+        }
+    };
+}
diff --git a/src/java/com/android/ims/ImsConferenceState.aidl b/src/java/com/android/ims/ImsConferenceState.aidl
new file mode 100644 (file)
index 0000000..2fc029f
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+parcelable ImsConferenceState;
diff --git a/src/java/com/android/ims/ImsConferenceState.java b/src/java/com/android/ims/ImsConferenceState.java
new file mode 100644 (file)
index 0000000..f708d5b
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Provides the conference information (defined in RFC 4575) for IMS conference call.
+ *
+ * @hide
+ */
+public class ImsConferenceState implements Parcelable {
+    /**
+     * conference-info : user
+     */
+    // user (String) : Tel or SIP URI
+    public static final String USER = "user";
+    // user > display text (String)
+    public static final String DISPLAY_TEXT = "display-text";
+    // user > endpoint (String) : URI or GRUU or Phone number
+    public static final String ENDPOINT = "endpoint";
+    // user > endpoint > status
+    public static final String STATUS = "status";
+
+    /**
+     * status-type (String) :
+     * "pending" : Endpoint is not yet in the session, but it is anticipated that he/she will
+     *      join in the near future.
+     * "dialing-out" : Focus has dialed out to connect the endpoint to the conference,
+     *      but the endpoint is not yet in the roster (probably being authenticated).
+     * "dialing-in" : Endpoint is dialing into the conference, not yet in the roster
+     *      (probably being authenticated).
+     * "alerting" : PSTN alerting or SIP 180 Ringing was returned for the outbound call;
+     *      endpoint is being alerted.
+     * "on-hold" : Active signaling dialog exists between an endpoint and a focus,
+     *      but endpoint is "on-hold" for this conference, i.e., he/she is neither "hearing"
+     *      the conference mix nor is his/her media being mixed in the conference.
+     * "connected" : Endpoint is a participant in the conference. Depending on the media policies,
+     *      he/she can send and receive media to and from other participants.
+     * "disconnecting" : Focus is in the process of disconnecting the endpoint
+     *      (e.g. in SIP a DISCONNECT or BYE was sent to the endpoint).
+     * "disconnected" : Endpoint is not a participant in the conference, and no active dialog
+     *      exists between the endpoint and the focus.
+     * "muted-via-focus" : Active signaling dialog exists beween an endpoint and a focus and
+     *      the endpoint can "listen" to the conference, but the endpoint's media is not being
+     *      mixed into the conference.
+     * "connect-fail" : Endpoint fails to join the conference by rejecting the conference call.
+     */
+    public static final String STATUS_PENDING = "pending";
+    public static final String STATUS_DIALING_OUT = "dialing-out";
+    public static final String STATUS_DIALING_IN = "dialing-in";
+    public static final String STATUS_ALERTING = "alerting";
+    public static final String STATUS_ON_HOLD = "on-hold";
+    public static final String STATUS_CONNECTED = "connected";
+    public static final String STATUS_DISCONNECTING = "disconnecting";
+    public static final String STATUS_DISCONNECTED = "disconnected";
+    public static final String STATUS_MUTED_VIA_FOCUS = "muted-via-focus";
+    public static final String STATUS_CONNECT_FAIL = "connect-fail";
+
+    /**
+     * conference-info : SIP status code (integer)
+     */
+    public static final String SIP_STATUS_CODE = "sipstatuscode";
+
+    public HashMap<String, Bundle> mParticipants = new HashMap<String, Bundle>();
+
+    public ImsConferenceState() {
+    }
+
+    public ImsConferenceState(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mParticipants.size());
+
+        if (mParticipants.size() > 0) {
+            Set<Entry<String, Bundle>> entries = mParticipants.entrySet();
+
+            if (entries != null) {
+                Iterator<Entry<String, Bundle>> iterator = entries.iterator();
+
+                while (iterator.hasNext()) {
+                    Entry<String, Bundle> entry = iterator.next();
+
+                    out.writeString(entry.getKey());
+                    out.writeParcelable(entry.getValue(), 0);
+                }
+            }
+        }
+    }
+
+    private void readFromParcel(Parcel in) {
+        int size = in.readInt();
+
+        for (int i = 0; i < size; ++i) {
+            String user = in.readString();
+            Bundle state = in.readParcelable(null);
+            mParticipants.put(user, state);
+        }
+    }
+
+    public static final Creator<ImsConferenceState> CREATOR =
+            new Creator<ImsConferenceState>() {
+        @Override
+        public ImsConferenceState createFromParcel(Parcel in) {
+            return new ImsConferenceState(in);
+        }
+
+        @Override
+        public ImsConferenceState[] newArray(int size) {
+            return new ImsConferenceState[size];
+        }
+    };
+}
diff --git a/src/java/com/android/ims/ImsConnectionStateListener.java b/src/java/com/android/ims/ImsConnectionStateListener.java
new file mode 100644 (file)
index 0000000..42ac717
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+/**
+ * Listener for receiving notifications about changes to the IMS connection.
+ * It provides a state of IMS registration between UE and IMS network, the service
+ * availability of the local device during IMS registered.
+ *
+ * @hide
+ */
+public class ImsConnectionStateListener {
+    /**
+     * Called when the device is connected to the IMS network.
+     */
+    public void onImsConnected() {
+        // no-op
+    }
+
+    /**
+     * Called when the device is disconnected from the IMS network.
+     */
+    public void onImsDisconnected() {
+        // no-op
+    }
+
+    /**
+     * Called when its suspended IMS connection is resumed, meaning the connection
+     * now allows throughput.
+     */
+    public void onImsResumed() {
+        // no-op
+    }
+
+    /**
+     * Called when its current IMS connection is suspended, meaning there is no data throughput.
+     */
+    public void onImsSuspended() {
+        // no-op
+    }
+}
diff --git a/src/java/com/android/ims/ImsException.java b/src/java/com/android/ims/ImsException.java
new file mode 100644 (file)
index 0000000..9ec3546
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+/**
+ * This class defines a general IMS-related exception.
+ *
+ * @hide
+ */
+public class ImsException extends Exception {
+
+    /**
+     * Refer to CODE_LOCAL_* in {@link ImsReasonInfo}
+     */
+    private int mCode;
+
+    public ImsException() {
+    }
+
+    public ImsException(String message, int code) {
+        super(message);
+        mCode = code;
+    }
+
+    public ImsException(String message, Throwable cause, int code) {
+        super(message, cause);
+        mCode = code;
+    }
+
+    /**
+     * Gets the detailed exception code when ImsException is throwed
+     *
+     * @return the exception code in {@link ImsReasonInfo}
+     */
+    public int getCode() {
+        return mCode;
+    }
+}
diff --git a/src/java/com/android/ims/ImsManager.java b/src/java/com/android/ims/ImsManager.java
new file mode 100644 (file)
index 0000000..3e59f1a
--- /dev/null
@@ -0,0 +1,601 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.Rlog;
+
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsService;
+import com.android.ims.internal.IImsUt;
+import com.android.ims.internal.ImsCallSession;
+
+/**
+ * Provides APIs for IMS services, such as initiating IMS calls, and provides access to
+ * the operator's IMS network. This class is the starting point for any IMS actions.
+ * You can acquire an instance of it with {@link #getInstance getInstance()}.</p>
+ * <p>The APIs in this class allows you to:</p>
+ *
+ * @hide
+ */
+public class ImsManager {
+    /**
+     * For accessing the IMS related service.
+     * Internal use only.
+     * @hide
+     */
+    public static final String IMS_SERVICE = "ims";
+
+    /**
+     * The result code to be sent back with the incoming call {@link PendingIntent}.
+     * @see #open(PendingIntent, ImsConnectionStateListener)
+     */
+    public static final int INCOMING_CALL_RESULT_CODE = 101;
+
+    /**
+     * Key to retrieve the call ID from an incoming call intent.
+     * @see #open(PendingIntent, ImsConnectionStateListener)
+     */
+    public static final String EXTRA_CALL_ID = "android:imsCallID";
+
+    /**
+     * Action to broadcast when ImsService is up.
+     * Internal use only.
+     * @hide
+     */
+    public static final String ACTION_IMS_SERVICE_UP =
+            "com.android.ims.IMS_SERVICE_UP";
+
+    /**
+     * Action to broadcast when ImsService is down.
+     * Internal use only.
+     * @hide
+     */
+    public static final String ACTION_IMS_SERVICE_DOWN =
+            "com.android.ims.IMS_SERVICE_DOWN";
+
+    /**
+     * Action for the incoming call intent for the Phone app.
+     * Internal use only.
+     * @hide
+     */
+    public static final String ACTION_IMS_INCOMING_CALL =
+            "com.android.ims.IMS_INCOMING_CALL";
+
+    /**
+     * Part of the ACTION_IMS_INCOMING_CALL intents.
+     * An integer value; service identifier obtained from {@link ImsManager#open}.
+     * Internal use only.
+     * @hide
+     */
+    public static final String EXTRA_SERVICE_ID = "android:imsServiceId";
+
+    /**
+     * Part of the ACTION_IMS_INCOMING_CALL intents.
+     * An boolean value; Flag to indicate that the incoming call is a normal call or call for USSD.
+     * The value "true" indicates that the incoming call is for USSD.
+     * Internal use only.
+     * @hide
+     */
+    public static final String EXTRA_USSD = "android:ussd";
+
+
+
+    private static final String TAG = "ImsManager";
+    private static final boolean DBG = true;
+
+    private static ImsManager mImsManager = null;
+    private Context mContext;
+    private IImsService mImsService = null;
+    private ImsServiceDeathRecipient mDeathRecipient = new ImsServiceDeathRecipient();
+    // Ut interface for the supplementary service configuration
+    private ImsUt mUt = null;
+
+    /**
+     * Gets a manager instance.
+     *
+     * @param context application context for creating the manager object
+     * @return the manager instance
+     */
+    public static ImsManager getInstance(Context context) {
+        if (mImsManager == null) {
+            mImsManager = new ImsManager(context);
+        }
+
+        return mImsManager;
+    }
+
+    private ImsManager(Context context) {
+        mContext = context;
+        createImsService(true);
+    }
+
+    /**
+     * Opens the IMS service for making calls and/or receiving generic IMS calls.
+     * The caller may make subsquent calls through {@link #makeCall}.
+     * The IMS service will register the device to the operator's network with the credentials
+     * (from ISIM) periodically in order to receive calls from the operator's network.
+     * When the IMS service receives a new call, it will send out an intent with
+     * the provided action string.
+     * The intent contains a call ID extra {@link getCallId} and it can be used to take a call.
+     *
+     * @param serviceClass a service class specified in {@link ImsServiceClass}
+     *      For VoLTE service, it MUST be a {@link ImsServiceClass#MMTEL}.
+     * @param incomingCallPendingIntent When an incoming call is received,
+     *        the IMS service will call {@link PendingIntent#send(Context, int, Intent)} to
+     *        send back the intent to the caller with {@link #INCOMING_CALL_RESULT_CODE}
+     *        as the result code and the intent to fill in the call ID; It cannot be null
+     * @param listener To listen to IMS registration events; It cannot be null
+     * @return identifier (greater than 0) for the specified service
+     * @throws NullPointerException if {@code incomingCallPendingIntent}
+     *      or {@code listener} is null
+     * @throws ImsException if calling the IMS service results in an error
+     * @see #getCallId
+     * @see #getServiceId
+     */
+    public int open(int serviceClass, PendingIntent incomingCallPendingIntent,
+            ImsConnectionStateListener listener) throws ImsException {
+        checkAndThrowExceptionIfServiceUnavailable();
+
+        if (incomingCallPendingIntent == null) {
+            throw new NullPointerException("incomingCallPendingIntent can't be null");
+        }
+
+        if (listener == null) {
+            throw new NullPointerException("listener can't be null");
+        }
+
+        int result = 0;
+
+        try {
+            result = mImsService.open(serviceClass, incomingCallPendingIntent,
+                    createRegistrationListenerProxy(serviceClass, listener));
+        } catch (RemoteException e) {
+            throw new ImsException("open()", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+
+        if (result <= 0) {
+            // If the return value is a minus value,
+            // it means that an error occurred in the service.
+            // So, it needs to convert to the reason code specified in ImsReasonInfo.
+            throw new ImsException("open()", (result * (-1)));
+        }
+
+        return result;
+    }
+
+    /**
+     * Closes the specified service ({@link ImsServiceClass}) not to make/receive calls.
+     * All the resources that were allocated to the service are also released.
+     *
+     * @param serviceId a service id to be closed which is obtained from {@link ImsManager#open}
+     * @throws ImsException if calling the IMS service results in an error
+     */
+    public void close(int serviceId) throws ImsException {
+        checkAndThrowExceptionIfServiceUnavailable();
+
+        try {
+            mImsService.close(serviceId);
+        } catch (RemoteException e) {
+            throw new ImsException("close()", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        } finally {
+            mUt = null;
+        }
+    }
+
+    /**
+     * Gets the configuration interface to provision / withdraw the supplementary service settings.
+     *
+     * @param serviceId a service id which is obtained from {@link ImsManager#open}
+     * @return the Ut interface instance
+     * @throws ImsException if getting the Ut interface results in an error
+     */
+    public ImsUtInterface getSupplementaryServiceConfiguration(int serviceId)
+            throws ImsException {
+        // FIXME: manage the multiple Ut interfaces based on the service id
+        if (mUt == null) {
+            checkAndThrowExceptionIfServiceUnavailable();
+
+            try {
+                IImsUt iUt = mImsService.getUtInterface(serviceId);
+
+                if (iUt == null) {
+                    throw new ImsException("getSupplementaryServiceConfiguration()",
+                            ImsReasonInfo.CODE_UT_NOT_SUPPORTED);
+                }
+
+                mUt = new ImsUt(iUt);
+            } catch (RemoteException e) {
+                throw new ImsException("getSupplementaryServiceConfiguration()", e,
+                        ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+            }
+        }
+
+        return mUt;
+    }
+
+    /**
+     * Checks if the IMS service has successfully registered to the IMS network
+     * with the specified service & call type.
+     *
+     * @param serviceId a service id which is obtained from {@link ImsManager#open}
+     * @param serviceType a service type that is specified in {@link ImsCallProfile}
+     *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
+     *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
+     * @param callType a call type that is specified in {@link ImsCallProfile}
+     *        {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO}
+     *        {@link ImsCallProfile#CALL_TYPE_VOICE}
+     *        {@link ImsCallProfile#CALL_TYPE_VT}
+     *        {@link ImsCallProfile#CALL_TYPE_VS}
+     * @return true if the specified service id is connected to the IMS network;
+     *        false otherwise
+     * @throws ImsException if calling the IMS service results in an error
+     */
+    public boolean isConnected(int serviceId, int serviceType, int callType)
+            throws ImsException {
+        checkAndThrowExceptionIfServiceUnavailable();
+
+        try {
+            return mImsService.isConnected(serviceId, serviceType, callType);
+        } catch (RemoteException e) {
+            throw new ImsException("isServiceConnected()", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+    }
+
+    /**
+     * Checks if the specified IMS service is opend.
+     *
+     * @param serviceId a service id which is obtained from {@link ImsManager#open}
+     * @return true if the specified service id is opened; false otherwise
+     * @throws ImsException if calling the IMS service results in an error
+     */
+    public boolean isOpened(int serviceId) throws ImsException {
+        checkAndThrowExceptionIfServiceUnavailable();
+
+        try {
+            return mImsService.isOpened(serviceId);
+        } catch (RemoteException e) {
+            throw new ImsException("isOpened()", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+    }
+
+    /**
+     * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
+     *
+     * @param serviceId a service id which is obtained from {@link ImsManager#open}
+     * @param serviceType a service type that is specified in {@link ImsCallProfile}
+     *        {@link ImsCallProfile#SERVICE_TYPE_NONE}
+     *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
+     *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
+     * @param callType a call type that is specified in {@link ImsCallProfile}
+     *        {@link ImsCallProfile#CALL_TYPE_VOICE}
+     *        {@link ImsCallProfile#CALL_TYPE_VT}
+     *        {@link ImsCallProfile#CALL_TYPE_VT_TX}
+     *        {@link ImsCallProfile#CALL_TYPE_VT_RX}
+     *        {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
+     *        {@link ImsCallProfile#CALL_TYPE_VS}
+     *        {@link ImsCallProfile#CALL_TYPE_VS_TX}
+     *        {@link ImsCallProfile#CALL_TYPE_VS_RX}
+     * @return a {@link ImsCallProfile} object
+     * @throws ImsException if calling the IMS service results in an error
+     */
+    public ImsCallProfile createCallProfile(int serviceId,
+            int serviceType, int callType) throws ImsException {
+        checkAndThrowExceptionIfServiceUnavailable();
+
+        try {
+            return mImsService.createCallProfile(serviceId, serviceType, callType);
+        } catch (RemoteException e) {
+            throw new ImsException("createCallProfile()", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+    }
+
+    /**
+     * Creates a {@link ImsCall} to make a call.
+     *
+     * @param serviceId a service id which is obtained from {@link ImsManager#open}
+     * @param profile a call profile to make the call
+     *      (it contains service type, call type, media information, etc.)
+     * @param participants participants to invite the conference call
+     * @param listener listen to the call events from {@link ImsCall}
+     * @return a {@link ImsCall} object
+     * @throws ImsException if calling the IMS service results in an error
+     */
+    public ImsCall makeCall(int serviceId, ImsCallProfile profile, String[] callees,
+            ImsCall.Listener listener) throws ImsException {
+        if (DBG) {
+            log("makeCall :: serviceId=" + serviceId
+                    + ", profile=" + profile + ", callees=" + callees);
+        }
+
+        checkAndThrowExceptionIfServiceUnavailable();
+
+        ImsCall call = new ImsCall(mContext, profile);
+
+        call.setListener(listener);
+        ImsCallSession session = createCallSession(serviceId, profile);
+
+        if ((callees != null) && (callees.length == 1)) {
+            call.start(session, callees[0]);
+        } else {
+            call.start(session, callees);
+        }
+
+        return call;
+    }
+
+    /**
+     * Creates a {@link ImsCall} to take an incoming call.
+     *
+     * @param serviceId a service id which is obtained from {@link ImsManager#open}
+     * @param incomingCallIntent the incoming call broadcast intent
+     * @param listener to listen to the call events from {@link ImsCall}
+     * @return a {@link ImsCall} object
+     * @throws ImsException if calling the IMS service results in an error
+     */
+    public ImsCall takeCall(int serviceId, Intent incomingCallIntent,
+            ImsCall.Listener listener) throws ImsException {
+        if (DBG) {
+            log("takeCall :: serviceId=" + serviceId
+                    + ", incomingCall=" + incomingCallIntent);
+        }
+
+        checkAndThrowExceptionIfServiceUnavailable();
+
+        if (incomingCallIntent == null) {
+            throw new ImsException("Can't retrieve session with null intent",
+                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
+        }
+
+        int incomingServiceId = getServiceId(incomingCallIntent);
+
+        if (serviceId != incomingServiceId) {
+            throw new ImsException("Service id is mismatched in the incoming call intent",
+                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
+        }
+
+        String callId = getCallId(incomingCallIntent);
+
+        if (callId == null) {
+            throw new ImsException("Call ID missing in the incoming call intent",
+                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
+        }
+
+        try {
+            IImsCallSession session = mImsService.getPendingCallSession(serviceId, callId);
+
+            if (session == null) {
+                throw new ImsException("No pending session for the call",
+                        ImsReasonInfo.CODE_LOCAL_NO_PENDING_CALL);
+            }
+
+            ImsCall call = new ImsCall(mContext, session.getCallProfile());
+
+            call.attachSession(new ImsCallSession(session));
+            call.setListener(listener);
+
+            return call;
+        } catch (Throwable t) {
+            throw new ImsException("takeCall()", t, ImsReasonInfo.CODE_UNSPECIFIED);
+        }
+    }
+
+    /**
+     * Gets the call ID from the specified incoming call broadcast intent.
+     *
+     * @param incomingCallIntent the incoming call broadcast intent
+     * @return the call ID or null if the intent does not contain it
+     */
+    private static String getCallId(Intent incomingCallIntent) {
+        if (incomingCallIntent == null) {
+            return null;
+        }
+
+        return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
+    }
+
+    /**
+     * Gets the service type from the specified incoming call broadcast intent.
+     *
+     * @param incomingCallIntent the incoming call broadcast intent
+     * @return the service identifier or -1 if the intent does not contain it
+     */
+    private static int getServiceId(Intent incomingCallIntent) {
+        if (incomingCallIntent == null) {
+            return (-1);
+        }
+
+        return incomingCallIntent.getIntExtra(EXTRA_SERVICE_ID, -1);
+    }
+
+    /**
+     * Binds the IMS service only if the service is not created.
+     */
+    private void checkAndThrowExceptionIfServiceUnavailable()
+            throws ImsException {
+        if (mImsService == null) {
+            createImsService(true);
+
+            if (mImsService == null) {
+                throw new ImsException("Service is unavailable",
+                        ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+            }
+        }
+    }
+
+    /**
+     * Binds the IMS service to make/receive the call.
+     */
+    private void createImsService(boolean checkService) {
+        if (checkService) {
+            IBinder binder = ServiceManager.checkService(IMS_SERVICE);
+
+            if (binder == null) {
+                return;
+            }
+        }
+
+        IBinder b = ServiceManager.getService(IMS_SERVICE);
+
+        if (b != null) {
+            try {
+                b.linkToDeath(mDeathRecipient, 0);
+            } catch (RemoteException e) {
+            }
+        }
+
+        mImsService = IImsService.Stub.asInterface(b);
+    }
+
+    /**
+     * Creates a {@link ImsCallSession} with the specified call profile.
+     * Use other methods, if applicable, instead of interacting with
+     * {@link ImsCallSession} directly.
+     *
+     * @param serviceId a service id which is obtained from {@link ImsManager#open}
+     * @param profile a call profile to make the call
+     */
+    private ImsCallSession createCallSession(int serviceId,
+            ImsCallProfile profile) throws ImsException {
+        try {
+            return new ImsCallSession(mImsService.createCallSession(serviceId, profile, null));
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    private ImsRegistrationListenerProxy createRegistrationListenerProxy(int serviceClass,
+            ImsConnectionStateListener listener) {
+        ImsRegistrationListenerProxy proxy =
+                new ImsRegistrationListenerProxy(serviceClass, listener);
+        return proxy;
+    }
+
+    private void log(String s) {
+        Rlog.d(TAG, s);
+    }
+
+    private void loge(String s) {
+        Rlog.e(TAG, s);
+    }
+
+    private void loge(String s, Throwable t) {
+        Rlog.e(TAG, s, t);
+    }
+
+    /**
+     * Death recipient class for monitoring IMS service.
+     */
+    private class ImsServiceDeathRecipient implements IBinder.DeathRecipient {
+        @Override
+        public void binderDied() {
+            mImsService = null;
+            mUt = null;
+
+            if (mContext != null) {
+                mContext.sendBroadcast(new Intent(ACTION_IMS_SERVICE_DOWN));
+            }
+        }
+    }
+
+    /**
+     * Adapter class for {@link IImsRegistrationListener}.
+     */
+    private class ImsRegistrationListenerProxy extends IImsRegistrationListener.Stub {
+        private int mServiceClass;
+        private ImsConnectionStateListener mListener;
+
+        public ImsRegistrationListenerProxy(int serviceClass,
+                ImsConnectionStateListener listener) {
+            mServiceClass = serviceClass;
+            mListener = listener;
+        }
+
+        public boolean isSameProxy(int serviceClass) {
+            return (mServiceClass == serviceClass);
+        }
+
+        @Override
+        public void registrationConnected() {
+            if (DBG) {
+                log("registrationConnected ::");
+            }
+
+            if (mListener != null) {
+                mListener.onImsConnected();
+            }
+        }
+
+        @Override
+        public void registrationDisconnected() {
+            if (DBG) {
+                log("registrationDisconnected ::");
+            }
+
+            if (mListener != null) {
+                mListener.onImsDisconnected();
+            }
+        }
+
+        @Override
+        public void registrationResumed() {
+            if (DBG) {
+                log("registrationResumed ::");
+            }
+
+            if (mListener != null) {
+                mListener.onImsResumed();
+            }
+        }
+
+        @Override
+        public void registrationSuspended() {
+            if (DBG) {
+                log("registrationSuspended ::");
+            }
+
+            if (mListener != null) {
+                mListener.onImsSuspended();
+            }
+        }
+
+        @Override
+        public void registrationServiceCapabilityChanged(int serviceClass, int event) {
+            log("registrationServiceCapabilityChanged :: serviceClass=" +
+                    serviceClass + ", event=" + event);
+
+            if (mListener != null) {
+                mListener.onImsConnected();
+            }
+        }
+    }
+}
diff --git a/src/java/com/android/ims/ImsReasonInfo.aidl b/src/java/com/android/ims/ImsReasonInfo.aidl
new file mode 100644 (file)
index 0000000..17e6d3a
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+parcelable ImsReasonInfo;
diff --git a/src/java/com/android/ims/ImsReasonInfo.java b/src/java/com/android/ims/ImsReasonInfo.java
new file mode 100644 (file)
index 0000000..99faba6
--- /dev/null
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class enables an application to get details on why a method call failed.
+ *
+ * @hide
+ */
+public class ImsReasonInfo implements Parcelable {
+
+    /**
+     * Reason types, defines the error category.
+     *    UNSPECIFIED - unknown error reason
+     *    LOCAL - indicates the local/device error reason
+     *    LOCAL_TIMEOUT - indicates the local error reason when a specific timer is expired
+     *    STATUSCODE - indicates the interworking error reason by SIP status code received
+     *        from the network
+     *    MEDIA - indicates the media error reason (local resource, SDP parameter, etc.)
+     *    USER - indicates the error reason by the local or remote user
+     *    UT - indicates the error reason for the supplementary service configuration
+     */
+    public static final int TYPE_UNSPECIFIED = 0;
+    public static final int TYPE_LOCAL = 1;
+    public static final int TYPE_TIMEOUT = 2;
+    public static final int TYPE_STATUSCODE = 3;
+    public static final int TYPE_MEDIA = 4;
+    public static final int TYPE_USER = 5;
+    public static final int TYPE_UT = 8;
+
+    /**
+     * Specific code of each types
+     */
+    public static final int CODE_UNSPECIFIED = 0;
+
+    /**
+     * LOCAL
+     */
+    // IMS -> Telephony
+    // The passed argument is an invalid
+    public static final int CODE_LOCAL_ILLEGAL_ARGUMENT = 101;
+    // The operation is invoked in invalid call state
+    public static final int CODE_LOCAL_ILLEGAL_STATE = 102;
+    // IMS service internal error
+    public static final int CODE_LOCAL_INTERNAL_ERROR = 103;
+    // IMS service goes down (service connection is lost)
+    public static final int CODE_LOCAL_IMS_SERVICE_DOWN = 106;
+    // No pending incoming call exists
+    public static final int CODE_LOCAL_NO_PENDING_CALL = 107;
+
+    // IMS -> Telephony
+    // Service unavailable; by power off
+    public static final int CODE_LOCAL_POWER_OFF = 111;
+    // Service unavailable; by low battery
+    public static final int CODE_LOCAL_LOW_BATTERY = 112;
+    // Service unavailable; by out of service (data service state)
+    public static final int CODE_LOCAL_NETWORK_NO_SERVICE = 121;
+    // Service unavailable; by no LTE coverage
+    // (VoLTE is not supported even though IMS is registered)
+    public static final int CODE_LOCAL_NETWORK_NO_LTE_COVERAGE = 122;
+    // Service unavailable; by located in roaming area
+    public static final int CODE_LOCAL_NETWORK_ROAMING = 123;
+    // Service unavailable; by IP changed
+    public static final int CODE_LOCAL_NETWORK_IP_CHANGED = 124;
+    // Service unavailable; other
+    public static final int CODE_LOCAL_SERVICE_UNAVAILABLE = 131;
+    // Service unavailable; IMS connection is lost (IMS is not registered)
+    public static final int CODE_LOCAL_NOT_REGISTERED = 132;
+
+    // IMS <-> Telephony
+    // Max call exceeded
+    public static final int CODE_LOCAL_CALL_EXCEEDED = 141;
+    // IMS <- Telephony
+    // Call busy
+    public static final int CODE_LOCAL_CALL_BUSY = 142;
+    // Call decline
+    public static final int CODE_LOCAL_CALL_DECLINE = 143;
+    // IMS -> Telephony
+    // SRVCC is in progress
+    public static final int CODE_LOCAL_CALL_VCC_ON_PROGRESSING = 144;
+    // Resource reservation is failed (QoS precondition)
+    public static final int CODE_LOCAL_CALL_RESOURCE_RESERVATION_FAILED = 145;
+    // Retry CS call; VoLTE service can't be provided by the network or remote end
+    // Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set
+    public static final int CODE_LOCAL_CALL_CS_RETRY_REQUIRED = 146;
+    // Retry VoLTE call; VoLTE service can't be provided by the network temporarily
+    public static final int CODE_LOCAL_CALL_VOLTE_RETRY_REQUIRED = 147;
+    // IMS call is already terminated (in TERMINATED state)
+    public static final int CODE_LOCAL_CALL_TERMINATED = 148;
+
+    /**
+     * TIMEOUT (IMS -> Telephony)
+     */
+    // 1xx waiting timer is expired after sending INVITE request (MO only)
+    public static final int CODE_TIMEOUT_1XX_WAITING = 201;
+    // User no answer during call setup operation (MO/MT)
+    // MO : 200 OK to INVITE request is not received,
+    // MT : No action from user after alerting the call
+    public static final int CODE_TIMEOUT_NO_ANSWER = 202;
+    // User no answer during call update operation (MO/MT)
+    // MO : 200 OK to re-INVITE request is not received,
+    // MT : No action from user after alerting the call
+    public static final int CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE = 203;
+
+    /**
+     * STATUSCODE (SIP response code) (IMS -> Telephony)
+     */
+    // 3xx responses
+    // SIP request is redirected
+    public static final int CODE_SIP_REDIRECTED = 321;
+    // 4xx responses
+    // 400 : Bad Request
+    public static final int CODE_SIP_BAD_REQUEST = 331;
+    // 403 : Forbidden
+    public static final int CODE_SIP_FORBIDDEN = 332;
+    // 404 : Not Found
+    public static final int CODE_SIP_NOT_FOUND = 333;
+    // 415 : Unsupported Media Type
+    // 416 : Unsupported URI Scheme
+    // 420 : Bad Extension
+    public static final int CODE_SIP_NOT_SUPPORTED = 334;
+    // 408 : Request Timeout
+    public static final int CODE_SIP_REQUEST_TIMEOUT = 335;
+    // 480 : Temporarily Unavailable
+    public static final int CODE_SIP_TEMPRARILY_UNAVAILABLE = 336;
+    // 484 : Address Incomplete
+    public static final int CODE_SIP_BAD_ADDRESS = 337;
+    // 486 : Busy Here
+    // 600 : Busy Everywhere
+    public static final int CODE_SIP_BUSY = 338;
+    // 487 : Request Terminated
+    public static final int CODE_SIP_REQUEST_CANCELLED = 339;
+    // 406 : Not Acceptable
+    // 488 : Not Acceptable Here
+    // 606 : Not Acceptable
+    public static final int CODE_SIP_NOT_ACCEPTABLE = 340;
+    // 410 : Gone
+    // 604 : Does Not Exist Anywhere
+    public static final int CODE_SIP_NOT_REACHABLE = 341;
+    // Others
+    public static final int CODE_SIP_CLIENT_ERROR = 342;
+    // 5xx responses
+    // 501 : Server Internal Error
+    public static final int CODE_SIP_SERVER_INTERNAL_ERROR = 351;
+    // 503 : Service Unavailable
+    public static final int CODE_SIP_SERVICE_UNAVAILABLE = 352;
+    // 504 : Server Time-out
+    public static final int CODE_SIP_SERVER_TIMEOUT = 353;
+    // Others
+    public static final int CODE_SIP_SERVER_ERROR = 354;
+    // 6xx responses
+    // 603 : Decline
+    public static final int CODE_SIP_USER_REJECTED = 361;
+    // Others
+    public static final int CODE_SIP_GLOBAL_ERROR = 362;
+
+    /**
+     * MEDIA (IMS -> Telephony)
+     */
+    // Media resource initialization failed
+    public static final int CODE_MEDIA_INIT_FAILED = 401;
+    // RTP timeout (no audio / video traffic in the session)
+    public static final int CODE_MEDIA_NO_DATA = 402;
+    // Media is not supported; so dropped the call
+    public static final int CODE_MEDIA_NOT_ACCEPTABLE = 403;
+    // Unknown media related errors
+    public static final int CODE_MEDIA_UNSPECIFIED = 404;
+
+    /**
+     * USER
+     */
+    // Telephony -> IMS
+    // User triggers the call end
+    public static final int CODE_USER_TERMINATED = 501;
+    // No action while an incoming call is ringing
+    public static final int CODE_USER_NOANSWER = 502;
+    // User ignores an incoming call
+    public static final int CODE_USER_IGNORE = 503;
+    // User declines an incoming call
+    public static final int CODE_USER_DECLINE = 504;
+    // IMS -> Telephony
+    // The call is terminated by the network or remote user
+    public static final int CODE_USER_TERMINATED_BY_REMOTE = 510;
+
+    /**
+     * Extra codes for the specific code value
+     * This value can be referred when the code is CODE_LOCAL_CALL_CS_RETRY_REQUIRED.
+     */
+    // Try to connect CS call; normal
+    public static final int EXTRA_CODE_CALL_RETRY_NORMAL = 1;
+    // Try to connect CS call without the notification to user
+    public static final int EXTRA_CODE_CALL_RETRY_SILENT_REDIAL = 2;
+    // Try to connect CS call by the settings of the menu
+    public static final int EXTRA_CODE_CALL_RETRY_BY_SETTINGS = 3;
+
+    /**
+     * UT
+     */
+    public static final int CODE_UT_NOT_SUPPORTED = 801;
+    public static final int CODE_UT_SERVICE_UNAVAILABLE = 802;
+    public static final int CODE_UT_OPERATION_NOT_ALLOWED = 803;
+    public static final int CODE_UT_CB_PASSWORD_MISMATCH = 821;
+
+
+
+    // For reason type
+    public int mReasonType;
+    // For main reason code
+    public int mCode;
+    // For the extra code value; it depends on the code value.
+    public int mExtraCode;
+    // For the additional message of the reason info.
+    public String mExtraMessage;
+
+    public ImsReasonInfo() {
+        mReasonType = TYPE_UNSPECIFIED;
+        mCode = CODE_UNSPECIFIED;
+        mExtraCode = CODE_UNSPECIFIED;
+        mExtraMessage = null;
+    }
+
+    public ImsReasonInfo(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public ImsReasonInfo(int code, int extraCode) {
+        mReasonType = (int) (code / 100);
+        mCode = code;
+        mExtraCode = extraCode;
+        mExtraMessage = null;
+    }
+
+    public ImsReasonInfo(int code, int extraCode, String extraMessage) {
+        mReasonType = (int) (code / 100);
+        mCode = code;
+        mExtraCode = extraCode;
+        mExtraMessage = extraMessage;
+    }
+
+    /**
+     *
+     */
+    public int getCode() {
+        return mCode;
+    }
+
+    /**
+     *
+     */
+    public int getExtraCode() {
+        return mExtraCode;
+    }
+
+    /**
+     *
+     */
+    public String getExtraMessage() {
+        return mExtraMessage;
+    }
+
+    /**
+     *
+     */
+    public int getReasonType() {
+        return mReasonType;
+    }
+
+    /**
+     * Returns the string format of {@link ImsReasonInfo}
+     *
+     * @return the string format of {@link ImsReasonInfo}
+     */
+    public String toString() {
+        return "ImsReasonInfo :: {" + mReasonType + ", "
+                + mCode + ", " + mExtraCode + ", " + mExtraMessage + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mReasonType);
+        out.writeInt(mCode);
+        out.writeInt(mExtraCode);
+        out.writeString(mExtraMessage);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mReasonType = in.readInt();
+        mCode = in.readInt();
+        mExtraCode = in.readInt();
+        mExtraMessage = in.readString();
+    }
+
+    public static final Creator<ImsReasonInfo> CREATOR = new Creator<ImsReasonInfo>() {
+        @Override
+        public ImsReasonInfo createFromParcel(Parcel in) {
+            return new ImsReasonInfo(in);
+        }
+
+        @Override
+        public ImsReasonInfo[] newArray(int size) {
+            return new ImsReasonInfo[size];
+        }
+    };
+}
diff --git a/src/java/com/android/ims/ImsServiceClass.java b/src/java/com/android/ims/ImsServiceClass.java
new file mode 100644 (file)
index 0000000..a4e43bc
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+/**
+ * This class defines an identifier for each IMS service category.
+ *
+ * @hide
+ */
+public class ImsServiceClass {
+    /**
+     * Service classes
+     *    It defines the service classes for IMS-based services.
+     */
+
+    /**
+     * MMTEL
+     *  supports the IMS multimedia telephony communication service
+     *  defined in 3GPP & GSMA IR.92, IR.94.
+     */
+    public static final int MMTEL = 1;
+
+    /**
+     * RCS
+     *    supports the RCS service defined in GSMA RCS.
+     */
+    /** public static final int RCS = 2; */
+}
diff --git a/src/java/com/android/ims/ImsSsInfo.aidl b/src/java/com/android/ims/ImsSsInfo.aidl
new file mode 100644 (file)
index 0000000..0ac598b
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+parcelable ImsSsInfo;
diff --git a/src/java/com/android/ims/ImsSsInfo.java b/src/java/com/android/ims/ImsSsInfo.java
new file mode 100644 (file)
index 0000000..dbde1c6
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Provides the result to the update operation for the supplementary service configuration.
+ *
+ * @hide
+ */
+public class ImsSsInfo implements Parcelable {
+    /**
+     * For the status of service registration or activation/deactivation.
+     */
+    public static final int NOT_REGISTERED = (-1);
+    public static final int DISABLED = 0;
+    public static final int ENABLED = 1;
+
+    // 0: disabled, 1: enabled
+    public int mStatus;
+
+    public ImsSsInfo() {
+    }
+
+    public ImsSsInfo(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mStatus);
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + ", Status: " + ((mStatus == 0) ? "disabled" : "enabled");
+    }
+
+    private void readFromParcel(Parcel in) {
+        mStatus = in.readInt();
+    }
+
+    public static final Creator<ImsSsInfo> CREATOR =
+            new Creator<ImsSsInfo>() {
+        @Override
+        public ImsSsInfo createFromParcel(Parcel in) {
+            return new ImsSsInfo(in);
+        }
+
+        @Override
+        public ImsSsInfo[] newArray(int size) {
+            return new ImsSsInfo[size];
+        }
+    };
+}
diff --git a/src/java/com/android/ims/ImsStreamMediaProfile.aidl b/src/java/com/android/ims/ImsStreamMediaProfile.aidl
new file mode 100644 (file)
index 0000000..d648a35
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+parcelable ImsStreamMediaProfile;
diff --git a/src/java/com/android/ims/ImsStreamMediaProfile.java b/src/java/com/android/ims/ImsStreamMediaProfile.java
new file mode 100644 (file)
index 0000000..003499c
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parcelable object to handle IMS stream media profile.
+ * It provides the media direction, quality of audio and/or video.
+ *
+ * @hide
+ */
+public class ImsStreamMediaProfile implements Parcelable {
+    private static final String TAG = "ImsStreamMediaProfile";
+
+    /**
+     * Media directions
+     */
+    public static final int DIRECTION_INVALID = (-1);
+    public static final int DIRECTION_INACTIVE = 0;
+    public static final int DIRECTION_RECEIVE = 1;
+    public static final int DIRECTION_SEND = 2;
+    public static final int DIRECTION_SEND_RECEIVE = 3;
+
+    /**
+     * Audio information
+     */
+    public static final int AUDIO_QUALITY_NONE = 0;
+    public static final int AUDIO_QUALITY_AMR = (1 << 0);
+    public static final int AUDIO_QUALITY_AMR_WB = (1 << 1);
+
+    /**
+     * Video information
+     */
+    public static final int VIDEO_QUALITY_NONE = 0;
+    public static final int VIDEO_QUALITY_QCIF = (1 << 0);
+    public static final int VIDEO_QUALITY_QVGA_LANDSCAPE = (1 << 1);
+    public static final int VIDEO_QUALITY_QVGA_PORTRAIT = (1 << 2);
+    public static final int VIDEO_QUALITY_VGA_LANDSCAPE = (1 << 3);
+    public static final int VIDEO_QUALITY_VGA_PORTRAIT = (1 << 4);
+
+    // Audio related information
+    public int mAudioQuality;
+    public int mAudioDirection;
+    // Video related information
+    public int mVideoQuality;
+    public int mVideoDirection;
+
+
+
+    public ImsStreamMediaProfile(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public ImsStreamMediaProfile() {
+        mAudioQuality = AUDIO_QUALITY_AMR_WB;
+        mAudioDirection = DIRECTION_SEND_RECEIVE;
+        mVideoQuality = VIDEO_QUALITY_NONE;
+        mVideoDirection = DIRECTION_INVALID;
+    }
+
+    public ImsStreamMediaProfile(int audioQuality, int audioDirection,
+            int videoQuality, int videoDirection) {
+        mAudioQuality = audioQuality;
+        mAudioDirection = audioDirection;
+        mVideoQuality = videoQuality;
+        mVideoDirection = videoDirection;
+    }
+
+    public void copyFrom(ImsStreamMediaProfile profile) {
+        mAudioQuality = profile.mAudioQuality;
+        mAudioDirection = profile.mAudioDirection;
+        mVideoQuality = profile.mVideoQuality;
+        mVideoDirection = profile.mVideoDirection;
+    }
+
+    @Override
+    public String toString() {
+        return "{ audioQuality=" + mAudioQuality +
+                ", audioDirection=" + mAudioDirection +
+                ", videoQuality=" + mVideoQuality +
+                ", videoDirection=" + mVideoDirection + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mAudioQuality);
+        out.writeInt(mAudioDirection);
+        out.writeInt(mVideoQuality);
+        out.writeInt(mVideoDirection);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mAudioQuality = in.readInt();
+        mAudioDirection = in.readInt();
+        mVideoQuality = in.readInt();
+        mVideoDirection = in.readInt();
+    }
+
+    public static final Creator<ImsStreamMediaProfile> CREATOR =
+            new Creator<ImsStreamMediaProfile>() {
+        @Override
+        public ImsStreamMediaProfile createFromParcel(Parcel in) {
+            return new ImsStreamMediaProfile(in);
+        }
+
+        @Override
+        public ImsStreamMediaProfile[] newArray(int size) {
+            return new ImsStreamMediaProfile[size];
+        }
+    };
+}
diff --git a/src/java/com/android/ims/ImsUt.java b/src/java/com/android/ims/ImsUt.java
new file mode 100644 (file)
index 0000000..c2f77d5
--- /dev/null
@@ -0,0 +1,433 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telephony.Rlog;
+
+import com.android.ims.internal.IImsUt;
+import com.android.ims.internal.IImsUtListener;
+
+/**
+ * Provides APIs for the supplementary service settings using IMS (Ut interface).
+ * It is created from 3GPP TS 24.623 (XCAP(XML Configuration Access Protocol)
+ * over the Ut interface for manipulating supplementary services).
+ *
+ * @hide
+ */
+public class ImsUt implements ImsUtInterface {
+    /**
+     * Key string for an additional supplementary service configurations.
+     */
+    /**
+     * Actions : string format of ImsUtInterface#ACTION_xxx
+     *      "0" (deactivation), "1" (activation), "2" (not_used),
+     *      "3" (registration), "4" (erasure), "5" (Interrogation)
+     */
+    public static final String KEY_ACTION = "action";
+    /**
+     * Categories :
+     *      "OIP", "OIR", "TIP", "TIR", "CDIV", "CB", "CW", "CONF",
+     *      "ACR", "MCID", "ECT", "CCBS", "AOC", "MWI", "FA", "CAT"
+     *
+     * Detailed parameter name will be determined according to the properties
+     * of the supplementary service configuration.
+     */
+    public static final String KEY_CATEGORY = "category";
+    public static final String CATEGORY_OIP = "OIP";
+    public static final String CATEGORY_OIR = "OIR";
+    public static final String CATEGORY_TIP = "TIP";
+    public static final String CATEGORY_TIR = "TIR";
+    public static final String CATEGORY_CDIV = "CDIV";
+    public static final String CATEGORY_CB = "CB";
+    public static final String CATEGORY_CW = "CW";
+    public static final String CATEGORY_CONF = "CONF";
+
+    private static final String TAG = "ImsUt";
+    private static final boolean DBG = true;
+
+    // For synchronization of private variables
+    private Object mLockObj = new Object();
+    private final IImsUt miUt;
+    private HashMap<Integer, Message> mPendingCmds =
+            new HashMap<Integer, Message>();
+
+    public ImsUt(IImsUt iUt) {
+        miUt = iUt;
+
+        if (miUt != null) {
+            try {
+                miUt.setListener(new IImsUtListenerProxy());
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public void close() {
+        synchronized(mLockObj) {
+            if (miUt != null) {
+                try {
+                    miUt.close();
+                } catch (RemoteException e) {
+                }
+            }
+
+            if (!mPendingCmds.isEmpty()) {
+                Map.Entry<Integer, Message>[] entries =
+                    mPendingCmds.entrySet().toArray(new Map.Entry[mPendingCmds.size()]);
+
+                for (Map.Entry<Integer, Message> entry : entries) {
+                    sendFailureReport(entry.getValue(),
+                            ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE);
+                }
+
+                mPendingCmds.clear();
+            }
+        }
+    }
+
+    /**
+     * Operations for the supplementary service configuration
+     */
+
+    /**
+     * Retrieves the configuration of the call barring.
+     *
+     * @param cbType type of call barring to be queried; ImsUtInterface#CB_XXX
+     * @param result message to pass the result of this operation
+     *      The return value of ((AsyncResult)result.obj) is an array of {@link ImsSsInfo}.
+     */
+    @Override
+    public void queryCallBarring(int cbType, Message result) {
+        if (DBG) {
+            log("queryCallBarring :: Ut=" + miUt + ", cbType=" + cbType);
+        }
+
+        synchronized(mLockObj) {
+            try {
+                int id = miUt.queryCallBarring(cbType);
+
+                if (id < 0) {
+                    id *= (-1);
+                    sendFailureReport(result, id);
+                    return;
+                }
+
+                mPendingCmds.put(Integer.valueOf(id), result);
+            } catch (RemoteException e) {
+                sendFailureReport(result, ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE);
+            }
+        }
+    }
+
+    /**
+     * Retrieves the configuration of the call forward.
+     * The return value of ((AsyncResult)result.obj) is an array of {@link ImsCallForwardInfo}.
+     */
+    @Override
+    public void queryCallForward(int condition, String number, Message result) {
+        if (DBG) {
+            log("queryCallForward :: Ut=" + miUt + ", condition=" + condition
+                    + ", number=" + number);
+        }
+
+        synchronized(mLockObj) {
+            try {
+                int id = miUt.queryCallForward(condition, number);
+
+                if (id < 0) {
+                    id *= (-1);
+                    sendFailureReport(result, id);
+                    return;
+                }
+
+                mPendingCmds.put(Integer.valueOf(id), result);
+            } catch (RemoteException e) {
+                sendFailureReport(result, ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE);
+            }
+        }
+    }
+
+    /**
+     * Retrieves the configuration of the call waiting.
+     * The return value of ((AsyncResult)result.obj) is an array of {@link ImsSsInfo}.
+     */
+    @Override
+    public void queryCallWaiting(Message result) {
+        if (DBG) {
+            log("queryCallWaiting :: Ut=" + miUt);
+        }
+
+        synchronized(mLockObj) {
+            try {
+                int id = miUt.queryCallWaiting();
+
+                if (id < 0) {
+                    id *= (-1);
+                    sendFailureReport(result, id);
+                    return;
+                }
+
+                mPendingCmds.put(Integer.valueOf(id), result);
+            } catch (RemoteException e) {
+                sendFailureReport(result, ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE);
+            }
+        }
+    }
+
+    /**
+     * Modifies the configuration of the call barring.
+     */
+    @Override
+    public void updateCallBarring(int cbType, boolean enable, Message result) {
+        if (DBG) {
+            log("updateCallBarring :: Ut=" + miUt + ", cbType=" + cbType
+                    + ", enable=" + enable);
+        }
+
+        synchronized(mLockObj) {
+            try {
+                int id = miUt.updateCallBarring(cbType, enable);
+
+                if (id < 0) {
+                    id *= (-1);
+                    sendFailureReport(result, id);
+                    return;
+                }
+
+                mPendingCmds.put(Integer.valueOf(id), result);
+            } catch (RemoteException e) {
+                sendFailureReport(result, ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE);
+            }
+        }
+    }
+
+    /**
+     * Modifies the configuration of the call forward.
+     */
+    @Override
+    public void updateCallForward(int action, int condition, String number,
+            int timeSeconds, Message result) {
+        if (DBG) {
+            log("updateCallForward :: Ut=" + miUt + ", action=" + action
+                    + ", condition=" + condition + ", number=" + number
+                    + ", timeSeconds=" + timeSeconds);
+        }
+
+        synchronized(mLockObj) {
+            try {
+                int id = miUt.updateCallForward(action, condition, number, timeSeconds);
+
+                if (id < 0) {
+                    id *= (-1);
+                    sendFailureReport(result, id);
+                    return;
+                }
+
+                mPendingCmds.put(Integer.valueOf(id), result);
+            } catch (RemoteException e) {
+                sendFailureReport(result, ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE);
+            }
+        }
+    }
+
+    /**
+     * Modifies the configuration of the call waiting.
+     */
+    @Override
+    public void updateCallWaiting(boolean enable, Message result) {
+        if (DBG) {
+            log("updateCallWaiting :: Ut=" + miUt + ", enable=" + enable);
+        }
+
+        synchronized(mLockObj) {
+            try {
+                int id = miUt.updateCallWaiting(enable);
+
+                if (id < 0) {
+                    id *= (-1);
+                    sendFailureReport(result, id);
+                    return;
+                }
+
+                mPendingCmds.put(Integer.valueOf(id), result);
+            } catch (RemoteException e) {
+                sendFailureReport(result, ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE);
+            }
+        }
+    }
+
+    public void transact(Bundle ssInfo, Message result) {
+        if (DBG) {
+            log("transact :: Ut=" + miUt + ", ssInfo=" + ssInfo);
+        }
+
+        synchronized(mLockObj) {
+            try {
+                int id = miUt.transact(ssInfo);
+
+                if (id < 0) {
+                    id *= (-1);
+                    sendFailureReport(result, id);
+                    return;
+                }
+
+                mPendingCmds.put(Integer.valueOf(id), result);
+            } catch (RemoteException e) {
+                sendFailureReport(result, ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE);
+            }
+        }
+    }
+
+    private void sendFailureReport(Message result, int errorCode) {
+        if (result == null) {
+            return;
+        }
+
+        AsyncResult.forMessage(result, null, new ImsException("Ut Exception", errorCode));
+        result.sendToTarget();
+    }
+
+    private void sendSuccessReport(Message result) {
+        if (result == null) {
+            return;
+        }
+
+        AsyncResult.forMessage(result, null, null);
+        result.sendToTarget();
+    }
+
+    private void sendSuccessReport(Message result, Object ssInfo) {
+        if (result == null) {
+            return;
+        }
+
+        AsyncResult.forMessage(result, ssInfo, null);
+        result.sendToTarget();
+    }
+
+    private void log(String s) {
+        Rlog.d(TAG, s);
+    }
+
+    private void loge(String s) {
+        Rlog.e(TAG, s);
+    }
+
+    private void loge(String s, Throwable t) {
+        Rlog.e(TAG, s, t);
+    }
+
+    /**
+     * A listener type for the result of the supplementary service configuration.
+     */
+    private class IImsUtListenerProxy extends IImsUtListener.Stub {
+        /**
+         * Notifies the result of the supplementary service configuration udpate.
+         */
+        @Override
+        public void utConfigurationUpdated(IImsUt ut, int id) {
+            Integer key = Integer.valueOf(id);
+
+            synchronized(mLockObj) {
+                sendSuccessReport(mPendingCmds.get(key));
+                mPendingCmds.remove(key);
+            }
+        }
+
+        @Override
+        public void utConfigurationUpdateFailed(IImsUt ut, int id, int errorCode) {
+            Integer key = Integer.valueOf(id);
+
+            synchronized(mLockObj) {
+                sendFailureReport(mPendingCmds.get(key), errorCode);
+                mPendingCmds.remove(key);
+            }
+        }
+
+        /**
+         * Notifies the result of the supplementary service configuration query.
+         */
+        @Override
+        public void utConfigurationQueried(IImsUt ut, int id, Bundle ssInfo) {
+            Integer key = Integer.valueOf(id);
+
+            synchronized(mLockObj) {
+                sendSuccessReport(mPendingCmds.get(key), ssInfo);
+                mPendingCmds.remove(key);
+            }
+        }
+
+        @Override
+        public void utConfigurationQueryFailed(IImsUt ut, int id, int errorCode) {
+            Integer key = Integer.valueOf(id);
+
+            synchronized(mLockObj) {
+                sendFailureReport(mPendingCmds.get(key), errorCode);
+                mPendingCmds.remove(key);
+            }
+        }
+
+        /**
+         * Notifies the status of the call barring supplementary service.
+         */
+        @Override
+        public void utConfigurationCallBarringQueried(IImsUt ut,
+                int id, ImsSsInfo[] cbInfo) {
+            Integer key = Integer.valueOf(id);
+
+            synchronized(mLockObj) {
+                sendSuccessReport(mPendingCmds.get(key), cbInfo);
+                mPendingCmds.remove(key);
+            }
+        }
+
+        /**
+         * Notifies the status of the call forwarding supplementary service.
+         */
+        @Override
+        public void utConfigurationCallForwardQueried(IImsUt ut,
+                int id, ImsCallForwardInfo[] cfInfo) {
+            Integer key = Integer.valueOf(id);
+
+            synchronized(mLockObj) {
+                sendSuccessReport(mPendingCmds.get(key), cfInfo);
+                mPendingCmds.remove(key);
+            }
+        }
+
+        /**
+         * Notifies the status of the call waiting supplementary service.
+         */
+        @Override
+        public void utConfigurationCallWaitingQueried(IImsUt ut,
+                int id, ImsSsInfo[] cwInfo) {
+            Integer key = Integer.valueOf(id);
+
+            synchronized(mLockObj) {
+                sendSuccessReport(mPendingCmds.get(key), cwInfo);
+                mPendingCmds.remove(key);
+            }
+        }
+    }
+}
diff --git a/src/java/com/android/ims/ImsUtInterface.java b/src/java/com/android/ims/ImsUtInterface.java
new file mode 100644 (file)
index 0000000..d7695a0
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+import android.os.Message;
+
+/**
+ * Provides APIs for the supplementary service settings using IMS (Ut interface).
+ * It is created from 3GPP TS 24.623 (XCAP(XML Configuration Access Protocol)
+ * over the Ut interface for manipulating supplementary services).
+ *
+ * @hide
+ */
+public interface ImsUtInterface {
+    /**
+     * Actions
+     * @hide
+     */
+    public static final int ACTION_DEACTIVATION = 0;
+    public static final int ACTION_ACTIVATION = 1;
+    public static final int ACTION_REGISTRATION = 3;
+    public static final int ACTION_ERASURE = 4;
+    public static final int ACTION_INTERROGATION = 5;
+
+    /**
+     * OIR (Originating Identification Restriction, 3GPP TS 24.607)
+     * OIP (Originating Identification Presentation, 3GPP TS 24.607)
+     * TIR (Terminating Identification Restriction, 3GPP TS 24.608)
+     * TIP (Terminating Identification Presentation, 3GPP TS 24.608)
+     */
+    public static final int OIR_DEFAULT = 0;    // "user subscription default value"
+    public static final int OIR_PRESENTATION_RESTRICTED = 1;
+    public static final int OIR_PRESENTATION_NOT_RESTRICTED = 2;
+
+    /**
+     * CW (Communication Waiting, 3GPP TS 24.615)
+     */
+
+    /**
+     * CDIV (Communication Diversion, 3GPP TS 24.604)
+     *     actions: target, no reply timer
+     */
+    public static final int CDIV_CF_UNCONDITIONAL = 0;
+    public static final int CDIV_CF_BUSY = 1;
+    public static final int CDIV_CF_NO_REPLY = 2;
+    public static final int CDIV_CF_NOT_REACHABLE = 3;
+    // For CS service code: 002
+    public static final int CDIV_CF_ALL = 4;
+    // For CS service code: 004
+    public static final int CDIV_CF_ALL_CONDITIONAL = 5;
+    // It's only supported in the IMS service (CS does not define it).
+    // IR.92 recommends that an UE activates both the CFNRc and the CFNL
+    // (CDIV using condition not-registered) to the same target.
+    public static final int CDIV_CF_NOT_LOGGED_IN = 6;
+
+    /**
+     * CB (Communication Barring, 3GPP TS 24.611)
+     */
+    // Barring of All Incoming Calls
+    public static final int CB_BAIC = 1;
+    // Barring of All Outgoing Calls
+    public static final int CB_BAOC = 2;
+    // Barring of Outgoing International Calls
+    public static final int CB_BOIC = 3;
+    // Barring of Outgoing International Calls - excluding Home Country
+    public static final int CB_BOIC_EXHC = 4;
+    // Barring of Incoming Calls - when roaming
+    public static final int CB_BIC_WR = 5;
+    // Barring of Anonymous Communication Rejection (ACR) - a particular case of ICB service
+    public static final int CB_BIC_ACR = 6;
+
+
+    /**
+     * Invalid result value.
+     */
+    public static final int INVALID = (-1);
+
+
+
+    /**
+     * Operations for the supplementary service configuration
+     */
+
+    /**
+     * Retrieves the configuration of the call barring.
+     * The return value of ((AsyncResult)result.obj) is an array of {@link ImsSsInfo}.
+     */
+    public void queryCallBarring(int cbType, Message result);
+
+    /**
+     * Retrieves the configuration of the call forward.
+     * The return value of ((AsyncResult)result.obj) is an array of {@link ImsCallForwardInfo}.
+     */
+    public void queryCallForward(int condition, String number, Message result);
+
+    /**
+     * Retrieves the configuration of the call waiting.
+     * The return value of ((AsyncResult)result.obj) is an array of {@link ImsSsInfo}.
+     */
+    public void queryCallWaiting(Message result);
+
+    /**
+     * Modifies the configuration of the call barring.
+     */
+    public void updateCallBarring(int cbType, boolean enable, Message result);
+
+    /**
+     * Modifies the configuration of the call forward.
+     */
+    public void updateCallForward(int action, int condition, String number,
+            int timeSeconds, Message result);
+
+    /**
+     * Modifies the configuration of the call waiting.
+     */
+    public void updateCallWaiting(boolean enable, Message result);
+}
diff --git a/src/java/com/android/ims/internal/CallGroup.java b/src/java/com/android/ims/internal/CallGroup.java
new file mode 100644 (file)
index 0000000..4cd15b5
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+import java.util.ArrayList;
+
+/**
+ * Wrapper class which has an ICallGroup interface.
+ *
+ * @hide
+ */
+public class CallGroup {
+    private final ICallGroup mCallGroup;
+
+    public CallGroup(ICallGroup callGroup) {
+        mCallGroup = callGroup;
+    }
+
+    public ICall getNeutralReferrer() {
+        if (mCallGroup == null) {
+            return null;
+        }
+
+        return mCallGroup.getNeutralReferrer();
+    }
+
+    public ICall getOwner() {
+        if (mCallGroup == null) {
+            return null;
+        }
+
+        return mCallGroup.getOwner();
+    }
+
+    public ICall getReferrer(String name) {
+        if (mCallGroup == null) {
+            return null;
+        }
+
+        return mCallGroup.getReferrer(name);
+    }
+
+    public ArrayList<ICall> getReferrers() {
+        if (mCallGroup == null) {
+            return null;
+        }
+
+        return mCallGroup.getReferrers();
+    }
+
+    public boolean hasReferrer() {
+        if (mCallGroup == null) {
+            return false;
+        }
+
+        return mCallGroup.hasReferrer();
+    }
+
+    public boolean isOwner(ICall call) {
+        if (mCallGroup == null) {
+            return false;
+        }
+
+        return mCallGroup.isOwner(call);
+    }
+
+    public boolean isReferrer(ICall call) {
+        if (mCallGroup == null) {
+            return false;
+        }
+
+        return mCallGroup.isReferrer(call);
+    }
+
+    public void addReferrer(ICall call) {
+        if (mCallGroup == null) {
+            return;
+        }
+
+        mCallGroup.addReferrer(call);
+    }
+
+    public void removeReferrer(ICall call) {
+        if (mCallGroup == null) {
+            return;
+        }
+
+        mCallGroup.removeReferrer(call);
+    }
+
+    public void setNeutralReferrer(ICall call) {
+        if (mCallGroup == null) {
+            return;
+        }
+
+        mCallGroup.setNeutralReferrer(call);
+    }
+
+    public void setOwner(ICall call) {
+        if (mCallGroup == null) {
+            return;
+        }
+
+        mCallGroup.setOwner(call);
+    }
+}
diff --git a/src/java/com/android/ims/internal/CallGroupManager.java b/src/java/com/android/ims/internal/CallGroupManager.java
new file mode 100644 (file)
index 0000000..36391de
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+import java.util.ArrayList;
+
+/**
+ * Manages CallGroup objects.
+ *
+ * @hide
+ */
+public class CallGroupManager {
+    private static CallGroupManager mCallGroupManager = new CallGroupManager();
+    private Object mLockObj = new Object();
+    private ArrayList<CallGroup> mCallGroups = new ArrayList<CallGroup>();
+
+    private CallGroupManager() {
+    }
+
+    public static CallGroupManager getInstance() {
+        return mCallGroupManager;
+    }
+
+    public CallGroup createCallGroup(ICallGroup callGroup) {
+        CallGroup cg = new CallGroup(callGroup);
+
+        synchronized(mLockObj) {
+            mCallGroups.add(cg);
+        }
+
+        return cg;
+    }
+
+    public void destroyCallGroup(CallGroup cg) {
+        if (cg == null) {
+            return;
+        }
+
+        synchronized(mLockObj) {
+            mCallGroups.remove(cg);
+        }
+    }
+
+    public CallGroup getCallGroupAsOwner(ICall call) {
+        synchronized(mLockObj) {
+            for (CallGroup cg : mCallGroups) {
+                if (cg.isOwner(call)) {
+                    return cg;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public CallGroup getCallGroupAsReferrer(ICall call) {
+        synchronized(mLockObj) {
+            for (CallGroup cg : mCallGroups) {
+                if (cg.isReferrer(call)) {
+                    return cg;
+                }
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/src/java/com/android/ims/internal/ICall.java b/src/java/com/android/ims/internal/ICall.java
new file mode 100644 (file)
index 0000000..e38b6e6
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+/**
+ * IMS call interface
+ *
+ * @hide
+ */
+public interface ICall {
+    /**
+     * Closes this object. This object is not usable after being closed.
+     */
+    public void close();
+
+    /**
+     * Checks if the call has a same remote user identity or not.
+     *
+     * @param userId the remote user identity
+     * @return true if the remote user identity is equal; otherwise, false
+     */
+    public boolean checkIfRemoteUserIsSame(String userId);
+
+    /**
+     * Checks if the call is equal or not.
+     *
+     * @param call the call to be compared
+     * @return true if the call is equal; otherwise, false
+     */
+    public boolean equalsTo(ICall call);
+}
diff --git a/src/java/com/android/ims/internal/ICallGroup.java b/src/java/com/android/ims/internal/ICallGroup.java
new file mode 100644 (file)
index 0000000..9b65d61
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+import java.util.ArrayList;
+
+/**
+ * Provides the interface to manage all calls which are established
+ * hereafter the initial 1-to-1 call is established.
+ * It's for providing the dummy calls which are disconnected with the IMS network after
+ * merged or extended to the conference.
+ *
+ * @hide
+ */
+public interface ICallGroup {
+    /**
+     * Gets the neutral referrer call object of this group.
+     *
+     * @return the neutral referrer call object
+     */
+    public ICall getNeutralReferrer();
+
+    /**
+     * Gets the owner call object of this group.
+     *
+     * @return the owner call object
+     */
+    public ICall getOwner();
+
+    /**
+     * Gets the referrer call object which is equal to the specified name.
+     *
+     * @return the referrer call object
+     */
+    public ICall getReferrer(String name);
+
+    /**
+     * Gets the referrer call objects of this group.
+     *
+     * @return the referrer call objects
+     */
+    public ArrayList<ICall> getReferrers();
+
+    /**
+     * Checks if the call group has a referrer.
+     *
+     * @return true if the call group has a referrer; false otherwise.
+     */
+    public boolean hasReferrer();
+
+    /**
+     * Checks if the specified call object is owner of this group.
+     *
+     * @param call the call object to be checked if it is an owner of this group
+     * @return true if the specified call object is an owner; false otherwise
+     */
+    public boolean isOwner(ICall call);
+
+    /**
+     * Checks if the specified call object is a referrer of this group.
+     *
+     * @param call the call object to be checked if it is a referrer of this group
+     * @return true if the specified call object is a referrer; false otherwise
+     */
+    public boolean isReferrer(ICall call);
+
+    /**
+     * Adds the call object to this call group.
+     *
+     * @param call the call object to be added to this group
+     */
+    public void addReferrer(ICall call);
+
+    /**
+     * Removes the call object from this call group.
+     *
+     * @param call the call object to be removed from this group
+     */
+    public void removeReferrer(ICall call);
+
+    /**
+     * Sets the referrer call object in the neutral state while the operation is in progress.
+     *
+     * @param call the call object to be added to this group if the operation is succeeded.
+     */
+    public void setNeutralReferrer(ICall call);
+
+    /**
+     * Sets the call object as the owner of this call group.
+     * If the owner call object is already present, this method overwrites the existing owner
+     * call object.
+     *
+     * @param call the call object to be added to this group as owner
+     */
+    public void setOwner(ICall call);
+}
diff --git a/src/java/com/android/ims/internal/IImsCallSession.aidl b/src/java/com/android/ims/internal/IImsCallSession.aidl
new file mode 100644 (file)
index 0000000..aa2204e
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.ImsStreamMediaProfile;
+import com.android.ims.internal.IImsCallSessionListener;
+
+/**
+ * An IMS session that is associated with a SIP dialog which is established from/to
+ * INVITE request or a mid-call transaction to control the session.
+ * {@hide}
+ */
+interface IImsCallSession {
+    /**
+     * Closes the object. This object is not usable after being closed.
+     */
+    void close();
+
+    /**
+     * Gets the call ID of the session.
+     *
+     * @return the call ID
+     */
+    String getCallId();
+
+    /**
+     * Gets the call profile that this session is associated with
+     *
+     * @return the call profile that this session is associated with
+     */
+    ImsCallProfile getCallProfile();
+
+    /**
+     * Gets the local call profile that this session is associated with
+     *
+     * @return the local call profile that this session is associated with
+     */
+    ImsCallProfile getLocalCallProfile();
+
+    /**
+     * Gets the value associated with the specified property of this session.
+     *
+     * @return the string value associated with the specified property
+     */
+    String getProperty(String name);
+
+    /**
+     * Gets the session state. The value returned must be one of the states in
+     * {@link ImsCallSession#State}.
+     *
+     * @return the session state
+     */
+    int getState();
+
+    /**
+     * Checks if the session is in a call.
+     *
+     * @return true if the session is in a call
+     */
+    boolean isInCall();
+
+    /**
+     * Sets the listener to listen to the session events. A {@link IImsCallSession}
+     * can only hold one listener at a time. Subsequent calls to this method
+     * override the previous listener.
+     *
+     * @param listener to listen to the session events of this object
+     */
+    void setListener(in IImsCallSessionListener listener);
+
+    /**
+     * Mutes or unmutes the mic for the active call.
+     *
+     * @param muted true if the call is muted, false otherwise
+     */
+    void setMute(boolean muted);
+
+    /**
+     * Initiates an IMS call with the specified target and call profile.
+     * The session listener is called back upon defined session events.
+     * The method is only valid to call when the session state is in
+     * {@link ImsCallSession#State#IDLE}.
+     *
+     * @param callee dialed string to make the call to
+     * @param profile call profile to make the call with the specified service type,
+     *      call type and media information
+     * @see Listener#callSessionStarted, Listener#callSessionStartFailed
+     */
+    void start(String callee, in ImsCallProfile profile);
+
+    /**
+     * Initiates an IMS call with the specified participants and call profile.
+     * The session listener is called back upon defined session events.
+     * The method is only valid to call when the session state is in
+     * {@link ImsCallSession#State#IDLE}.
+     *
+     * @param participants participant list to initiate an IMS conference call
+     * @param profile call profile to make the call with the specified service type,
+     *      call type and media information
+     * @see Listener#callSessionStarted, Listener#callSessionStartFailed
+     */
+    void startConference(in String[] participants, in ImsCallProfile profile);
+
+    /**
+     * Accepts an incoming call or session update.
+     *
+     * @param callType call type specified in {@link ImsCallProfile} to be answered
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to be answered
+     * @see Listener#callSessionStarted
+     */
+    void accept(int callType, in ImsStreamMediaProfile profile);
+
+    /**
+     * Rejects an incoming call or session update.
+     *
+     * @param reason reason code to reject an incoming call
+     * @see Listener#callSessionStartFailed
+     */
+    void reject(int reason);
+
+    /**
+     * Terminates a call.
+     *
+     * @see Listener#callSessionTerminated
+     */
+    void terminate(int reason);
+
+    /**
+     * Puts a call on hold. When it succeeds, {@link Listener#callSessionHeld} is called.
+     *
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to hold the call
+     * @see Listener#callSessionHeld, Listener#callSessionHoldFailed
+     */
+    void hold(in ImsStreamMediaProfile profile);
+
+    /**
+     * Continues a call that's on hold. When it succeeds, {@link Listener#callSessionResumed}
+     * is called.
+     *
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to resume the call
+     * @see Listener#callSessionResumed, Listener#callSessionResumeFailed
+     */
+    void resume(in ImsStreamMediaProfile profile);
+
+    /**
+     * Merges the active & hold call. When it succeeds, {@link Listener#callSessionMerged}
+     * is called.
+     *
+     * @see Listener#callSessionMerged, Listener#callSessionMergeFailed
+     */
+    void merge();
+
+    /**
+     * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
+     *
+     * @param callType call type specified in {@link ImsCallProfile} to be updated
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to be updated
+     * @see Listener#callSessionUpdated, Listener#callSessionUpdateFailed
+     */
+    void update(int callType, in ImsStreamMediaProfile profile);
+
+    /**
+     * Extends this call to the conference call with the specified recipients.
+     *
+     * @participants participant list to be invited to the conference call after extending the call
+     * @see Listener#sessionConferenceExtened, Listener#sessionConferenceExtendFailed
+     */
+    void extendToConference(in String[] participants);
+
+    /**
+     * Requests the conference server to invite an additional participants to the conference.
+     *
+     * @participants participant list to be invited to the conference call
+     * @see Listener#sessionInviteParticipantsRequestDelivered,
+     *      Listener#sessionInviteParticipantsRequestFailed
+     */
+    void inviteParticipants(in String[] participants);
+
+    /**
+     * Requests the conference server to remove the specified participants from the conference.
+     *
+     * @param participants participant list to be removed from the conference call
+     * @see Listener#sessionRemoveParticipantsRequestDelivered,
+     *      Listener#sessionRemoveParticipantsRequestFailed
+     */
+    void removeParticipants(in String[] participants);
+
+    /**
+     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
+     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
+     * and event flash to 16. Currently, event flash is not supported.
+     *
+     * @param code the DTMF to send. Value 0 to 15 (inclusive) are valid inputs.
+     * @param duration the interval in milli-seconds between the DTMFs
+     */
+    void sendDtmf(int code, int duration);
+
+    /**
+     * Sends an USSD message.
+     *
+     * @param ussdMessage USSD message to send
+     */
+    void sendUssd(String ussdMessage);
+}
diff --git a/src/java/com/android/ims/internal/IImsCallSessionListener.aidl b/src/java/com/android/ims/internal/IImsCallSessionListener.aidl
new file mode 100644 (file)
index 0000000..f36cf39
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+import com.android.ims.ImsStreamMediaProfile;
+import com.android.ims.ImsCallProfile;
+import com.android.ims.ImsReasonInfo;
+import com.android.ims.ImsConferenceState;
+import com.android.ims.internal.IImsCallSession;
+
+/**
+ * A listener type for receiving notification on IMS call session events.
+ * When an event is generated for an {@link IImsCallSession}, the application is notified
+ * by having one of the methods called on the {@link IImsCallSessionListener}.
+ * {@hide}
+ */
+interface IImsCallSessionListener {
+    /**
+     * Notifies the result of the basic session operation (setup / terminate).
+     */
+    void callSessionProgressing(in IImsCallSession session, in ImsStreamMediaProfile profile);
+    void callSessionStarted(in IImsCallSession session, in ImsCallProfile profile);
+    void callSessionStartFailed(in IImsCallSession session, in ImsReasonInfo reasonInfo);
+    void callSessionTerminated(in IImsCallSession session, in ImsReasonInfo reasonInfo);
+
+    /**
+     * Notifies the result of the call hold/resume operation.
+     */
+    void callSessionHeld(in IImsCallSession session, in ImsCallProfile profile);
+    void callSessionHoldFailed(in IImsCallSession session, in ImsReasonInfo reasonInfo);
+    void callSessionHoldReceived(in IImsCallSession session, in ImsCallProfile profile);
+    void callSessionResumed(in IImsCallSession session, in ImsCallProfile profile);
+    void callSessionResumeFailed(in IImsCallSession session, in ImsReasonInfo reasonInfo);
+    void callSessionResumeReceived(in IImsCallSession session, in ImsCallProfile profile);
+
+    /**
+     * Notifiies the result of call merge operation.
+     */
+    void callSessionMerged(in IImsCallSession session,
+            in IImsCallSession newSession, in ImsCallProfile profile);
+    void callSessionMergeFailed(in IImsCallSession session,
+            in ImsReasonInfo reasonInfo);
+
+    /**
+     * Notifies the result of call upgrade / downgrade or any other call updates.
+     */
+    void callSessionUpdated(in IImsCallSession session,
+            in ImsCallProfile profile);
+    void callSessionUpdateFailed(in IImsCallSession session,
+            in ImsReasonInfo reasonInfo);
+    void callSessionUpdateReceived(in IImsCallSession session,
+            in ImsCallProfile profile);
+
+    /**
+     * Notifies the result of conference extension.
+     */
+    void callSessionConferenceExtended(in IImsCallSession session,
+            in IImsCallSession newSession, in ImsCallProfile profile);
+    void callSessionConferenceExtendFailed(in IImsCallSession session,
+            in ImsReasonInfo reasonInfo);
+    void callSessionConferenceExtendReceived(in IImsCallSession session,
+            in IImsCallSession newSession, in ImsCallProfile profile);
+
+    /**
+     * Notifies the result of the participant invitation / removal to/from the conference session.
+     */
+    void callSessionInviteParticipantsRequestDelivered(in IImsCallSession session);
+    void callSessionInviteParticipantsRequestFailed(in IImsCallSession session,
+            in ImsReasonInfo reasonInfo);
+    void callSessionRemoveParticipantsRequestDelivered(in IImsCallSession session);
+    void callSessionRemoveParticipantsRequestFailed(in IImsCallSession session,
+            in ImsReasonInfo reasonInfo);
+
+    /**
+     * Notifies the changes of the conference info. in the conference session.
+     */
+    void callSessionConferenceStateUpdated(in IImsCallSession session,
+            in ImsConferenceState state);
+
+    /**
+     * Notifies the incoming USSD message.
+     */
+    void callSessionUssdMessageReceived(in IImsCallSession session,
+            int mode, String ussdMessage);
+}
diff --git a/src/java/com/android/ims/internal/IImsRegistrationListener.aidl b/src/java/com/android/ims/internal/IImsRegistrationListener.aidl
new file mode 100644 (file)
index 0000000..5f243a0
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+/**
+ * A listener type for receiving notifications about the changes to
+ * the IMS connection(registration).
+ *
+ * {@hide}
+ */
+interface IImsRegistrationListener {
+    /**
+     * Notifies the application when the device is connected to the IMS network.
+     */
+    void registrationConnected();
+
+    /**
+     * Notifies the application when the device is disconnected from the IMS network.
+     */
+    void registrationDisconnected();
+
+    /**
+     * Notifies the application when its suspended IMS connection is resumed,
+     * meaning the connection now allows throughput.
+     */
+    void registrationResumed();
+
+    /**
+     * Notifies the application when its current IMS connection is suspended,
+     * meaning there is no data throughput.
+     */
+    void registrationSuspended();
+
+    /**
+     * Notifies the application when its current IMS connection is updated
+     * since the service setting is changed or the service is added/removed.
+     *
+     * @param serviceClass a service class specified in {@link ImsServiceClass}
+     * @param event an event type when this callback is called
+     *    If {@code event} is 0, meaning the specified service is removed from the IMS connection.
+     *    Else ({@code event} is 1), meaning the specified service is added to the IMS connection.
+     */
+    void registrationServiceCapabilityChanged(int serviceClass, int event);
+}
diff --git a/src/java/com/android/ims/internal/IImsService.aidl b/src/java/com/android/ims/internal/IImsService.aidl
new file mode 100644 (file)
index 0000000..bac5651
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+import android.app.PendingIntent;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsCallSessionListener;
+import com.android.ims.internal.IImsUt;
+
+/**
+ * {@hide}
+ */
+interface IImsService {
+    int open(int serviceClass, in PendingIntent incomingCallIntent,
+            in IImsRegistrationListener listener);
+    void close(int serviceId);
+    boolean isConnected(int serviceId, int serviceType, int callType);
+    boolean isOpened(int serviceId);
+    void setRegistrationListener(int serviceId, in IImsRegistrationListener listener);
+
+    ImsCallProfile createCallProfile(int serviceId, int serviceType, int callType);
+
+    IImsCallSession createCallSession(int serviceId, in ImsCallProfile profile,
+            in IImsCallSessionListener listener);
+    IImsCallSession getPendingCallSession(int serviceId, String callId);
+
+    /**
+     * Ut interface for the supplementary service configuration.
+     */
+    IImsUt getUtInterface(int serviceId);
+}
diff --git a/src/java/com/android/ims/internal/IImsStreamMediaSession.aidl b/src/java/com/android/ims/internal/IImsStreamMediaSession.aidl
new file mode 100644 (file)
index 0000000..459c685
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+/**
+ *
+ * {@hide}
+ */
+interface IImsStreamMediaSession {
+    void close();
+}
diff --git a/src/java/com/android/ims/internal/IImsUt.aidl b/src/java/com/android/ims/internal/IImsUt.aidl
new file mode 100644 (file)
index 0000000..32929ed
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+import android.os.Bundle;
+
+import com.android.ims.internal.IImsUtListener;
+
+/**
+ * Provides the Ut interface interworking to get/set the supplementary service configuration.
+ *
+ * {@hide}
+ */
+interface IImsUt {
+    /**
+     * Closes the object. This object is not usable after being closed.
+     */
+    void close();
+
+    /**
+     * Retrieves the configuration of the call barring.
+     */
+    int queryCallBarring(int cbType);
+
+    /**
+     * Retrieves the configuration of the call forward.
+     */
+    int queryCallForward(int condition, String number);
+
+    /**
+     * Retrieves the configuration of the call waiting.
+     */
+    int queryCallWaiting();
+
+    /**
+     * Updates or retrieves the supplementary service configuration.
+     */
+    int transact(in Bundle ssInfo);
+
+    /**
+     * Updates the configuration of the call barring.
+     */
+    int updateCallBarring(int cbType, boolean enable);
+
+    /**
+     * Updates the configuration of the call forward.
+     */
+    int updateCallForward(int action, int condition, String number, int timeSeconds);
+
+    /**
+     * Updates the configuration of the call waiting.
+     */
+    int updateCallWaiting(boolean enable);
+
+    /**
+     * Sets the listener.
+     */
+    void setListener(in IImsUtListener listener);
+}
diff --git a/src/java/com/android/ims/internal/IImsUtListener.aidl b/src/java/com/android/ims/internal/IImsUtListener.aidl
new file mode 100644 (file)
index 0000000..3f1b5a7
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+import android.os.Bundle;
+
+import com.android.ims.ImsCallForwardInfo;
+import com.android.ims.ImsSsInfo;
+import com.android.ims.internal.IImsUt;
+
+/**
+ * {@hide}
+ */
+interface IImsUtListener {
+    /**
+     * Notifies the result of the supplementary service configuration udpate.
+     */
+    void utConfigurationUpdated(in IImsUt ut, int id);
+    void utConfigurationUpdateFailed(in IImsUt ut, int id, int errorCode);
+
+    /**
+     * Notifies the result of the supplementary service configuration query.
+     */
+    void utConfigurationQueried(in IImsUt ut, int id, in Bundle ssInfo);
+    void utConfigurationQueryFailed(in IImsUt ut, int id, int errorCode);
+
+    /**
+     * Notifies the status of the call barring supplementary service.
+     */
+    void utConfigurationCallBarringQueried(in IImsUt ut,
+            int id, in ImsSsInfo[] cbInfo);
+
+    /**
+     * Notifies the status of the call forwarding supplementary service.
+     */
+    void utConfigurationCallForwardQueried(in IImsUt ut,
+            int id, in ImsCallForwardInfo[] cfInfo);
+
+    /**
+     * Notifies the status of the call waiting supplementary service.
+     */
+    void utConfigurationCallWaitingQueried(in IImsUt ut,
+            int id, in ImsSsInfo[] cwInfo);
+}
diff --git a/src/java/com/android/ims/internal/ImsCallSession.java b/src/java/com/android/ims/internal/ImsCallSession.java
new file mode 100644 (file)
index 0000000..208875c
--- /dev/null
@@ -0,0 +1,988 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+import android.os.RemoteException;
+import android.telephony.Rlog;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.ImsConferenceState;
+import com.android.ims.ImsReasonInfo;
+import com.android.ims.ImsStreamMediaProfile;
+
+/**
+ * Provides the call initiation/termination, and media exchange between two IMS endpoints.
+ * It directly communicates with IMS service which implements the IMS protocol behavior.
+ *
+ * @hide
+ */
+public class ImsCallSession {
+    private static final String TAG = "ImsCallSession";
+
+    /**
+     * Defines IMS call session state.
+     */
+    public static class State {
+        public static final int IDLE = 0;
+        public static final int INITIATED = 1;
+        public static final int NEGOTIATING = 2;
+        public static final int ESTABLISHING = 3;
+        public static final int ESTABLISHED = 4;
+
+        public static final int RENEGOTIATING = 5;
+        public static final int REESTABLISHING = 6;
+
+        public static final int TERMINATING = 7;
+        public static final int TERMINATED = 8;
+
+        public static final int INVALID = (-1);
+
+        /**
+         * Converts the state to string.
+         */
+        public static String toString(int state) {
+            switch (state) {
+                case IDLE:
+                    return "IDLE";
+                case INITIATED:
+                    return "INITIATED";
+                case NEGOTIATING:
+                    return "NEGOTIATING";
+                case ESTABLISHING:
+                    return "ESTABLISHING";
+                case ESTABLISHED:
+                    return "ESTABLISHED";
+                case RENEGOTIATING:
+                    return "RENEGOTIATING";
+                case REESTABLISHING:
+                    return "REESTABLISHING";
+                case TERMINATING:
+                    return "TERMINATING";
+                case TERMINATED:
+                    return "TERMINATED";
+                default:
+                    return "UNKNOWN";
+            }
+        }
+
+        private State() {
+        }
+    }
+
+    /**
+     * Listener for events relating to an IMS session, such as when a session is being
+     * recieved ("on ringing") or a call is outgoing ("on calling").
+     * <p>Many of these events are also received by {@link ImsCall.Listener}.</p>
+     */
+    public static class Listener {
+        /**
+         * Called when a request is sent out to initiate a new session
+         * and 1xx response is received from the network.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionProgressing(ImsCallSession session,
+                ImsStreamMediaProfile profile) {
+            // no-op
+        }
+
+        /**
+         * Called when the session is established.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionStarted(ImsCallSession session,
+                ImsCallProfile profile) {
+            // no-op
+        }
+
+        /**
+         * Called when the session establishment is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the session establishment failure
+         */
+        public void callSessionStartFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the session is terminated.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the session termination
+         */
+        public void callSessionTerminated(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the session is in hold.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionHeld(ImsCallSession session,
+                ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the session hold is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the session hold failure
+         */
+        public void callSessionHoldFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the session hold is received from the remote user.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionHoldReceived(ImsCallSession session,
+                ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the session resume is done.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionResumed(ImsCallSession session,
+                ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the session resume is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the session resume failure
+         */
+        public void callSessionResumeFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the session resume is received from the remote user.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionResumeReceived(ImsCallSession session,
+                ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the session merge is done.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param newSession the session object that is merged with an active & hold session
+         */
+        public void callSessionMerged(ImsCallSession session,
+                ImsCallSession newSession, ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the session merge is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the call merge failure
+         */
+        public void callSessionMergeFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the session is updated (except for hold/unhold).
+         *
+         * @param call the call object that carries out the IMS call
+         */
+        public void callSessionUpdated(ImsCallSession session,
+                ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the session update is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the session update failure
+         */
+        public void callSessionUpdateFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the session update is received from the remote user.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionUpdateReceived(ImsCallSession session,
+                ImsCallProfile profile) {
+            // no-op
+        }
+
+        /**
+         * Called when the session is extended to the conference session.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param newSession the session object that is extended to the conference
+         *      from the active session
+         */
+        public void callSessionConferenceExtended(ImsCallSession session,
+                ImsCallSession newSession, ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the conference extension is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the conference extension failure
+         */
+        public void callSessionConferenceExtendFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the conference extension is received from the remote user.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionConferenceExtendReceived(ImsCallSession session,
+                ImsCallSession newSession, ImsCallProfile profile) {
+            // no-op
+        }
+
+        /**
+         * Called when the invitation request of the participants is delivered to the conference
+         * server.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
+            // no-op
+        }
+
+        /**
+         * Called when the invitation request of the participants is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the conference invitation failure
+         */
+        public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            // no-op
+        }
+
+        /**
+         * Called when the removal request of the participants is delivered to the conference
+         * server.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
+            // no-op
+        }
+
+        /**
+         * Called when the removal request of the participants is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the conference removal failure
+         */
+        public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            // no-op
+        }
+
+        /**
+         * Called when the conference state is updated.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionConferenceStateUpdated(ImsCallSession session,
+                ImsConferenceState state) {
+            // no-op
+        }
+
+        /**
+         * Called when the USSD message is received from the network.
+         *
+         * @param mode mode of the USSD message (REQUEST / NOTIFY)
+         * @param ussdMessage USSD message
+         */
+        public void callSessionUssdMessageReceived(ImsCallSession session,
+                int mode, String ussdMessage) {
+            // no-op
+        }
+    }
+
+    private final IImsCallSession miSession;
+    private boolean mClosed = false;
+    private Listener mListener;
+
+    public ImsCallSession(IImsCallSession iSession) {
+        miSession = iSession;
+
+        if (iSession != null) {
+            try {
+                iSession.setListener(new IImsCallSessionListenerProxy());
+            } catch (RemoteException e) {
+            }
+        } else {
+            mClosed = true;
+        }
+    }
+
+    public ImsCallSession(IImsCallSession iSession, Listener listener) {
+        this(iSession);
+        setListener(listener);
+    }
+
+    /**
+     * Closes this object. This object is not usable after being closed.
+     */
+    public synchronized void close() {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.close();
+            mClosed = true;
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Gets the call ID of the session.
+     *
+     * @return the call ID
+     */
+    public String getCallId() {
+        if (mClosed) {
+            return null;
+        }
+
+        try {
+            return miSession.getCallId();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the call profile that this session is associated with
+     *
+     * @return the call profile that this session is associated with
+     */
+    public ImsCallProfile getCallProfile() {
+        if (mClosed) {
+            return null;
+        }
+
+        try {
+            return miSession.getCallProfile();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the local call profile that this session is associated with
+     *
+     * @return the local call profile that this session is associated with
+     */
+    public ImsCallProfile getLocalCallProfile() {
+        if (mClosed) {
+            return null;
+        }
+
+        try {
+            return miSession.getLocalCallProfile();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the value associated with the specified property of this session.
+     *
+     * @return the string value associated with the specified property
+     */
+    public String getProperty(String name) {
+        if (mClosed) {
+            return null;
+        }
+
+        try {
+            return miSession.getProperty(name);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the session state.
+     * The value returned must be one of the states in {@link State}.
+     *
+     * @return the session state
+     */
+    public int getState() {
+        if (mClosed) {
+            return State.INVALID;
+        }
+
+        try {
+            return miSession.getState();
+        } catch (RemoteException e) {
+            return State.INVALID;
+        }
+    }
+
+    /**
+     * Gets the native IMS call session.
+     * @hide
+     */
+    public IImsCallSession getSession() {
+        return miSession;
+    }
+
+    /**
+     * Checks if the session is in call.
+     *
+     * @return true if the session is in call
+     */
+    public boolean isInCall() {
+        if (mClosed) {
+            return false;
+        }
+
+        try {
+            return miSession.isInCall();
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Sets the listener to listen to the session events. A {@link ImsCallSession}
+     * can only hold one listener at a time. Subsequent calls to this method
+     * override the previous listener.
+     *
+     * @param listener to listen to the session events of this object
+     */
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Mutes or unmutes the mic for the active call.
+     *
+     * @param muted true if the call is muted, false otherwise
+     */
+    public void setMute(boolean muted) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.setMute(muted);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Initiates an IMS call with the specified target and call profile.
+     * The session listener is called back upon defined session events.
+     * The method is only valid to call when the session state is in
+     * {@link ImsCallSession#State#IDLE}.
+     *
+     * @param callee dialed string to make the call to
+     * @param profile call profile to make the call with the specified service type,
+     *      call type and media information
+     * @see Listener#callSessionStarted, Listener#callSessionStartFailed
+     */
+    public void start(String callee, ImsCallProfile profile) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.start(callee, profile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Initiates an IMS conference call with the specified target and call profile.
+     * The session listener is called back upon defined session events.
+     * The method is only valid to call when the session state is in
+     * {@link ImsCallSession#State#IDLE}.
+     *
+     * @param participants participant list to initiate an IMS conference call
+     * @param profile call profile to make the call with the specified service type,
+     *      call type and media information
+     * @see Listener#callSessionStarted, Listener#callSessionStartFailed
+     */
+    public void start(String[] participants, ImsCallProfile profile) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.startConference(participants, profile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Accepts an incoming call or session update.
+     *
+     * @param callType call type specified in {@link ImsCallProfile} to be answered
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to be answered
+     * @see Listener#callSessionStarted
+     */
+    public void accept(int callType, ImsStreamMediaProfile profile) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.accept(callType, profile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Rejects an incoming call or session update.
+     *
+     * @param reason reason code to reject an incoming call
+     * @see Listener#callSessionStartFailed
+     */
+    public void reject(int reason) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.reject(reason);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Terminates a call.
+     *
+     * @see Listener#callSessionTerminated
+     */
+    public void terminate(int reason) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.terminate(reason);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Puts a call on hold. When it succeeds, {@link Listener#callSessionHeld} is called.
+     *
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to hold the call
+     * @see Listener#callSessionHeld, Listener#callSessionHoldFailed
+     */
+    public void hold(ImsStreamMediaProfile profile) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.hold(profile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Continues a call that's on hold. When it succeeds,
+     * {@link Listener#callSessionResumed} is called.
+     *
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to resume the call
+     * @see Listener#callSessionResumed, Listener#callSessionResumeFailed
+     */
+    public void resume(ImsStreamMediaProfile profile) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.resume(profile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Merges the active & hold call. When it succeeds,
+     * {@link Listener#callSessionMerged} is called.
+     *
+     * @see Listener#callSessionMerged, Listener#callSessionMergeFailed
+     */
+    public void merge() {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.merge();
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
+     *
+     * @param callType call type specified in {@link ImsCallProfile} to be updated
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to be updated
+     * @see Listener#callSessionUpdated, Listener#callSessionUpdateFailed
+     */
+    public void update(int callType, ImsStreamMediaProfile profile) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.update(callType, profile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Extends this call to the conference call with the specified recipients.
+     *
+     * @participants participant list to be invited to the conference call after extending the call
+     * @see Listener#sessionConferenceExtened, Listener#sessionConferenceExtendFailed
+     */
+    public void extendToConference(String[] participants) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.extendToConference(participants);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Requests the conference server to invite an additional participants to the conference.
+     *
+     * @participants participant list to be invited to the conference call
+     * @see Listener#sessionInviteParticipantsRequestDelivered,
+     *      Listener#sessionInviteParticipantsRequestFailed
+     */
+    public void inviteParticipants(String[] participants) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.inviteParticipants(participants);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Requests the conference server to remove the specified participants from the conference.
+     *
+     * @param participants participant list to be removed from the conference call
+     * @see Listener#sessionRemoveParticipantsRequestDelivered,
+     *      Listener#sessionRemoveParticipantsRequestFailed
+     */
+    public void removeParticipants(String[] participants) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.removeParticipants(participants);
+        } catch (RemoteException e) {
+        }
+    }
+
+
+    /**
+     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
+     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
+     * and event flash to 16. Currently, event flash is not supported.
+     *
+     * @param code the DTMF to send. Value 0 to 15 (inclusive) are valid inputs.
+     */
+    public void sendDtmf(int code) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.sendDtmf(code, 200);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Sends an USSD message.
+     *
+     * @param ussdMessage USSD message to send
+     */
+    public void sendUssd(String ussdMessage) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.sendUssd(ussdMessage);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * A listener type for receiving notification on IMS call session events.
+     * When an event is generated for an {@link IImsCallSession},
+     * the application is notified by having one of the methods called on
+     * the {@link IImsCallSessionListener}.
+     */
+    private class IImsCallSessionListenerProxy extends IImsCallSessionListener.Stub {
+        /**
+         * Notifies the result of the basic session operation (setup / terminate).
+         */
+        @Override
+        public void callSessionProgressing(IImsCallSession session,
+                ImsStreamMediaProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionProgressing(ImsCallSession.this, profile);
+            }
+        }
+
+        @Override
+        public void callSessionStarted(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionStarted(ImsCallSession.this, profile);
+            }
+        }
+
+        @Override
+        public void callSessionStartFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        @Override
+        public void callSessionTerminated(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionTerminated(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        /**
+         * Notifies the result of the call hold/resume operation.
+         */
+        @Override
+        public void callSessionHeld(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionHeld(ImsCallSession.this, profile);
+            }
+        }
+
+        @Override
+        public void callSessionHoldFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionHoldFailed(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        @Override
+        public void callSessionHoldReceived(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionHoldReceived(ImsCallSession.this, profile);
+            }
+        }
+
+        @Override
+        public void callSessionResumed(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionResumed(ImsCallSession.this, profile);
+            }
+        }
+
+        @Override
+        public void callSessionResumeFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionResumeFailed(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        @Override
+        public void callSessionResumeReceived(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionResumeReceived(ImsCallSession.this, profile);
+            }
+        }
+
+        /**
+         * Notifiies the result of call merge operation.
+         */
+        @Override
+        public void callSessionMerged(IImsCallSession session,
+                IImsCallSession newSession, ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionMerged(ImsCallSession.this,
+                        new ImsCallSession(newSession), profile);
+            }
+        }
+
+        @Override
+        public void callSessionMergeFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        /**
+         * Notifies the result of call upgrade / downgrade or any other call updates.
+         */
+        @Override
+        public void callSessionUpdated(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionUpdated(ImsCallSession.this, profile);
+            }
+        }
+
+        @Override
+        public void callSessionUpdateFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        @Override
+        public void callSessionUpdateReceived(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionUpdateReceived(ImsCallSession.this, profile);
+            }
+        }
+
+        /**
+         * Notifies the result of conference extension.
+         */
+        @Override
+        public void callSessionConferenceExtended(IImsCallSession session,
+                IImsCallSession newSession, ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionConferenceExtended(ImsCallSession.this,
+                        new ImsCallSession(newSession), profile);
+            }
+        }
+
+        @Override
+        public void callSessionConferenceExtendFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionConferenceExtendFailed(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        @Override
+        public void callSessionConferenceExtendReceived(IImsCallSession session,
+                IImsCallSession newSession, ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionConferenceExtendReceived(ImsCallSession.this,
+                        new ImsCallSession(newSession), profile);
+            }
+        }
+
+        /**
+         * Notifies the result of the participant invitation / removal to/from
+         * the conference session.
+         */
+        @Override
+        public void callSessionInviteParticipantsRequestDelivered(IImsCallSession session) {
+            if (mListener != null) {
+                mListener.callSessionInviteParticipantsRequestDelivered(ImsCallSession.this);
+            }
+        }
+
+        @Override
+        public void callSessionInviteParticipantsRequestFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this,
+                        reasonInfo);
+            }
+        }
+
+        @Override
+        public void callSessionRemoveParticipantsRequestDelivered(IImsCallSession session) {
+            if (mListener != null) {
+                mListener.callSessionRemoveParticipantsRequestDelivered(ImsCallSession.this);
+            }
+        }
+
+        @Override
+        public void callSessionRemoveParticipantsRequestFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this,
+                        reasonInfo);
+            }
+        }
+
+        /**
+         * Notifies the changes of the conference info. in the conference session.
+         */
+        @Override
+        public void callSessionConferenceStateUpdated(IImsCallSession session,
+                ImsConferenceState state) {
+            if (mListener != null) {
+                mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state);
+            }
+        }
+
+        /**
+         * Notifies the incoming USSD message.
+         */
+        @Override
+        public void callSessionUssdMessageReceived(IImsCallSession session,
+                int mode, String ussdMessage) {
+            if (mListener != null) {
+                mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, ussdMessage);
+            }
+        }
+    }
+}
diff --git a/src/java/com/android/ims/internal/ImsStreamMediaSession.java b/src/java/com/android/ims/internal/ImsStreamMediaSession.java
new file mode 100644 (file)
index 0000000..5814891
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+
+
+/**
+ * Provides the APIs to control the media session, such as passing the surface object,
+ * controlling the camera (front/rear selection, zoom, brightness, ...) for a video calling.
+ *
+ * @hide
+ */
+public class ImsStreamMediaSession {
+    private static final String TAG = "ImsStreamMediaSession";
+
+    /**
+     * Listener for events relating to an IMS media session.
+     * <p>Many of these events are also received by {@link ImsStreamMediaSession.Listener}.</p>
+     */
+    public static class Listener {
+    }
+
+    private Listener mListener;
+
+    ImsStreamMediaSession(IImsStreamMediaSession mediaSession) {
+    }
+
+    ImsStreamMediaSession(IImsStreamMediaSession mediaSession, Listener listener) {
+        this(mediaSession);
+        setListener(listener);
+    }
+
+    /**
+     * Sets the listener to listen to the media session events. A {@code ImsStreamMediaSession}
+     * can only hold one listener at a time. Subsequent calls to this method
+     * override the previous listener.
+     *
+     * @param listener to listen to the media session events of this object
+     */
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+}