ImsManager.java revision 6f3737f5b42845db52084702f08a1d7119db1bc4
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.os.IBinder;
24import android.os.Message;
25import android.os.RemoteException;
26import android.os.ServiceManager;
27import android.os.SystemProperties;
28import android.provider.Settings;
29import android.telecom.TelecomManager;
30import android.telephony.Rlog;
31import android.telephony.SubscriptionManager;
32import android.telephony.TelephonyManager;
33
34import com.android.ims.internal.IImsCallSession;
35import com.android.ims.internal.IImsEcbm;
36import com.android.ims.internal.IImsEcbmListener;
37import com.android.ims.internal.IImsRegistrationListener;
38import com.android.ims.internal.IImsService;
39import com.android.ims.internal.IImsUt;
40import com.android.ims.internal.ImsCallSession;
41import com.android.ims.internal.IImsConfig;
42
43import 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 */
53public 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.ON);
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}
1123