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