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