ImsManager.java revision 7da5a11accec318cc3da2087fb3a891db4f661f7
1/*
2 * Copyright (c) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.ims;
18
19import android.app.PendingIntent;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.os.IBinder;
24import android.os.IBinder.DeathRecipient;
25import android.os.Message;
26import android.os.Process;
27import android.os.RemoteException;
28import android.os.ServiceManager;
29import android.telephony.Rlog;
30
31import com.android.ims.internal.IImsCallSession;
32import com.android.ims.internal.IImsRegistrationListener;
33import com.android.ims.internal.IImsService;
34import com.android.ims.internal.IImsUt;
35import com.android.ims.internal.ImsCallSession;
36import com.android.ims.internal.IImsConfig;
37
38
39import java.util.HashMap;
40
41/**
42 * Provides APIs for IMS services, such as initiating IMS calls, and provides access to
43 * the operator's IMS network. This class is the starting point for any IMS actions.
44 * You can acquire an instance of it with {@link #getInstance getInstance()}.</p>
45 * <p>The APIs in this class allows you to:</p>
46 *
47 * @hide
48 */
49public class ImsManager {
50    /**
51     * For accessing the IMS related service.
52     * Internal use only.
53     * @hide
54     */
55    private static final String IMS_SERVICE = "ims";
56
57    /**
58     * The result code to be sent back with the incoming call {@link PendingIntent}.
59     * @see #open(PendingIntent, ImsConnectionStateListener)
60     */
61    public static final int INCOMING_CALL_RESULT_CODE = 101;
62
63    /**
64     * Key to retrieve the call ID from an incoming call intent.
65     * @see #open(PendingIntent, ImsConnectionStateListener)
66     */
67    public static final String EXTRA_CALL_ID = "android:imsCallID";
68
69    /**
70     * Action to broadcast when ImsService is up.
71     * Internal use only.
72     * @hide
73     */
74    public static final String ACTION_IMS_SERVICE_UP =
75            "com.android.ims.IMS_SERVICE_UP";
76
77    /**
78     * Action to broadcast when ImsService is down.
79     * Internal use only.
80     * @hide
81     */
82    public static final String ACTION_IMS_SERVICE_DOWN =
83            "com.android.ims.IMS_SERVICE_DOWN";
84
85    /**
86     * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents.
87     * A long value; the subId corresponding to the IMS service coming up or down.
88     * Internal use only.
89     * @hide
90     */
91    public static final String EXTRA_SUBID = "android:subid";
92
93    /**
94     * Action for the incoming call intent for the Phone app.
95     * Internal use only.
96     * @hide
97     */
98    public static final String ACTION_IMS_INCOMING_CALL =
99            "com.android.ims.IMS_INCOMING_CALL";
100
101    /**
102     * Part of the ACTION_IMS_INCOMING_CALL intents.
103     * An integer value; service identifier obtained from {@link ImsManager#open}.
104     * Internal use only.
105     * @hide
106     */
107    public static final String EXTRA_SERVICE_ID = "android:imsServiceId";
108
109    /**
110     * Part of the ACTION_IMS_INCOMING_CALL intents.
111     * An boolean value; Flag to indicate that the incoming call is a normal call or call for USSD.
112     * The value "true" indicates that the incoming call is for USSD.
113     * Internal use only.
114     * @hide
115     */
116    public static final String EXTRA_USSD = "android:ussd";
117
118
119
120    private static final String TAG = "ImsManager";
121    private static final boolean DBG = true;
122
123    private static HashMap<Long, ImsManager> sImsManagerInstances =
124            new HashMap<Long, ImsManager>();
125
126    private Context mContext;
127    private long mSubId;
128    private IImsService mImsService = null;
129    private ImsServiceDeathRecipient mDeathRecipient = new ImsServiceDeathRecipient();
130    // Ut interface for the supplementary service configuration
131    private ImsUt mUt = null;
132    // Interface to get/set ims config items
133    private ImsConfig mConfig = null;
134
135    /**
136     * Gets a manager instance.
137     *
138     * @param context application context for creating the manager object
139     * @param subId the subscription ID for the IMS Service
140     * @return the manager instance corresponding to the subId
141     */
142    public static ImsManager getInstance(Context context, long subId) {
143        synchronized (sImsManagerInstances) {
144            if (sImsManagerInstances.containsKey(subId))
145                return sImsManagerInstances.get(subId);
146
147            ImsManager mgr = new ImsManager(context, subId);
148            sImsManagerInstances.put(subId, mgr);
149
150            return mgr;
151        }
152    }
153
154    private ImsManager(Context context, long subId) {
155        mContext = context;
156        mSubId = subId;
157        createImsService(true);
158    }
159
160    /**
161     * Opens the IMS service for making calls and/or receiving generic IMS calls.
162     * The caller may make subsquent calls through {@link #makeCall}.
163     * The IMS service will register the device to the operator's network with the credentials
164     * (from ISIM) periodically in order to receive calls from the operator's network.
165     * When the IMS service receives a new call, it will send out an intent with
166     * the provided action string.
167     * The intent contains a call ID extra {@link getCallId} and it can be used to take a call.
168     *
169     * @param serviceClass a service class specified in {@link ImsServiceClass}
170     *      For VoLTE service, it MUST be a {@link ImsServiceClass#MMTEL}.
171     * @param incomingCallPendingIntent When an incoming call is received,
172     *        the IMS service will call {@link PendingIntent#send(Context, int, Intent)} to
173     *        send back the intent to the caller with {@link #INCOMING_CALL_RESULT_CODE}
174     *        as the result code and the intent to fill in the call ID; It cannot be null
175     * @param listener To listen to IMS registration events; It cannot be null
176     * @return identifier (greater than 0) for the specified service
177     * @throws NullPointerException if {@code incomingCallPendingIntent}
178     *      or {@code listener} is null
179     * @throws ImsException if calling the IMS service results in an error
180     * @see #getCallId
181     * @see #getServiceId
182     */
183    public int open(int serviceClass, PendingIntent incomingCallPendingIntent,
184            ImsConnectionStateListener listener) throws ImsException {
185        checkAndThrowExceptionIfServiceUnavailable();
186
187        if (incomingCallPendingIntent == null) {
188            throw new NullPointerException("incomingCallPendingIntent can't be null");
189        }
190
191        if (listener == null) {
192            throw new NullPointerException("listener can't be null");
193        }
194
195        int result = 0;
196
197        try {
198            result = mImsService.open(serviceClass, incomingCallPendingIntent,
199                    createRegistrationListenerProxy(serviceClass, listener));
200        } catch (RemoteException e) {
201            throw new ImsException("open()", e,
202                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
203        }
204
205        if (result <= 0) {
206            // If the return value is a minus value,
207            // it means that an error occurred in the service.
208            // So, it needs to convert to the reason code specified in ImsReasonInfo.
209            throw new ImsException("open()", (result * (-1)));
210        }
211
212        return result;
213    }
214
215    /**
216     * Closes the specified service ({@link ImsServiceClass}) not to make/receive calls.
217     * All the resources that were allocated to the service are also released.
218     *
219     * @param serviceId a service id to be closed which is obtained from {@link ImsManager#open}
220     * @throws ImsException if calling the IMS service results in an error
221     */
222    public void close(int serviceId) throws ImsException {
223        checkAndThrowExceptionIfServiceUnavailable();
224
225        try {
226            mImsService.close(serviceId);
227        } catch (RemoteException e) {
228            throw new ImsException("close()", e,
229                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
230        } finally {
231            mUt = null;
232            mConfig = null;
233        }
234    }
235
236    /**
237     * Gets the configuration interface to provision / withdraw the supplementary service settings.
238     *
239     * @param serviceId a service id which is obtained from {@link ImsManager#open}
240     * @return the Ut interface instance
241     * @throws ImsException if getting the Ut interface results in an error
242     */
243    public ImsUtInterface getSupplementaryServiceConfiguration(int serviceId)
244            throws ImsException {
245        // FIXME: manage the multiple Ut interfaces based on the service id
246        if (mUt == null) {
247            checkAndThrowExceptionIfServiceUnavailable();
248
249            try {
250                IImsUt iUt = mImsService.getUtInterface(serviceId);
251
252                if (iUt == null) {
253                    throw new ImsException("getSupplementaryServiceConfiguration()",
254                            ImsReasonInfo.CODE_UT_NOT_SUPPORTED);
255                }
256
257                mUt = new ImsUt(iUt);
258            } catch (RemoteException e) {
259                throw new ImsException("getSupplementaryServiceConfiguration()", e,
260                        ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
261            }
262        }
263
264        return mUt;
265    }
266
267    /**
268     * Checks if the IMS service has successfully registered to the IMS network
269     * with the specified service & call type.
270     *
271     * @param serviceId a service id which is obtained from {@link ImsManager#open}
272     * @param serviceType a service type that is specified in {@link ImsCallProfile}
273     *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
274     *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
275     * @param callType a call type that is specified in {@link ImsCallProfile}
276     *        {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO}
277     *        {@link ImsCallProfile#CALL_TYPE_VOICE}
278     *        {@link ImsCallProfile#CALL_TYPE_VT}
279     *        {@link ImsCallProfile#CALL_TYPE_VS}
280     * @return true if the specified service id is connected to the IMS network;
281     *        false otherwise
282     * @throws ImsException if calling the IMS service results in an error
283     */
284    public boolean isConnected(int serviceId, int serviceType, int callType)
285            throws ImsException {
286        checkAndThrowExceptionIfServiceUnavailable();
287
288        try {
289            return mImsService.isConnected(serviceId, serviceType, callType);
290        } catch (RemoteException e) {
291            throw new ImsException("isServiceConnected()", e,
292                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
293        }
294    }
295
296    /**
297     * Checks if the specified IMS service is opend.
298     *
299     * @param serviceId a service id which is obtained from {@link ImsManager#open}
300     * @return true if the specified service id is opened; false otherwise
301     * @throws ImsException if calling the IMS service results in an error
302     */
303    public boolean isOpened(int serviceId) throws ImsException {
304        checkAndThrowExceptionIfServiceUnavailable();
305
306        try {
307            return mImsService.isOpened(serviceId);
308        } catch (RemoteException e) {
309            throw new ImsException("isOpened()", e,
310                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
311        }
312    }
313
314    /**
315     * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
316     *
317     * @param serviceId a service id which is obtained from {@link ImsManager#open}
318     * @param serviceType a service type that is specified in {@link ImsCallProfile}
319     *        {@link ImsCallProfile#SERVICE_TYPE_NONE}
320     *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
321     *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
322     * @param callType a call type that is specified in {@link ImsCallProfile}
323     *        {@link ImsCallProfile#CALL_TYPE_VOICE}
324     *        {@link ImsCallProfile#CALL_TYPE_VT}
325     *        {@link ImsCallProfile#CALL_TYPE_VT_TX}
326     *        {@link ImsCallProfile#CALL_TYPE_VT_RX}
327     *        {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
328     *        {@link ImsCallProfile#CALL_TYPE_VS}
329     *        {@link ImsCallProfile#CALL_TYPE_VS_TX}
330     *        {@link ImsCallProfile#CALL_TYPE_VS_RX}
331     * @return a {@link ImsCallProfile} object
332     * @throws ImsException if calling the IMS service results in an error
333     */
334    public ImsCallProfile createCallProfile(int serviceId,
335            int serviceType, int callType) throws ImsException {
336        checkAndThrowExceptionIfServiceUnavailable();
337
338        try {
339            return mImsService.createCallProfile(serviceId, serviceType, callType);
340        } catch (RemoteException e) {
341            throw new ImsException("createCallProfile()", e,
342                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
343        }
344    }
345
346    /**
347     * Creates a {@link ImsCall} to make a call.
348     *
349     * @param serviceId a service id which is obtained from {@link ImsManager#open}
350     * @param profile a call profile to make the call
351     *      (it contains service type, call type, media information, etc.)
352     * @param participants participants to invite the conference call
353     * @param listener listen to the call events from {@link ImsCall}
354     * @return a {@link ImsCall} object
355     * @throws ImsException if calling the IMS service results in an error
356     */
357    public ImsCall makeCall(int serviceId, ImsCallProfile profile, String[] callees,
358            ImsCall.Listener listener) throws ImsException {
359        if (DBG) {
360            log("makeCall :: serviceId=" + serviceId
361                    + ", profile=" + profile + ", callees=" + callees);
362        }
363
364        checkAndThrowExceptionIfServiceUnavailable();
365
366        ImsCall call = new ImsCall(mContext, profile);
367
368        call.setListener(listener);
369        ImsCallSession session = createCallSession(serviceId, profile);
370
371        if ((callees != null) && (callees.length == 1)) {
372            call.start(session, callees[0]);
373        } else {
374            call.start(session, callees);
375        }
376
377        return call;
378    }
379
380    /**
381     * Creates a {@link ImsCall} to take an incoming call.
382     *
383     * @param serviceId a service id which is obtained from {@link ImsManager#open}
384     * @param incomingCallIntent the incoming call broadcast intent
385     * @param listener to listen to the call events from {@link ImsCall}
386     * @return a {@link ImsCall} object
387     * @throws ImsException if calling the IMS service results in an error
388     */
389    public ImsCall takeCall(int serviceId, Intent incomingCallIntent,
390            ImsCall.Listener listener) throws ImsException {
391        if (DBG) {
392            log("takeCall :: serviceId=" + serviceId
393                    + ", incomingCall=" + incomingCallIntent);
394        }
395
396        checkAndThrowExceptionIfServiceUnavailable();
397
398        if (incomingCallIntent == null) {
399            throw new ImsException("Can't retrieve session with null intent",
400                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
401        }
402
403        int incomingServiceId = getServiceId(incomingCallIntent);
404
405        if (serviceId != incomingServiceId) {
406            throw new ImsException("Service id is mismatched in the incoming call intent",
407                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
408        }
409
410        String callId = getCallId(incomingCallIntent);
411
412        if (callId == null) {
413            throw new ImsException("Call ID missing in the incoming call intent",
414                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
415        }
416
417        try {
418            IImsCallSession session = mImsService.getPendingCallSession(serviceId, callId);
419
420            if (session == null) {
421                throw new ImsException("No pending session for the call",
422                        ImsReasonInfo.CODE_LOCAL_NO_PENDING_CALL);
423            }
424
425            ImsCall call = new ImsCall(mContext, session.getCallProfile());
426
427            call.attachSession(new ImsCallSession(session));
428            call.setListener(listener);
429
430            return call;
431        } catch (Throwable t) {
432            throw new ImsException("takeCall()", t, ImsReasonInfo.CODE_UNSPECIFIED);
433        }
434    }
435
436    /**
437     * Gets the config interface to get/set service/capability parameters.
438     *
439     * @return the ImsConfig instance.
440     * @throws ImsException if getting the setting interface results in an error.
441     */
442    public ImsConfig getConfigInterface() throws ImsException {
443
444        if (mConfig == null) {
445            checkAndThrowExceptionIfServiceUnavailable();
446
447            try {
448                IImsConfig config = mImsService.getConfigInterface();
449                if (config == null) {
450                    throw new ImsException("getConfigInterface()",
451                            ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
452                }
453                mConfig = new ImsConfig(config);
454            } catch (RemoteException e) {
455                throw new ImsException("getConfigInterface()", e,
456                        ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
457            }
458        }
459        if (DBG) log("getConfigInterface(), mConfig= " + mConfig);
460        return mConfig;
461    }
462
463    /**
464     * Gets the call ID from the specified incoming call broadcast intent.
465     *
466     * @param incomingCallIntent the incoming call broadcast intent
467     * @return the call ID or null if the intent does not contain it
468     */
469    private static String getCallId(Intent incomingCallIntent) {
470        if (incomingCallIntent == null) {
471            return null;
472        }
473
474        return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
475    }
476
477    /**
478     * Gets the service type from the specified incoming call broadcast intent.
479     *
480     * @param incomingCallIntent the incoming call broadcast intent
481     * @return the service identifier or -1 if the intent does not contain it
482     */
483    private static int getServiceId(Intent incomingCallIntent) {
484        if (incomingCallIntent == null) {
485            return (-1);
486        }
487
488        return incomingCallIntent.getIntExtra(EXTRA_SERVICE_ID, -1);
489    }
490
491    /**
492     * Binds the IMS service only if the service is not created.
493     */
494    private void checkAndThrowExceptionIfServiceUnavailable()
495            throws ImsException {
496        if (mImsService == null) {
497            createImsService(true);
498
499            if (mImsService == null) {
500                throw new ImsException("Service is unavailable",
501                        ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
502            }
503        }
504    }
505
506    private static String getImsServiceName(long subId) {
507        // TODO: MSIM implementation needs to decide on service name as a function of subId
508        // or value derived from subId (slot ID?)
509        return IMS_SERVICE;
510    }
511
512    /**
513     * Binds the IMS service to make/receive the call.
514     */
515    private void createImsService(boolean checkService) {
516        if (checkService) {
517            IBinder binder = ServiceManager.checkService(getImsServiceName(mSubId));
518
519            if (binder == null) {
520                return;
521            }
522        }
523
524        IBinder b = ServiceManager.getService(getImsServiceName(mSubId));
525
526        if (b != null) {
527            try {
528                b.linkToDeath(mDeathRecipient, 0);
529            } catch (RemoteException e) {
530            }
531        }
532
533        mImsService = IImsService.Stub.asInterface(b);
534    }
535
536    /**
537     * Creates a {@link ImsCallSession} with the specified call profile.
538     * Use other methods, if applicable, instead of interacting with
539     * {@link ImsCallSession} directly.
540     *
541     * @param serviceId a service id which is obtained from {@link ImsManager#open}
542     * @param profile a call profile to make the call
543     */
544    private ImsCallSession createCallSession(int serviceId,
545            ImsCallProfile profile) throws ImsException {
546        try {
547            return new ImsCallSession(mImsService.createCallSession(serviceId, profile, null));
548        } catch (RemoteException e) {
549            return null;
550        }
551    }
552
553    private ImsRegistrationListenerProxy createRegistrationListenerProxy(int serviceClass,
554            ImsConnectionStateListener listener) {
555        ImsRegistrationListenerProxy proxy =
556                new ImsRegistrationListenerProxy(serviceClass, listener);
557        return proxy;
558    }
559
560    private void log(String s) {
561        Rlog.d(TAG, s);
562    }
563
564    private void loge(String s) {
565        Rlog.e(TAG, s);
566    }
567
568    private void loge(String s, Throwable t) {
569        Rlog.e(TAG, s, t);
570    }
571
572    /**
573     * Used for turning on IMS.if its off already
574     */
575    public void turnOnIms() throws ImsException {
576        try {
577            mImsService.turnOnIms();
578        } catch (RemoteException e) {
579            throw new ImsException("turnOnIms() ", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
580        }
581    }
582
583    /**
584     * Used for turning off IMS completely in order to make the device CSFB'ed.
585     * Once turned off, all calls will be over CS.
586     */
587    public void turnOffIms() throws ImsException {
588        try {
589            mImsService.turnOffIms();
590        } catch (RemoteException e) {
591            throw new ImsException("turnOffIms() ", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
592        }
593    }
594
595    /**
596     * Death recipient class for monitoring IMS service.
597     */
598    private class ImsServiceDeathRecipient implements IBinder.DeathRecipient {
599        @Override
600        public void binderDied() {
601            mImsService = null;
602            mUt = null;
603            mConfig = null;
604
605            if (mContext != null) {
606                Intent intent = new Intent(ACTION_IMS_SERVICE_DOWN);
607                intent.putExtra(EXTRA_SUBID, mSubId);
608                mContext.sendBroadcast(new Intent(intent));
609            }
610        }
611    }
612
613    /**
614     * Adapter class for {@link IImsRegistrationListener}.
615     */
616    private class ImsRegistrationListenerProxy extends IImsRegistrationListener.Stub {
617        private int mServiceClass;
618        private ImsConnectionStateListener mListener;
619
620        public ImsRegistrationListenerProxy(int serviceClass,
621                ImsConnectionStateListener listener) {
622            mServiceClass = serviceClass;
623            mListener = listener;
624        }
625
626        public boolean isSameProxy(int serviceClass) {
627            return (mServiceClass == serviceClass);
628        }
629
630        @Override
631        public void registrationConnected() {
632            if (DBG) {
633                log("registrationConnected ::");
634            }
635
636            if (mListener != null) {
637                mListener.onImsConnected();
638            }
639        }
640
641        @Override
642        public void registrationDisconnected() {
643            if (DBG) {
644                log("registrationDisconnected ::");
645            }
646
647            if (mListener != null) {
648                mListener.onImsDisconnected();
649            }
650        }
651
652        @Override
653        public void registrationResumed() {
654            if (DBG) {
655                log("registrationResumed ::");
656            }
657
658            if (mListener != null) {
659                mListener.onImsResumed();
660            }
661        }
662
663        @Override
664        public void registrationSuspended() {
665            if (DBG) {
666                log("registrationSuspended ::");
667            }
668
669            if (mListener != null) {
670                mListener.onImsSuspended();
671            }
672        }
673
674        @Override
675        public void registrationServiceCapabilityChanged(int serviceClass, int event) {
676            log("registrationServiceCapabilityChanged :: serviceClass=" +
677                    serviceClass + ", event=" + event);
678
679            if (mListener != null) {
680                mListener.onImsConnected();
681            }
682        }
683
684        @Override
685        public void registrationFeatureCapabilityChanged(int serviceClass,
686                int[] enabledFeatures, int[] disabledFeatures) {
687            log("registrationFeatureCapabilityChanged :: serviceClass=" +
688                    serviceClass);
689        }
690
691    }
692}
693