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