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