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