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