ImsManager.java revision 183af604fb69ff73a9aeb333c0965221ab380f98
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.app.QueuedWork;
21import android.content.Context;
22import android.content.Intent;
23import android.os.IBinder;
24import android.os.Message;
25import android.os.RemoteException;
26import android.os.ServiceManager;
27import android.os.SystemProperties;
28import android.provider.Settings;
29import android.telecom.TelecomManager;
30import android.telephony.Rlog;
31import android.telephony.SubscriptionManager;
32import android.telephony.TelephonyManager;
33
34import com.android.ims.internal.IImsCallSession;
35import com.android.ims.internal.IImsEcbm;
36import com.android.ims.internal.IImsEcbmListener;
37import com.android.ims.internal.IImsRegistrationListener;
38import com.android.ims.internal.IImsService;
39import com.android.ims.internal.IImsUt;
40import com.android.ims.internal.ImsCallSession;
41import com.android.ims.internal.IImsConfig;
42
43import java.util.HashMap;
44
45/**
46 * Provides APIs for IMS services, such as initiating IMS calls, and provides access to
47 * the operator's IMS network. This class is the starting point for any IMS actions.
48 * You can acquire an instance of it with {@link #getInstance getInstance()}.</p>
49 * <p>The APIs in this class allows you to:</p>
50 *
51 * @hide
52 */
53public class ImsManager {
54
55    /*
56     * Debug flag to override configuration flag
57     */
58    public static final String PROPERTY_DBG_VOLTE_AVAIL_OVERRIDE = "persist.dbg.volte_avail_ovr";
59    public static final int PROPERTY_DBG_VOLTE_AVAIL_OVERRIDE_DEFAULT = 0;
60    public static final String PROPERTY_DBG_VT_AVAIL_OVERRIDE = "persist.dbg.vt_avail_ovr";
61    public static final int PROPERTY_DBG_VT_AVAIL_OVERRIDE_DEFAULT = 0;
62    public static final String PROPERTY_DBG_WFC_AVAIL_OVERRIDE = "persist.dbg.wfc_avail_ovr";
63    public static final int PROPERTY_DBG_WFC_AVAIL_OVERRIDE_DEFAULT = 0;
64
65    /**
66     * For accessing the IMS related service.
67     * Internal use only.
68     * @hide
69     */
70    private static final String IMS_SERVICE = "ims";
71
72    /**
73     * The result code to be sent back with the incoming call {@link PendingIntent}.
74     * @see #open(PendingIntent, ImsConnectionStateListener)
75     */
76    public static final int INCOMING_CALL_RESULT_CODE = 101;
77
78    /**
79     * Key to retrieve the call ID from an incoming call intent.
80     * @see #open(PendingIntent, ImsConnectionStateListener)
81     */
82    public static final String EXTRA_CALL_ID = "android:imsCallID";
83
84    /**
85     * Action to broadcast when ImsService is up.
86     * Internal use only.
87     * @hide
88     */
89    public static final String ACTION_IMS_SERVICE_UP =
90            "com.android.ims.IMS_SERVICE_UP";
91
92    /**
93     * Action to broadcast when ImsService is down.
94     * Internal use only.
95     * @hide
96     */
97    public static final String ACTION_IMS_SERVICE_DOWN =
98            "com.android.ims.IMS_SERVICE_DOWN";
99
100    /**
101     * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents.
102     * A long value; the phone ID corresponding to the IMS service coming up or down.
103     * Internal use only.
104     * @hide
105     */
106    public static final String EXTRA_PHONE_ID = "android:phone_id";
107
108    /**
109     * Action for the incoming call intent for the Phone app.
110     * Internal use only.
111     * @hide
112     */
113    public static final String ACTION_IMS_INCOMING_CALL =
114            "com.android.ims.IMS_INCOMING_CALL";
115
116    /**
117     * Part of the ACTION_IMS_INCOMING_CALL intents.
118     * An integer value; service identifier obtained from {@link ImsManager#open}.
119     * Internal use only.
120     * @hide
121     */
122    public static final String EXTRA_SERVICE_ID = "android:imsServiceId";
123
124    /**
125     * Part of the ACTION_IMS_INCOMING_CALL intents.
126     * An boolean value; Flag to indicate that the incoming call is a normal call or call for USSD.
127     * The value "true" indicates that the incoming call is for USSD.
128     * Internal use only.
129     * @hide
130     */
131    public static final String EXTRA_USSD = "android:ussd";
132
133    private static final String TAG = "ImsManager";
134    private static final boolean DBG = true;
135
136    private static HashMap<Integer, ImsManager> sImsManagerInstances =
137            new HashMap<Integer, ImsManager>();
138
139    private Context mContext;
140    private int mPhoneId;
141    private IImsService mImsService = null;
142    private ImsServiceDeathRecipient mDeathRecipient = new ImsServiceDeathRecipient();
143    // Ut interface for the supplementary service configuration
144    private ImsUt mUt = null;
145    // Interface to get/set ims config items
146    private ImsConfig mConfig = null;
147
148    // ECBM interface
149    private ImsEcbm mEcbm = null;
150
151    /**
152     * Gets a manager instance.
153     *
154     * @param context application context for creating the manager object
155     * @param phoneId the phone ID for the IMS Service
156     * @return the manager instance corresponding to the phoneId
157     */
158    public static ImsManager getInstance(Context context, int phoneId) {
159        synchronized (sImsManagerInstances) {
160            if (sImsManagerInstances.containsKey(phoneId))
161                return sImsManagerInstances.get(phoneId);
162
163            ImsManager mgr = new ImsManager(context, phoneId);
164            sImsManagerInstances.put(phoneId, mgr);
165
166            return mgr;
167        }
168    }
169
170    /**
171     * Returns the user configuration of Enhanced 4G LTE Mode setting
172     */
173    public static boolean isEnhanced4gLteModeSettingEnabledByUser(Context context) {
174        int enabled = android.provider.Settings.Global.getInt(
175                    context.getContentResolver(),
176                    android.provider.Settings.Global.ENHANCED_4G_MODE_ENABLED, ImsConfig.FeatureValueConstants.ON);
177        return (enabled == 1) ? true : false;
178    }
179
180    /**
181     * Change persistent Enhanced 4G LTE Mode setting
182     */
183    public static void setEnhanced4gLteModeSetting(Context context, boolean enabled) {
184        int value = enabled ? 1 : 0;
185        android.provider.Settings.Global.putInt(
186                context.getContentResolver(),
187                android.provider.Settings.Global.ENHANCED_4G_MODE_ENABLED, value);
188
189        if (isNonTtyOrTtyOnVolteEnabled(context)) {
190            ImsManager imsManager = ImsManager.getInstance(context,
191                    SubscriptionManager.getDefaultVoicePhoneId());
192            if (imsManager != null) {
193                try {
194                    imsManager.setAdvanced4GMode(enabled);
195                } catch (ImsException ie) {
196                    // do nothing
197                }
198            }
199        }
200    }
201
202    /**
203     * Indicates whether the call is non-TTY or if TTY - whether TTY on VoLTE is
204     * supported.
205     */
206    public static boolean isNonTtyOrTtyOnVolteEnabled(Context context) {
207        if (context.getResources().getBoolean(
208                com.android.internal.R.bool.config_carrier_volte_tty_supported)) {
209            return true;
210        }
211
212        return Settings.Secure.getInt(context.getContentResolver(),
213                Settings.Secure.PREFERRED_TTY_MODE, TelecomManager.TTY_MODE_OFF)
214                == TelecomManager.TTY_MODE_OFF;
215    }
216
217    /**
218     * Returns a platform configuration for VoLTE which may override the user setting.
219     */
220    public static boolean isVolteEnabledByPlatform(Context context) {
221        if (SystemProperties.getInt(PROPERTY_DBG_VOLTE_AVAIL_OVERRIDE,
222                PROPERTY_DBG_VOLTE_AVAIL_OVERRIDE_DEFAULT) == 1) {
223            return true;
224        }
225
226        boolean disabledByGlobalSetting = android.provider.Settings.Global.getInt(
227                context.getContentResolver(),
228                android.provider.Settings.Global.VOLTE_FEATURE_DISABLED, 0) == 1;
229
230        return context.getResources().getBoolean(
231                com.android.internal.R.bool.config_device_volte_available) && context.getResources()
232                .getBoolean(com.android.internal.R.bool.config_carrier_volte_available)
233                && !disabledByGlobalSetting;
234    }
235
236    /*
237     * Indicates whether VoLTE is provisioned on device
238     */
239    public static boolean isVolteProvisionedOnDevice(Context context) {
240        boolean isProvisioned = true;
241        if (context.getResources().getBoolean(
242                        com.android.internal.R.bool.config_carrier_volte_provisioned)) {
243            isProvisioned = false; // disable on any error
244            ImsManager mgr = ImsManager.getInstance(context,
245                    SubscriptionManager.getDefaultVoiceSubId());
246            if (mgr != null) {
247                try {
248                    ImsConfig config = mgr.getConfigInterface();
249                    if (config != null) {
250                        isProvisioned = config.getVolteProvisioned();
251                    }
252                } catch (ImsException ie) {
253                    // do nothing
254                }
255            }
256        }
257
258        return isProvisioned;
259    }
260
261    /**
262     * Returns a platform configuration for VT which may override the user setting.
263     *
264     * Note: VT presumes that VoLTE is enabled (these are configuration settings
265     * which must be done correctly).
266     */
267    public static boolean isVtEnabledByPlatform(Context context) {
268        if (SystemProperties.getInt(PROPERTY_DBG_VT_AVAIL_OVERRIDE,
269                PROPERTY_DBG_VT_AVAIL_OVERRIDE_DEFAULT) == 1) {
270            return true;
271        }
272
273        return
274                context.getResources().getBoolean(
275                        com.android.internal.R.bool.config_device_vt_available) &&
276                context.getResources().getBoolean(
277                        com.android.internal.R.bool.config_carrier_vt_available);
278    }
279
280    /**
281     * Returns the user configuration of WFC setting
282     */
283    public static boolean isWfcEnabledByUser(Context context) {
284        int enabled = android.provider.Settings.Global.getInt(context.getContentResolver(),
285                android.provider.Settings.Global.WFC_IMS_ENABLED,
286                ImsConfig.FeatureValueConstants.ON);
287        return (enabled == 1) ? true : false;
288    }
289
290    /**
291     * Change persistent WFC enabled setting
292     */
293    public static void setWfcSetting(Context context, boolean enabled) {
294        int value = enabled ? 1 : 0;
295        android.provider.Settings.Global.putInt(context.getContentResolver(),
296                android.provider.Settings.Global.WFC_IMS_ENABLED, value);
297
298        ImsManager imsManager = ImsManager.getInstance(context,
299                SubscriptionManager.getDefaultVoicePhoneId());
300        if (imsManager != null) {
301            try {
302                ImsConfig config = imsManager.getConfigInterface();
303                // FIXME: replace NETWORK_TYPE_LTE with NETWORK_TYPE_IWLAN
304                // when available
305                config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI,
306                        TelephonyManager.NETWORK_TYPE_LTE,
307                        enabled ? ImsConfig.FeatureValueConstants.ON
308                                : ImsConfig.FeatureValueConstants.OFF, null);
309
310                if (enabled) {
311                    imsManager.turnOnIms();
312                } else if (context.getResources().getBoolean(
313                        com.android.internal.R.bool.imsServiceAllowTurnOff)
314                        && (!isVolteEnabledByPlatform(context)
315                        || !isEnhanced4gLteModeSettingEnabledByUser(context))) {
316                    log("setWfcSetting() : imsServiceAllowTurnOff -> turnOffIms");
317                    imsManager.turnOffIms();
318                }
319            } catch (ImsException e) {
320                loge("setWfcSetting(): " + e);
321            }
322        }
323    }
324
325    /**
326     * Returns the user configuration of WFC modem setting
327     */
328    public static int getWfcMode(Context context) {
329        int setting = android.provider.Settings.Global.getInt(context.getContentResolver(),
330                android.provider.Settings.Global.WFC_IMS_MODE,
331                ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED);
332        if (DBG) log("getWfcMode - setting=" + setting);
333        return setting;
334    }
335
336    /**
337     * Returns the user configuration of WFC modem setting
338     */
339    public static void setWfcMode(Context context, int wfcMode) {
340        if (DBG) log("setWfcMode - setting=" + wfcMode);
341        android.provider.Settings.Global.putInt(context.getContentResolver(),
342                android.provider.Settings.Global.WFC_IMS_MODE, wfcMode);
343
344        final ImsManager imsManager = ImsManager.getInstance(context,
345                SubscriptionManager.getDefaultVoicePhoneId());
346        if (imsManager != null) {
347            final int value = wfcMode;
348            QueuedWork.singleThreadExecutor().submit(new Runnable() {
349                public void run() {
350                    try {
351                        imsManager.getConfigInterface().setProvisionedValue(
352                                ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE,
353                                value);
354                    } catch (ImsException e) {
355                        // do nothing
356                    }
357                }
358            });
359        }
360    }
361
362    /**
363     * Returns the user configuration of WFC roaming setting
364     */
365    public static boolean isWfcRoamingEnabledByUser(Context context) {
366        int enabled = android.provider.Settings.Global.getInt(context.getContentResolver(),
367                android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED,
368                ImsConfig.FeatureValueConstants.ON);
369        return (enabled == 1) ? true : false;
370    }
371
372    /**
373     * Change persistent WFC roaming enabled setting
374     */
375    public static void setWfcRoamingSetting(Context context, boolean enabled) {
376        final int value = enabled
377                ? ImsConfig.FeatureValueConstants.ON
378                : ImsConfig.FeatureValueConstants.OFF;
379        android.provider.Settings.Global.putInt(context.getContentResolver(),
380                android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED, value);
381
382        final ImsManager imsManager = ImsManager.getInstance(context,
383                SubscriptionManager.getDefaultVoicePhoneId());
384        if (imsManager != null) {
385            QueuedWork.singleThreadExecutor().submit(new Runnable() {
386                public void run() {
387                    try {
388                        imsManager.getConfigInterface().setProvisionedValue(
389                                ImsConfig.ConfigConstants.VOICE_OVER_WIFI_ROAMING,
390                                value);
391                    } catch (ImsException e) {
392                        // do nothing
393                    }
394                }
395            });
396        }
397    }
398
399    /**
400     * Returns a platform configuration for WFC which may override the user
401     * setting. Note: WFC presumes that VoLTE is enabled (these are
402     * configuration settings which must be done correctly).
403     */
404    public static boolean isWfcEnabledByPlatform(Context context) {
405        if (SystemProperties.getInt(PROPERTY_DBG_WFC_AVAIL_OVERRIDE,
406                PROPERTY_DBG_WFC_AVAIL_OVERRIDE_DEFAULT) == 1) {
407            return true;
408        }
409
410        return
411               context.getResources().getBoolean(
412                       com.android.internal.R.bool.config_device_wfc_ims_available) &&
413               context.getResources().getBoolean(
414                       com.android.internal.R.bool.config_carrier_wfc_ims_available);
415    }
416
417    private ImsManager(Context context, int phoneId) {
418        mContext = context;
419        mPhoneId = phoneId;
420        createImsService(true);
421    }
422
423    /**
424     * Opens the IMS service for making calls and/or receiving generic IMS calls.
425     * The caller may make subsquent calls through {@link #makeCall}.
426     * The IMS service will register the device to the operator's network with the credentials
427     * (from ISIM) periodically in order to receive calls from the operator's network.
428     * When the IMS service receives a new call, it will send out an intent with
429     * the provided action string.
430     * The intent contains a call ID extra {@link getCallId} and it can be used to take a call.
431     *
432     * @param serviceClass a service class specified in {@link ImsServiceClass}
433     *      For VoLTE service, it MUST be a {@link ImsServiceClass#MMTEL}.
434     * @param incomingCallPendingIntent When an incoming call is received,
435     *        the IMS service will call {@link PendingIntent#send(Context, int, Intent)} to
436     *        send back the intent to the caller with {@link #INCOMING_CALL_RESULT_CODE}
437     *        as the result code and the intent to fill in the call ID; It cannot be null
438     * @param listener To listen to IMS registration events; It cannot be null
439     * @return identifier (greater than 0) for the specified service
440     * @throws NullPointerException if {@code incomingCallPendingIntent}
441     *      or {@code listener} is null
442     * @throws ImsException if calling the IMS service results in an error
443     * @see #getCallId
444     * @see #getServiceId
445     */
446    public int open(int serviceClass, PendingIntent incomingCallPendingIntent,
447            ImsConnectionStateListener listener) throws ImsException {
448        checkAndThrowExceptionIfServiceUnavailable();
449
450        if (incomingCallPendingIntent == null) {
451            throw new NullPointerException("incomingCallPendingIntent can't be null");
452        }
453
454        if (listener == null) {
455            throw new NullPointerException("listener can't be null");
456        }
457
458        int result = 0;
459
460        try {
461            result = mImsService.open(mPhoneId, serviceClass, incomingCallPendingIntent,
462                    createRegistrationListenerProxy(serviceClass, listener));
463        } catch (RemoteException e) {
464            throw new ImsException("open()", e,
465                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
466        }
467
468        if (result <= 0) {
469            // If the return value is a minus value,
470            // it means that an error occurred in the service.
471            // So, it needs to convert to the reason code specified in ImsReasonInfo.
472            throw new ImsException("open()", (result * (-1)));
473        }
474
475        return result;
476    }
477
478    /**
479     * Closes the specified service ({@link ImsServiceClass}) not to make/receive calls.
480     * All the resources that were allocated to the service are also released.
481     *
482     * @param serviceId a service id to be closed which is obtained from {@link ImsManager#open}
483     * @throws ImsException if calling the IMS service results in an error
484     */
485    public void close(int serviceId) throws ImsException {
486        checkAndThrowExceptionIfServiceUnavailable();
487
488        try {
489            mImsService.close(serviceId);
490        } catch (RemoteException e) {
491            throw new ImsException("close()", e,
492                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
493        } finally {
494            mUt = null;
495            mConfig = null;
496            mEcbm = null;
497        }
498    }
499
500    /**
501     * Gets the configuration interface to provision / withdraw the supplementary service settings.
502     *
503     * @param serviceId a service id which is obtained from {@link ImsManager#open}
504     * @return the Ut interface instance
505     * @throws ImsException if getting the Ut interface results in an error
506     */
507    public ImsUtInterface getSupplementaryServiceConfiguration(int serviceId)
508            throws ImsException {
509        // FIXME: manage the multiple Ut interfaces based on the service id
510        if (mUt == null) {
511            checkAndThrowExceptionIfServiceUnavailable();
512
513            try {
514                IImsUt iUt = mImsService.getUtInterface(serviceId);
515
516                if (iUt == null) {
517                    throw new ImsException("getSupplementaryServiceConfiguration()",
518                            ImsReasonInfo.CODE_UT_NOT_SUPPORTED);
519                }
520
521                mUt = new ImsUt(iUt);
522            } catch (RemoteException e) {
523                throw new ImsException("getSupplementaryServiceConfiguration()", e,
524                        ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
525            }
526        }
527
528        return mUt;
529    }
530
531    /**
532     * Checks if the IMS service has successfully registered to the IMS network
533     * with the specified service & call type.
534     *
535     * @param serviceId a service id which is obtained from {@link ImsManager#open}
536     * @param serviceType a service type that is specified in {@link ImsCallProfile}
537     *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
538     *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
539     * @param callType a call type that is specified in {@link ImsCallProfile}
540     *        {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO}
541     *        {@link ImsCallProfile#CALL_TYPE_VOICE}
542     *        {@link ImsCallProfile#CALL_TYPE_VT}
543     *        {@link ImsCallProfile#CALL_TYPE_VS}
544     * @return true if the specified service id is connected to the IMS network;
545     *        false otherwise
546     * @throws ImsException if calling the IMS service results in an error
547     */
548    public boolean isConnected(int serviceId, int serviceType, int callType)
549            throws ImsException {
550        checkAndThrowExceptionIfServiceUnavailable();
551
552        try {
553            return mImsService.isConnected(serviceId, serviceType, callType);
554        } catch (RemoteException e) {
555            throw new ImsException("isServiceConnected()", e,
556                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
557        }
558    }
559
560    /**
561     * Checks if the specified IMS service is opend.
562     *
563     * @param serviceId a service id which is obtained from {@link ImsManager#open}
564     * @return true if the specified service id is opened; false otherwise
565     * @throws ImsException if calling the IMS service results in an error
566     */
567    public boolean isOpened(int serviceId) throws ImsException {
568        checkAndThrowExceptionIfServiceUnavailable();
569
570        try {
571            return mImsService.isOpened(serviceId);
572        } catch (RemoteException e) {
573            throw new ImsException("isOpened()", e,
574                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
575        }
576    }
577
578    /**
579     * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
580     *
581     * @param serviceId a service id which is obtained from {@link ImsManager#open}
582     * @param serviceType a service type that is specified in {@link ImsCallProfile}
583     *        {@link ImsCallProfile#SERVICE_TYPE_NONE}
584     *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
585     *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
586     * @param callType a call type that is specified in {@link ImsCallProfile}
587     *        {@link ImsCallProfile#CALL_TYPE_VOICE}
588     *        {@link ImsCallProfile#CALL_TYPE_VT}
589     *        {@link ImsCallProfile#CALL_TYPE_VT_TX}
590     *        {@link ImsCallProfile#CALL_TYPE_VT_RX}
591     *        {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
592     *        {@link ImsCallProfile#CALL_TYPE_VS}
593     *        {@link ImsCallProfile#CALL_TYPE_VS_TX}
594     *        {@link ImsCallProfile#CALL_TYPE_VS_RX}
595     * @return a {@link ImsCallProfile} object
596     * @throws ImsException if calling the IMS service results in an error
597     */
598    public ImsCallProfile createCallProfile(int serviceId,
599            int serviceType, int callType) throws ImsException {
600        checkAndThrowExceptionIfServiceUnavailable();
601
602        try {
603            return mImsService.createCallProfile(serviceId, serviceType, callType);
604        } catch (RemoteException e) {
605            throw new ImsException("createCallProfile()", e,
606                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
607        }
608    }
609
610    /**
611     * Creates a {@link ImsCall} to make a call.
612     *
613     * @param serviceId a service id which is obtained from {@link ImsManager#open}
614     * @param profile a call profile to make the call
615     *      (it contains service type, call type, media information, etc.)
616     * @param participants participants to invite the conference call
617     * @param listener listen to the call events from {@link ImsCall}
618     * @return a {@link ImsCall} object
619     * @throws ImsException if calling the IMS service results in an error
620     */
621    public ImsCall makeCall(int serviceId, ImsCallProfile profile, String[] callees,
622            ImsCall.Listener listener) throws ImsException {
623        if (DBG) {
624            log("makeCall :: serviceId=" + serviceId
625                    + ", profile=" + profile + ", callees=" + callees);
626        }
627
628        checkAndThrowExceptionIfServiceUnavailable();
629
630        ImsCall call = new ImsCall(mContext, profile);
631
632        call.setListener(listener);
633        ImsCallSession session = createCallSession(serviceId, profile);
634
635        if ((callees != null) && (callees.length == 1)) {
636            call.start(session, callees[0]);
637        } else {
638            call.start(session, callees);
639        }
640
641        return call;
642    }
643
644    /**
645     * Creates a {@link ImsCall} to take an incoming call.
646     *
647     * @param serviceId a service id which is obtained from {@link ImsManager#open}
648     * @param incomingCallIntent the incoming call broadcast intent
649     * @param listener to listen to the call events from {@link ImsCall}
650     * @return a {@link ImsCall} object
651     * @throws ImsException if calling the IMS service results in an error
652     */
653    public ImsCall takeCall(int serviceId, Intent incomingCallIntent,
654            ImsCall.Listener listener) throws ImsException {
655        if (DBG) {
656            log("takeCall :: serviceId=" + serviceId
657                    + ", incomingCall=" + incomingCallIntent);
658        }
659
660        checkAndThrowExceptionIfServiceUnavailable();
661
662        if (incomingCallIntent == null) {
663            throw new ImsException("Can't retrieve session with null intent",
664                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
665        }
666
667        int incomingServiceId = getServiceId(incomingCallIntent);
668
669        if (serviceId != incomingServiceId) {
670            throw new ImsException("Service id is mismatched in the incoming call intent",
671                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
672        }
673
674        String callId = getCallId(incomingCallIntent);
675
676        if (callId == null) {
677            throw new ImsException("Call ID missing in the incoming call intent",
678                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
679        }
680
681        try {
682            IImsCallSession session = mImsService.getPendingCallSession(serviceId, callId);
683
684            if (session == null) {
685                throw new ImsException("No pending session for the call",
686                        ImsReasonInfo.CODE_LOCAL_NO_PENDING_CALL);
687            }
688
689            ImsCall call = new ImsCall(mContext, session.getCallProfile());
690
691            call.attachSession(new ImsCallSession(session));
692            call.setListener(listener);
693
694            return call;
695        } catch (Throwable t) {
696            throw new ImsException("takeCall()", t, ImsReasonInfo.CODE_UNSPECIFIED);
697        }
698    }
699
700    /**
701     * Gets the config interface to get/set service/capability parameters.
702     *
703     * @return the ImsConfig instance.
704     * @throws ImsException if getting the setting interface results in an error.
705     */
706    public ImsConfig getConfigInterface() throws ImsException {
707
708        if (mConfig == null) {
709            checkAndThrowExceptionIfServiceUnavailable();
710
711            try {
712                IImsConfig config = mImsService.getConfigInterface(mPhoneId);
713                if (config == null) {
714                    throw new ImsException("getConfigInterface()",
715                            ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
716                }
717                mConfig = new ImsConfig(config, mContext);
718            } catch (RemoteException e) {
719                throw new ImsException("getConfigInterface()", e,
720                        ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
721            }
722        }
723        if (DBG) log("getConfigInterface(), mConfig= " + mConfig);
724        return mConfig;
725    }
726
727    public void setUiTTYMode(Context context, int serviceId, int uiTtyMode, Message onComplete)
728            throws ImsException {
729
730        checkAndThrowExceptionIfServiceUnavailable();
731
732        try {
733            mImsService.setUiTTYMode(serviceId, uiTtyMode, onComplete);
734        } catch (RemoteException e) {
735            throw new ImsException("setTTYMode()", e,
736                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
737        }
738
739        if (!context.getResources().getBoolean(
740                com.android.internal.R.bool.config_carrier_volte_tty_supported)) {
741            setAdvanced4GMode((uiTtyMode == TelecomManager.TTY_MODE_OFF) &&
742                    isEnhanced4gLteModeSettingEnabledByUser(context));
743        }
744    }
745
746    /**
747     * Gets the call ID from the specified incoming call broadcast intent.
748     *
749     * @param incomingCallIntent the incoming call broadcast intent
750     * @return the call ID or null if the intent does not contain it
751     */
752    private static String getCallId(Intent incomingCallIntent) {
753        if (incomingCallIntent == null) {
754            return null;
755        }
756
757        return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
758    }
759
760    /**
761     * Gets the service type from the specified incoming call broadcast intent.
762     *
763     * @param incomingCallIntent the incoming call broadcast intent
764     * @return the service identifier or -1 if the intent does not contain it
765     */
766    private static int getServiceId(Intent incomingCallIntent) {
767        if (incomingCallIntent == null) {
768            return (-1);
769        }
770
771        return incomingCallIntent.getIntExtra(EXTRA_SERVICE_ID, -1);
772    }
773
774    /**
775     * Binds the IMS service only if the service is not created.
776     */
777    private void checkAndThrowExceptionIfServiceUnavailable()
778            throws ImsException {
779        if (mImsService == null) {
780            createImsService(true);
781
782            if (mImsService == null) {
783                throw new ImsException("Service is unavailable",
784                        ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
785            }
786        }
787    }
788
789    private static String getImsServiceName(int phoneId) {
790        // TODO: MSIM implementation needs to decide on service name as a function of phoneId
791        return IMS_SERVICE;
792    }
793
794    /**
795     * Binds the IMS service to make/receive the call.
796     */
797    private void createImsService(boolean checkService) {
798        if (checkService) {
799            IBinder binder = ServiceManager.checkService(getImsServiceName(mPhoneId));
800
801            if (binder == null) {
802                return;
803            }
804        }
805
806        IBinder b = ServiceManager.getService(getImsServiceName(mPhoneId));
807
808        if (b != null) {
809            try {
810                b.linkToDeath(mDeathRecipient, 0);
811            } catch (RemoteException e) {
812            }
813        }
814
815        mImsService = IImsService.Stub.asInterface(b);
816    }
817
818    /**
819     * Creates a {@link ImsCallSession} with the specified call profile.
820     * Use other methods, if applicable, instead of interacting with
821     * {@link ImsCallSession} directly.
822     *
823     * @param serviceId a service id which is obtained from {@link ImsManager#open}
824     * @param profile a call profile to make the call
825     */
826    private ImsCallSession createCallSession(int serviceId,
827            ImsCallProfile profile) throws ImsException {
828        try {
829            return new ImsCallSession(mImsService.createCallSession(serviceId, profile, null));
830        } catch (RemoteException e) {
831            return null;
832        }
833    }
834
835    private ImsRegistrationListenerProxy createRegistrationListenerProxy(int serviceClass,
836            ImsConnectionStateListener listener) {
837        ImsRegistrationListenerProxy proxy =
838                new ImsRegistrationListenerProxy(serviceClass, listener);
839        return proxy;
840    }
841
842    private static void log(String s) {
843        Rlog.d(TAG, s);
844    }
845
846    private static void loge(String s) {
847        Rlog.e(TAG, s);
848    }
849
850    private static void loge(String s, Throwable t) {
851        Rlog.e(TAG, s, t);
852    }
853
854    /**
855     * Used for turning on IMS.if its off already
856     */
857    private void turnOnIms() throws ImsException {
858        checkAndThrowExceptionIfServiceUnavailable();
859
860        try {
861            mImsService.turnOnIms(mPhoneId);
862        } catch (RemoteException e) {
863            throw new ImsException("turnOnIms() ", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
864        }
865    }
866
867    private void setAdvanced4GMode(boolean turnOn) throws ImsException {
868        checkAndThrowExceptionIfServiceUnavailable();
869
870        ImsConfig config = getConfigInterface();
871        if (config != null) {
872            config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE,
873                    TelephonyManager.NETWORK_TYPE_LTE, turnOn ? 1 : 0, null);
874            if (isVtEnabledByPlatform(mContext)) {
875                // TODO: once VT is available on platform replace the '1' with the current
876                // user configuration of VT.
877                config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE,
878                        TelephonyManager.NETWORK_TYPE_LTE, turnOn ? 1 : 0, null);
879            }
880        }
881
882        if (turnOn) {
883            turnOnIms();
884        } else if (mContext.getResources().getBoolean(
885                com.android.internal.R.bool.imsServiceAllowTurnOff)
886                && (!isWfcEnabledByPlatform(mContext)
887                || !isWfcEnabledByUser(mContext))) {
888            log("setAdvanced4GMode() : imsServiceAllowTurnOff -> turnOffIms");
889            turnOffIms();
890        }
891    }
892
893    /**
894     * Used for turning off IMS completely in order to make the device CSFB'ed.
895     * Once turned off, all calls will be over CS.
896     */
897    private void turnOffIms() throws ImsException {
898        checkAndThrowExceptionIfServiceUnavailable();
899
900        try {
901            mImsService.turnOffIms(mPhoneId);
902        } catch (RemoteException e) {
903            throw new ImsException("turnOffIms() ", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
904        }
905    }
906
907    /**
908     * Death recipient class for monitoring IMS service.
909     */
910    private class ImsServiceDeathRecipient implements IBinder.DeathRecipient {
911        @Override
912        public void binderDied() {
913            mImsService = null;
914            mUt = null;
915            mConfig = null;
916            mEcbm = null;
917
918            if (mContext != null) {
919                Intent intent = new Intent(ACTION_IMS_SERVICE_DOWN);
920                intent.putExtra(EXTRA_PHONE_ID, mPhoneId);
921                mContext.sendBroadcast(new Intent(intent));
922            }
923        }
924    }
925
926    /**
927     * Adapter class for {@link IImsRegistrationListener}.
928     */
929    private class ImsRegistrationListenerProxy extends IImsRegistrationListener.Stub {
930        private int mServiceClass;
931        private ImsConnectionStateListener mListener;
932
933        public ImsRegistrationListenerProxy(int serviceClass,
934                ImsConnectionStateListener listener) {
935            mServiceClass = serviceClass;
936            mListener = listener;
937        }
938
939        public boolean isSameProxy(int serviceClass) {
940            return (mServiceClass == serviceClass);
941        }
942
943        @Override
944        public void registrationConnected() {
945            if (DBG) {
946                log("registrationConnected ::");
947            }
948
949            if (mListener != null) {
950                mListener.onImsConnected();
951            }
952        }
953
954        @Override
955        public void registrationDisconnected() {
956            if (DBG) {
957                log("registrationDisconnected ::");
958            }
959
960            if (mListener != null) {
961                mListener.onImsDisconnected();
962            }
963        }
964
965        @Override
966        public void registrationResumed() {
967            if (DBG) {
968                log("registrationResumed ::");
969            }
970
971            if (mListener != null) {
972                mListener.onImsResumed();
973            }
974        }
975
976        @Override
977        public void registrationSuspended() {
978            if (DBG) {
979                log("registrationSuspended ::");
980            }
981
982            if (mListener != null) {
983                mListener.onImsSuspended();
984            }
985        }
986
987        @Override
988        public void registrationServiceCapabilityChanged(int serviceClass, int event) {
989            log("registrationServiceCapabilityChanged :: serviceClass=" +
990                    serviceClass + ", event=" + event);
991
992            if (mListener != null) {
993                mListener.onImsConnected();
994            }
995        }
996
997        @Override
998        public void registrationFeatureCapabilityChanged(int serviceClass,
999                int[] enabledFeatures, int[] disabledFeatures) {
1000            log("registrationFeatureCapabilityChanged :: serviceClass=" +
1001                    serviceClass);
1002            if (mListener != null) {
1003                mListener.onFeatureCapabilityChanged(serviceClass,
1004                        enabledFeatures, disabledFeatures);
1005            }
1006        }
1007
1008    }
1009    /**
1010     * Gets the ECBM interface to request ECBM exit.
1011     *
1012     * @param serviceId a service id which is obtained from {@link ImsManager#open}
1013     * @return the ECBM interface instance
1014     * @throws ImsException if getting the ECBM interface results in an error
1015     */
1016    public ImsEcbm getEcbmInterface(int serviceId) throws ImsException {
1017        if (mEcbm == null) {
1018            checkAndThrowExceptionIfServiceUnavailable();
1019
1020            try {
1021                IImsEcbm iEcbm = mImsService.getEcbmInterface(serviceId);
1022
1023                if (iEcbm == null) {
1024                    throw new ImsException("getEcbmInterface()",
1025                            ImsReasonInfo.CODE_ECBM_NOT_SUPPORTED);
1026                }
1027                mEcbm = new ImsEcbm(iEcbm);
1028            } catch (RemoteException e) {
1029                throw new ImsException("getEcbmInterface()", e,
1030                        ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
1031            }
1032        }
1033        return mEcbm;
1034    }
1035}
1036