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