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