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