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