Add ImsManager.updateImsServiceConfig() function
[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 WFC setting
304      */
305     public static boolean isWfcEnabledByUser(Context context) {
306         int enabled = android.provider.Settings.Global.getInt(context.getContentResolver(),
307                 android.provider.Settings.Global.WFC_IMS_ENABLED,
308                 ImsConfig.FeatureValueConstants.OFF);
309         return (enabled == 1) ? true : false;
310     }
311
312     /**
313      * Change persistent WFC enabled setting
314      */
315     public static void setWfcSetting(Context context, boolean enabled) {
316         int value = enabled ? 1 : 0;
317         android.provider.Settings.Global.putInt(context.getContentResolver(),
318                 android.provider.Settings.Global.WFC_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_VOICE_OVER_WIFI,
326                         TelephonyManager.NETWORK_TYPE_IWLAN,
327                         enabled ? ImsConfig.FeatureValueConstants.ON
328                                 : ImsConfig.FeatureValueConstants.OFF, null);
329
330                 if (enabled) {
331                     imsManager.turnOnIms();
332                 } else if (getBooleanCarrierConfig(context,
333                         CarrierConfigManager.KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL)
334                         && (!isVolteEnabledByPlatform(context)
335                         || !isEnhanced4gLteModeSettingEnabledByUser(context))) {
336                     log("setWfcSetting() : imsServiceAllowTurnOff -> turnOffIms");
337                     imsManager.turnOffIms();
338                 }
339
340                 // Force IMS to register over LTE when turning off WFC
341                 setWfcModeInternal(context, enabled
342                         ? getWfcMode(context)
343                         : ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED);
344             } catch (ImsException e) {
345                 loge("setWfcSetting(): " + e);
346             }
347         }
348     }
349
350     /**
351      * Returns the user configuration of WFC modem setting
352      */
353     public static int getWfcMode(Context context) {
354         int setting = android.provider.Settings.Global.getInt(context.getContentResolver(),
355                 android.provider.Settings.Global.WFC_IMS_MODE,
356                 ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED);
357         if (DBG) log("getWfcMode - setting=" + setting);
358         return setting;
359     }
360
361     /**
362      * Returns the user configuration of WFC modem setting
363      */
364     public static void setWfcMode(Context context, int wfcMode) {
365         if (DBG) log("setWfcMode - setting=" + wfcMode);
366         android.provider.Settings.Global.putInt(context.getContentResolver(),
367                 android.provider.Settings.Global.WFC_IMS_MODE, wfcMode);
368
369         setWfcModeInternal(context, wfcMode);
370     }
371
372     private static void setWfcModeInternal(Context context, int wfcMode) {
373         final ImsManager imsManager = ImsManager.getInstance(context,
374                 SubscriptionManager.getDefaultVoicePhoneId());
375         if (imsManager != null) {
376             final int value = wfcMode;
377             QueuedWork.singleThreadExecutor().submit(new Runnable() {
378                 public void run() {
379                     try {
380                         imsManager.getConfigInterface().setProvisionedValue(
381                                 ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE,
382                                 value);
383                     } catch (ImsException e) {
384                         // do nothing
385                     }
386                 }
387             });
388         }
389     }
390
391     /**
392      * Returns the user configuration of WFC roaming setting
393      */
394     public static boolean isWfcRoamingEnabledByUser(Context context) {
395         int enabled = android.provider.Settings.Global.getInt(context.getContentResolver(),
396                 android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED,
397                 ImsConfig.FeatureValueConstants.OFF);
398         return (enabled == 1) ? true : false;
399     }
400
401     /**
402      * Change persistent WFC roaming enabled setting
403      */
404     public static void setWfcRoamingSetting(Context context, boolean enabled) {
405         android.provider.Settings.Global.putInt(context.getContentResolver(),
406                 android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED,
407                 enabled
408                         ? ImsConfig.FeatureValueConstants.ON
409                         : ImsConfig.FeatureValueConstants.OFF);
410
411         setWfcRoamingSettingInternal(context, enabled);
412     }
413
414     private static void setWfcRoamingSettingInternal(Context context, boolean enabled) {
415         final ImsManager imsManager = ImsManager.getInstance(context,
416                 SubscriptionManager.getDefaultVoicePhoneId());
417         if (imsManager != null) {
418             final int value = enabled
419                     ? ImsConfig.FeatureValueConstants.ON
420                     : ImsConfig.FeatureValueConstants.OFF;
421             QueuedWork.singleThreadExecutor().submit(new Runnable() {
422                 public void run() {
423                     try {
424                         imsManager.getConfigInterface().setProvisionedValue(
425                                 ImsConfig.ConfigConstants.VOICE_OVER_WIFI_ROAMING,
426                                 value);
427                     } catch (ImsException e) {
428                         // do nothing
429                     }
430                 }
431             });
432         }
433     }
434
435     /**
436      * Returns a platform configuration for WFC which may override the user
437      * setting. Note: WFC presumes that VoLTE is enabled (these are
438      * configuration settings which must be done correctly).
439      */
440     public static boolean isWfcEnabledByPlatform(Context context) {
441         if (SystemProperties.getInt(PROPERTY_DBG_WFC_AVAIL_OVERRIDE,
442                 PROPERTY_DBG_WFC_AVAIL_OVERRIDE_DEFAULT) == 1) {
443             return true;
444         }
445
446         return
447                context.getResources().getBoolean(
448                        com.android.internal.R.bool.config_device_wfc_ims_available) &&
449                getBooleanCarrierConfig(context,
450                        CarrierConfigManager.KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL);
451     }
452
453     /**
454      * Sync carrier config and user settings with ImsConfig.
455      *
456      * @param context for the manager object
457      * @param phoneId phone id
458      * @param force update
459      */
460     public static void updateImsServiceConfig(Context context, int phoneId, boolean force) {
461         final ImsManager imsManager = ImsManager.getInstance(context, phoneId);
462         if (imsManager != null && (!imsManager.mConfigUpdated || force)) {
463             try {
464                 imsManager.updateVolteFeatureValue();
465                 imsManager.updateVideoCallFeatureValue();
466                 imsManager.updateWfcFeatureAndProvisionedValues();
467                 imsManager.mConfigUpdated = true;
468             } catch (ImsException e) {
469                 loge("updateImsServiceConfig: " + e);
470                 imsManager.mConfigUpdated = false;
471             }
472         }
473     }
474
475     private void updateVolteFeatureValue() throws ImsException {
476         boolean available = isVolteEnabledByPlatform(mContext);
477         boolean enabled = isEnhanced4gLteModeSettingEnabledByUser(mContext);
478         boolean isNonTty = isNonTtyOrTtyOnVolteEnabled(mContext);
479
480         log("updateVolteFeatureValue: available = " + available
481                 + ", enabled = " + enabled
482                 + ", nonTTY = " + isNonTty);
483
484         getConfigInterface().setFeatureValue(
485                 ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE,
486                 TelephonyManager.NETWORK_TYPE_LTE,
487                 (available && enabled && isNonTty) ?
488                         ImsConfig.FeatureValueConstants.ON :
489                         ImsConfig.FeatureValueConstants.OFF,
490                 null);
491     }
492
493     private void updateVideoCallFeatureValue() throws ImsException {
494         boolean available = isVtEnabledByPlatform(mContext);
495         SharedPreferences sharedPrefs =
496                 PreferenceManager.getDefaultSharedPreferences(mContext);
497         boolean enabled = isEnhanced4gLteModeSettingEnabledByUser(mContext) &&
498                 sharedPrefs.getBoolean(PREF_ENABLE_VIDEO_CALLING_KEY, true);
499         boolean isNonTty = Settings.Secure.getInt(mContext.getContentResolver(),
500                 Settings.Secure.PREFERRED_TTY_MODE, TelecomManager.TTY_MODE_OFF)
501                 == TelecomManager.TTY_MODE_OFF;
502
503         log("updateVideoCallFeatureValue: available = " + available
504                 + ", enabled = " + enabled
505                 + ", nonTTY = " + isNonTty);
506
507         getConfigInterface().setFeatureValue(
508                 ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE,
509                 TelephonyManager.NETWORK_TYPE_LTE,
510                 (available && enabled && isNonTty) ?
511                         ImsConfig.FeatureValueConstants.ON :
512                         ImsConfig.FeatureValueConstants.OFF,
513                 null);
514     }
515
516     private void updateWfcFeatureAndProvisionedValues() throws ImsException {
517         boolean available = isWfcEnabledByPlatform(mContext);
518         boolean enabled = isWfcEnabledByUser(mContext);
519         int mode = getWfcMode(mContext);
520         boolean roaming = isWfcRoamingEnabledByUser(mContext);
521
522         log("updateWfcFeatureAndProvisionedValues: available = " + available
523                 + ", enabled = " + enabled
524                 + ", mode = " + mode
525                 + ", roaming = " + roaming);
526
527         getConfigInterface().setFeatureValue(
528                 ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE,
529                 TelephonyManager.NETWORK_TYPE_LTE,
530                 (available && enabled) ?
531                         ImsConfig.FeatureValueConstants.ON :
532                         ImsConfig.FeatureValueConstants.OFF,
533                 null);
534
535         if (!available || !enabled) {
536             mode = ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED;
537             roaming = false;
538         }
539         setWfcModeInternal(mContext, mode);
540         setWfcRoamingSettingInternal(mContext, roaming);
541     }
542
543     private ImsManager(Context context, int phoneId) {
544         mContext = context;
545         mPhoneId = phoneId;
546         createImsService(true);
547     }
548
549     /*
550      * Returns a flag indicating whether the IMS service is available.
551      */
552     public boolean isServiceAvailable() {
553         if (mImsService != null) {
554             return true;
555         }
556
557         IBinder binder = ServiceManager.checkService(getImsServiceName(mPhoneId));
558         if (binder != null) {
559             return true;
560         }
561
562         return false;
563     }
564
565     /**
566      * Opens the IMS service for making calls and/or receiving generic IMS calls.
567      * The caller may make subsquent calls through {@link #makeCall}.
568      * The IMS service will register the device to the operator's network with the credentials
569      * (from ISIM) periodically in order to receive calls from the operator's network.
570      * When the IMS service receives a new call, it will send out an intent with
571      * the provided action string.
572      * The intent contains a call ID extra {@link getCallId} and it can be used to take a call.
573      *
574      * @param serviceClass a service class specified in {@link ImsServiceClass}
575      *      For VoLTE service, it MUST be a {@link ImsServiceClass#MMTEL}.
576      * @param incomingCallPendingIntent When an incoming call is received,
577      *        the IMS service will call {@link PendingIntent#send(Context, int, Intent)} to
578      *        send back the intent to the caller with {@link #INCOMING_CALL_RESULT_CODE}
579      *        as the result code and the intent to fill in the call ID; It cannot be null
580      * @param listener To listen to IMS registration events; It cannot be null
581      * @return identifier (greater than 0) for the specified service
582      * @throws NullPointerException if {@code incomingCallPendingIntent}
583      *      or {@code listener} is null
584      * @throws ImsException if calling the IMS service results in an error
585      * @see #getCallId
586      * @see #getServiceId
587      */
588     public int open(int serviceClass, PendingIntent incomingCallPendingIntent,
589             ImsConnectionStateListener listener) throws ImsException {
590         checkAndThrowExceptionIfServiceUnavailable();
591
592         if (incomingCallPendingIntent == null) {
593             throw new NullPointerException("incomingCallPendingIntent can't be null");
594         }
595
596         if (listener == null) {
597             throw new NullPointerException("listener can't be null");
598         }
599
600         int result = 0;
601
602         try {
603             result = mImsService.open(mPhoneId, serviceClass, incomingCallPendingIntent,
604                     createRegistrationListenerProxy(serviceClass, listener));
605         } catch (RemoteException e) {
606             throw new ImsException("open()", e,
607                     ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
608         }
609
610         if (result <= 0) {
611             // If the return value is a minus value,
612             // it means that an error occurred in the service.
613             // So, it needs to convert to the reason code specified in ImsReasonInfo.
614             throw new ImsException("open()", (result * (-1)));
615         }
616
617         return result;
618     }
619
620     /**
621      * Closes the specified service ({@link ImsServiceClass}) not to make/receive calls.
622      * All the resources that were allocated to the service are also released.
623      *
624      * @param serviceId a service id to be closed which is obtained from {@link ImsManager#open}
625      * @throws ImsException if calling the IMS service results in an error
626      */
627     public void close(int serviceId) throws ImsException {
628         checkAndThrowExceptionIfServiceUnavailable();
629
630         try {
631             mImsService.close(serviceId);
632         } catch (RemoteException e) {
633             throw new ImsException("close()", e,
634                     ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
635         } finally {
636             mUt = null;
637             mConfig = null;
638             mEcbm = null;
639         }
640     }
641
642     /**
643      * Gets the configuration interface to provision / withdraw the supplementary service settings.
644      *
645      * @param serviceId a service id which is obtained from {@link ImsManager#open}
646      * @return the Ut interface instance
647      * @throws ImsException if getting the Ut interface results in an error
648      */
649     public ImsUtInterface getSupplementaryServiceConfiguration(int serviceId)
650             throws ImsException {
651         // FIXME: manage the multiple Ut interfaces based on the service id
652         if (mUt == null) {
653             checkAndThrowExceptionIfServiceUnavailable();
654
655             try {
656                 IImsUt iUt = mImsService.getUtInterface(serviceId);
657
658                 if (iUt == null) {
659                     throw new ImsException("getSupplementaryServiceConfiguration()",
660                             ImsReasonInfo.CODE_UT_NOT_SUPPORTED);
661                 }
662
663                 mUt = new ImsUt(iUt);
664             } catch (RemoteException e) {
665                 throw new ImsException("getSupplementaryServiceConfiguration()", e,
666                         ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
667             }
668         }
669
670         return mUt;
671     }
672
673     /**
674      * Checks if the IMS service has successfully registered to the IMS network
675      * with the specified service & call type.
676      *
677      * @param serviceId a service id which is obtained from {@link ImsManager#open}
678      * @param serviceType a service type that is specified in {@link ImsCallProfile}
679      *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
680      *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
681      * @param callType a call type that is specified in {@link ImsCallProfile}
682      *        {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO}
683      *        {@link ImsCallProfile#CALL_TYPE_VOICE}
684      *        {@link ImsCallProfile#CALL_TYPE_VT}
685      *        {@link ImsCallProfile#CALL_TYPE_VS}
686      * @return true if the specified service id is connected to the IMS network;
687      *        false otherwise
688      * @throws ImsException if calling the IMS service results in an error
689      */
690     public boolean isConnected(int serviceId, int serviceType, int callType)
691             throws ImsException {
692         checkAndThrowExceptionIfServiceUnavailable();
693
694         try {
695             return mImsService.isConnected(serviceId, serviceType, callType);
696         } catch (RemoteException e) {
697             throw new ImsException("isServiceConnected()", e,
698                     ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
699         }
700     }
701
702     /**
703      * Checks if the specified IMS service is opend.
704      *
705      * @param serviceId a service id which is obtained from {@link ImsManager#open}
706      * @return true if the specified service id is opened; false otherwise
707      * @throws ImsException if calling the IMS service results in an error
708      */
709     public boolean isOpened(int serviceId) throws ImsException {
710         checkAndThrowExceptionIfServiceUnavailable();
711
712         try {
713             return mImsService.isOpened(serviceId);
714         } catch (RemoteException e) {
715             throw new ImsException("isOpened()", e,
716                     ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
717         }
718     }
719
720     /**
721      * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
722      *
723      * @param serviceId a service id which is obtained from {@link ImsManager#open}
724      * @param serviceType a service type that is specified in {@link ImsCallProfile}
725      *        {@link ImsCallProfile#SERVICE_TYPE_NONE}
726      *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
727      *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
728      * @param callType a call type that is specified in {@link ImsCallProfile}
729      *        {@link ImsCallProfile#CALL_TYPE_VOICE}
730      *        {@link ImsCallProfile#CALL_TYPE_VT}
731      *        {@link ImsCallProfile#CALL_TYPE_VT_TX}
732      *        {@link ImsCallProfile#CALL_TYPE_VT_RX}
733      *        {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
734      *        {@link ImsCallProfile#CALL_TYPE_VS}
735      *        {@link ImsCallProfile#CALL_TYPE_VS_TX}
736      *        {@link ImsCallProfile#CALL_TYPE_VS_RX}
737      * @return a {@link ImsCallProfile} object
738      * @throws ImsException if calling the IMS service results in an error
739      */
740     public ImsCallProfile createCallProfile(int serviceId,
741             int serviceType, int callType) throws ImsException {
742         checkAndThrowExceptionIfServiceUnavailable();
743
744         try {
745             return mImsService.createCallProfile(serviceId, serviceType, callType);
746         } catch (RemoteException e) {
747             throw new ImsException("createCallProfile()", e,
748                     ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
749         }
750     }
751
752     /**
753      * Creates a {@link ImsCall} to make a call.
754      *
755      * @param serviceId a service id which is obtained from {@link ImsManager#open}
756      * @param profile a call profile to make the call
757      *      (it contains service type, call type, media information, etc.)
758      * @param participants participants to invite the conference call
759      * @param listener listen to the call events from {@link ImsCall}
760      * @return a {@link ImsCall} object
761      * @throws ImsException if calling the IMS service results in an error
762      */
763     public ImsCall makeCall(int serviceId, ImsCallProfile profile, String[] callees,
764             ImsCall.Listener listener) throws ImsException {
765         if (DBG) {
766             log("makeCall :: serviceId=" + serviceId
767                     + ", profile=" + profile + ", callees=" + callees);
768         }
769
770         checkAndThrowExceptionIfServiceUnavailable();
771
772         ImsCall call = new ImsCall(mContext, profile);
773
774         call.setListener(listener);
775         ImsCallSession session = createCallSession(serviceId, profile);
776
777         if ((callees != null) && (callees.length == 1)) {
778             call.start(session, callees[0]);
779         } else {
780             call.start(session, callees);
781         }
782
783         return call;
784     }
785
786     /**
787      * Creates a {@link ImsCall} to take an incoming call.
788      *
789      * @param serviceId a service id which is obtained from {@link ImsManager#open}
790      * @param incomingCallIntent the incoming call broadcast intent
791      * @param listener to listen to the call events from {@link ImsCall}
792      * @return a {@link ImsCall} object
793      * @throws ImsException if calling the IMS service results in an error
794      */
795     public ImsCall takeCall(int serviceId, Intent incomingCallIntent,
796             ImsCall.Listener listener) throws ImsException {
797         if (DBG) {
798             log("takeCall :: serviceId=" + serviceId
799                     + ", incomingCall=" + incomingCallIntent);
800         }
801
802         checkAndThrowExceptionIfServiceUnavailable();
803
804         if (incomingCallIntent == null) {
805             throw new ImsException("Can't retrieve session with null intent",
806                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
807         }
808
809         int incomingServiceId = getServiceId(incomingCallIntent);
810
811         if (serviceId != incomingServiceId) {
812             throw new ImsException("Service id is mismatched in the incoming call intent",
813                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
814         }
815
816         String callId = getCallId(incomingCallIntent);
817
818         if (callId == null) {
819             throw new ImsException("Call ID missing in the incoming call intent",
820                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
821         }
822
823         try {
824             IImsCallSession session = mImsService.getPendingCallSession(serviceId, callId);
825
826             if (session == null) {
827                 throw new ImsException("No pending session for the call",
828                         ImsReasonInfo.CODE_LOCAL_NO_PENDING_CALL);
829             }
830
831             ImsCall call = new ImsCall(mContext, session.getCallProfile());
832
833             call.attachSession(new ImsCallSession(session));
834             call.setListener(listener);
835
836             return call;
837         } catch (Throwable t) {
838             throw new ImsException("takeCall()", t, ImsReasonInfo.CODE_UNSPECIFIED);
839         }
840     }
841
842     /**
843      * Gets the config interface to get/set service/capability parameters.
844      *
845      * @return the ImsConfig instance.
846      * @throws ImsException if getting the setting interface results in an error.
847      */
848     public ImsConfig getConfigInterface() throws ImsException {
849
850         if (mConfig == null) {
851             checkAndThrowExceptionIfServiceUnavailable();
852
853             try {
854                 IImsConfig config = mImsService.getConfigInterface(mPhoneId);
855                 if (config == null) {
856                     throw new ImsException("getConfigInterface()",
857                             ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
858                 }
859                 mConfig = new ImsConfig(config, mContext);
860             } catch (RemoteException e) {
861                 throw new ImsException("getConfigInterface()", e,
862                         ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
863             }
864         }
865         if (DBG) log("getConfigInterface(), mConfig= " + mConfig);
866         return mConfig;
867     }
868
869     public void setUiTTYMode(Context context, int serviceId, int uiTtyMode, Message onComplete)
870             throws ImsException {
871
872         checkAndThrowExceptionIfServiceUnavailable();
873
874         try {
875             mImsService.setUiTTYMode(serviceId, uiTtyMode, onComplete);
876         } catch (RemoteException e) {
877             throw new ImsException("setTTYMode()", e,
878                     ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
879         }
880
881         if (!getBooleanCarrierConfig(context,
882                 CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL)) {
883             setAdvanced4GMode((uiTtyMode == TelecomManager.TTY_MODE_OFF) &&
884                     isEnhanced4gLteModeSettingEnabledByUser(context));
885         }
886     }
887
888     /**
889      * Get the boolean config from carrier config manager.
890      *
891      * @param context the context to get carrier service
892      * @param key config key defined in CarrierConfigManager
893      * @return boolean value of corresponding key.
894      */
895     private static boolean getBooleanCarrierConfig(Context context, String key) {
896         CarrierConfigManager configManager = (CarrierConfigManager) context.getSystemService(
897                 Context.CARRIER_CONFIG_SERVICE);
898         PersistableBundle b = null;
899         if (configManager != null) {
900             b = configManager.getConfig();
901         }
902         if (b != null) {
903             return b.getBoolean(key);
904         } else {
905             // Return static default defined in CarrierConfigManager.
906             return CarrierConfigManager.getDefaultConfig().getBoolean(key);
907         }
908     }
909
910     /**
911      * Gets the call ID from the specified incoming call broadcast intent.
912      *
913      * @param incomingCallIntent the incoming call broadcast intent
914      * @return the call ID or null if the intent does not contain it
915      */
916     private static String getCallId(Intent incomingCallIntent) {
917         if (incomingCallIntent == null) {
918             return null;
919         }
920
921         return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
922     }
923
924     /**
925      * Gets the service type from the specified incoming call broadcast intent.
926      *
927      * @param incomingCallIntent the incoming call broadcast intent
928      * @return the service identifier or -1 if the intent does not contain it
929      */
930     private static int getServiceId(Intent incomingCallIntent) {
931         if (incomingCallIntent == null) {
932             return (-1);
933         }
934
935         return incomingCallIntent.getIntExtra(EXTRA_SERVICE_ID, -1);
936     }
937
938     /**
939      * Binds the IMS service only if the service is not created.
940      */
941     private void checkAndThrowExceptionIfServiceUnavailable()
942             throws ImsException {
943         if (mImsService == null) {
944             createImsService(true);
945
946             if (mImsService == null) {
947                 throw new ImsException("Service is unavailable",
948                         ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
949             }
950         }
951     }
952
953     private static String getImsServiceName(int phoneId) {
954         // TODO: MSIM implementation needs to decide on service name as a function of phoneId
955         return IMS_SERVICE;
956     }
957
958     /**
959      * Binds the IMS service to make/receive the call.
960      */
961     private void createImsService(boolean checkService) {
962         if (checkService) {
963             IBinder binder = ServiceManager.checkService(getImsServiceName(mPhoneId));
964
965             if (binder == null) {
966                 return;
967             }
968         }
969
970         IBinder b = ServiceManager.getService(getImsServiceName(mPhoneId));
971
972         if (b != null) {
973             try {
974                 b.linkToDeath(mDeathRecipient, 0);
975             } catch (RemoteException e) {
976             }
977         }
978
979         mImsService = IImsService.Stub.asInterface(b);
980     }
981
982     /**
983      * Creates a {@link ImsCallSession} with the specified call profile.
984      * Use other methods, if applicable, instead of interacting with
985      * {@link ImsCallSession} directly.
986      *
987      * @param serviceId a service id which is obtained from {@link ImsManager#open}
988      * @param profile a call profile to make the call
989      */
990     private ImsCallSession createCallSession(int serviceId,
991             ImsCallProfile profile) throws ImsException {
992         try {
993             return new ImsCallSession(mImsService.createCallSession(serviceId, profile, null));
994         } catch (RemoteException e) {
995             return null;
996         }
997     }
998
999     private ImsRegistrationListenerProxy createRegistrationListenerProxy(int serviceClass,
1000             ImsConnectionStateListener listener) {
1001         ImsRegistrationListenerProxy proxy =
1002                 new ImsRegistrationListenerProxy(serviceClass, listener);
1003         return proxy;
1004     }
1005
1006     private static void log(String s) {
1007         Rlog.d(TAG, s);
1008     }
1009
1010     private static void loge(String s) {
1011         Rlog.e(TAG, s);
1012     }
1013
1014     private static void loge(String s, Throwable t) {
1015         Rlog.e(TAG, s, t);
1016     }
1017
1018     /**
1019      * Used for turning on IMS.if its off already
1020      */
1021     private void turnOnIms() throws ImsException {
1022         checkAndThrowExceptionIfServiceUnavailable();
1023
1024         try {
1025             mImsService.turnOnIms(mPhoneId);
1026         } catch (RemoteException e) {
1027             throw new ImsException("turnOnIms() ", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
1028         }
1029     }
1030
1031     private boolean isImsTurnOffAllowed() {
1032         return getBooleanCarrierConfig(mContext,
1033                 CarrierConfigManager.KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL)
1034                 && (!isWfcEnabledByPlatform(mContext)
1035                 || !isWfcEnabledByUser(mContext));
1036     }
1037
1038     private void setAdvanced4GMode(boolean turnOn) throws ImsException {
1039         checkAndThrowExceptionIfServiceUnavailable();
1040
1041         try {
1042             ImsConfig config = getConfigInterface();
1043             if (config != null && (turnOn || !isImsTurnOffAllowed())) {
1044                 config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE,
1045                         TelephonyManager.NETWORK_TYPE_LTE, turnOn ? 1 : 0, null);
1046                 if (isVtEnabledByPlatform(mContext)) {
1047                     // TODO: once VT is available on platform:
1048                     // - replace the '1' with the current user configuration of VT.
1049                     // - separate exception checks for setFeatureValue() failures for VoLTE and VT.
1050                     //   I.e. if VoLTE fails still try to configure VT.
1051                     config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE,
1052                             TelephonyManager.NETWORK_TYPE_LTE, turnOn ? 1 : 0, null);
1053                 }
1054             }
1055         } catch (ImsException e) {
1056             log("setAdvanced4GMode() : " + e);
1057         }
1058         if (turnOn) {
1059             turnOnIms();
1060         } else if (isImsTurnOffAllowed()) {
1061             log("setAdvanced4GMode() : imsServiceAllowTurnOff -> turnOffIms");
1062             turnOffIms();
1063         }
1064     }
1065
1066     /**
1067      * Used for turning off IMS completely in order to make the device CSFB'ed.
1068      * Once turned off, all calls will be over CS.
1069      */
1070     private void turnOffIms() throws ImsException {
1071         checkAndThrowExceptionIfServiceUnavailable();
1072
1073         try {
1074             mImsService.turnOffIms(mPhoneId);
1075         } catch (RemoteException e) {
1076             throw new ImsException("turnOffIms() ", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
1077         }
1078     }
1079
1080     /**
1081      * Death recipient class for monitoring IMS service.
1082      */
1083     private class ImsServiceDeathRecipient implements IBinder.DeathRecipient {
1084         @Override
1085         public void binderDied() {
1086             mImsService = null;
1087             mUt = null;
1088             mConfig = null;
1089             mEcbm = null;
1090
1091             if (mContext != null) {
1092                 Intent intent = new Intent(ACTION_IMS_SERVICE_DOWN);
1093                 intent.putExtra(EXTRA_PHONE_ID, mPhoneId);
1094                 mContext.sendBroadcast(new Intent(intent));
1095             }
1096         }
1097     }
1098
1099     /**
1100      * Adapter class for {@link IImsRegistrationListener}.
1101      */
1102     private class ImsRegistrationListenerProxy extends IImsRegistrationListener.Stub {
1103         private int mServiceClass;
1104         private ImsConnectionStateListener mListener;
1105
1106         public ImsRegistrationListenerProxy(int serviceClass,
1107                 ImsConnectionStateListener listener) {
1108             mServiceClass = serviceClass;
1109             mListener = listener;
1110         }
1111
1112         public boolean isSameProxy(int serviceClass) {
1113             return (mServiceClass == serviceClass);
1114         }
1115
1116         @Override
1117         public void registrationConnected() {
1118             if (DBG) {
1119                 log("registrationConnected ::");
1120             }
1121
1122             if (mListener != null) {
1123                 mListener.onImsConnected();
1124             }
1125         }
1126
1127         @Override
1128         public void registrationProgressing() {
1129             if (DBG) {
1130                 log("registrationProgressing ::");
1131             }
1132
1133             if (mListener != null) {
1134                 mListener.onImsProgressing();
1135             }
1136         }
1137
1138         @Override
1139         public void registrationDisconnected(ImsReasonInfo imsReasonInfo) {
1140             if (DBG) {
1141                 log("registrationDisconnected :: imsReasonInfo" + imsReasonInfo);
1142             }
1143
1144             if (mListener != null) {
1145                 mListener.onImsDisconnected(imsReasonInfo);
1146             }
1147         }
1148
1149         @Override
1150         public void registrationResumed() {
1151             if (DBG) {
1152                 log("registrationResumed ::");
1153             }
1154
1155             if (mListener != null) {
1156                 mListener.onImsResumed();
1157             }
1158         }
1159
1160         @Override
1161         public void registrationSuspended() {
1162             if (DBG) {
1163                 log("registrationSuspended ::");
1164             }
1165
1166             if (mListener != null) {
1167                 mListener.onImsSuspended();
1168             }
1169         }
1170
1171         @Override
1172         public void registrationServiceCapabilityChanged(int serviceClass, int event) {
1173             log("registrationServiceCapabilityChanged :: serviceClass=" +
1174                     serviceClass + ", event=" + event);
1175
1176             if (mListener != null) {
1177                 mListener.onImsConnected();
1178             }
1179         }
1180
1181         @Override
1182         public void registrationFeatureCapabilityChanged(int serviceClass,
1183                 int[] enabledFeatures, int[] disabledFeatures) {
1184             log("registrationFeatureCapabilityChanged :: serviceClass=" +
1185                     serviceClass);
1186             if (mListener != null) {
1187                 mListener.onFeatureCapabilityChanged(serviceClass,
1188                         enabledFeatures, disabledFeatures);
1189             }
1190         }
1191
1192         @Override
1193         public void voiceMessageCountUpdate(int count) {
1194             log("voiceMessageCountUpdate :: count=" + count);
1195
1196             if (mListener != null) {
1197                 mListener.onVoiceMessageCountChanged(count);
1198             }
1199         }
1200
1201     }
1202     /**
1203      * Gets the ECBM interface to request ECBM exit.
1204      *
1205      * @param serviceId a service id which is obtained from {@link ImsManager#open}
1206      * @return the ECBM interface instance
1207      * @throws ImsException if getting the ECBM interface results in an error
1208      */
1209     public ImsEcbm getEcbmInterface(int serviceId) throws ImsException {
1210         if (mEcbm == null) {
1211             checkAndThrowExceptionIfServiceUnavailable();
1212
1213             try {
1214                 IImsEcbm iEcbm = mImsService.getEcbmInterface(serviceId);
1215
1216                 if (iEcbm == null) {
1217                     throw new ImsException("getEcbmInterface()",
1218                             ImsReasonInfo.CODE_ECBM_NOT_SUPPORTED);
1219                 }
1220                 mEcbm = new ImsEcbm(iEcbm);
1221             } catch (RemoteException e) {
1222                 throw new ImsException("getEcbmInterface()", e,
1223                         ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
1224             }
1225         }
1226         return mEcbm;
1227     }
1228 }