Merge kwd to master
Change-Id: Idb607c0aa32f80fe4fe1539aedea7a221e9e7f04
diff --git a/src/java/com/android/ims/ImsManager.java b/src/java/com/android/ims/ImsManager.java
new file mode 100644
index 0000000..3e59f1a
--- /dev/null
+++ b/src/java/com/android/ims/ImsManager.java
@@ -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();
+ }
+ }
+ }
+}