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