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