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      */
getInstance(Context context, int phoneId)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      */
isEnhanced4gLteModeSettingEnabledByUser(Context context)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      */
setEnhanced4gLteModeSetting(Context context, boolean enabled)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      */
isNonTtyOrTtyOnVolteEnabled(Context context)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      */
isVolteEnabledByPlatform(Context context)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      */
isVolteProvisionedOnDevice(Context context)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      */
isVtEnabledByPlatform(Context context)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      */
isWfcEnabledByUser(Context context)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      */
setWfcSetting(Context context, boolean enabled)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      */
getWfcMode(Context context)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      */
setWfcMode(Context context, int wfcMode)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 
setWfcModeInternal(Context context, int wfcMode)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      */
isWfcRoamingEnabledByUser(Context context)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      */
setWfcRoamingSetting(Context context, boolean enabled)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      */
isWfcEnabledByPlatform(Context context)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 
ImsManager(Context context, int phoneId)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      */
isServiceAvailable()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      */
open(int serviceClass, PendingIntent incomingCallPendingIntent, ImsConnectionStateListener listener)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      */
close(int serviceId)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      */
getSupplementaryServiceConfiguration(int serviceId)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      */
isConnected(int serviceId, int serviceType, int callType)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      */
isOpened(int serviceId)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      */
createCallProfile(int serviceId, int serviceType, int callType)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      */
makeCall(int serviceId, ImsCallProfile profile, String[] callees, ImsCall.Listener listener)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      */
takeCall(int serviceId, Intent incomingCallIntent, ImsCall.Listener listener)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      */
getConfigInterface()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 
setUiTTYMode(Context context, int serviceId, int uiTtyMode, Message onComplete)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      */
getBooleanCarrierConfig(Context context, String key)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      */
getCallId(Intent incomingCallIntent)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      */
getServiceId(Intent incomingCallIntent)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      */
checkAndThrowExceptionIfServiceUnavailable()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 
getImsServiceName(int phoneId)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      */
createImsService(boolean checkService)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      */
createCallSession(int serviceId, ImsCallProfile profile)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 
createRegistrationListenerProxy(int serviceClass, ImsConnectionStateListener listener)886     private ImsRegistrationListenerProxy createRegistrationListenerProxy(int serviceClass,
887             ImsConnectionStateListener listener) {
888         ImsRegistrationListenerProxy proxy =
889                 new ImsRegistrationListenerProxy(serviceClass, listener);
890         return proxy;
891     }
892 
log(String s)893     private static void log(String s) {
894         Rlog.d(TAG, s);
895     }
896 
loge(String s)897     private static void loge(String s) {
898         Rlog.e(TAG, s);
899     }
900 
loge(String s, Throwable t)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      */
turnOnIms()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 
isImsTurnOffAllowed()918     private boolean isImsTurnOffAllowed() {
919         return getBooleanCarrierConfig(mContext,
920                 CarrierConfigManager.KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL)
921                 && (!isWfcEnabledByPlatform(mContext)
922                 || !isWfcEnabledByUser(mContext));
923     }
924 
setAdvanced4GMode(boolean turnOn)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      */
turnOffIms()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
binderDied()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 
ImsRegistrationListenerProxy(int serviceClass, ImsConnectionStateListener listener)993         public ImsRegistrationListenerProxy(int serviceClass,
994                 ImsConnectionStateListener listener) {
995             mServiceClass = serviceClass;
996             mListener = listener;
997         }
998 
isSameProxy(int serviceClass)999         public boolean isSameProxy(int serviceClass) {
1000             return (mServiceClass == serviceClass);
1001         }
1002 
1003         @Override
registrationConnected()1004         public void registrationConnected() {
1005             if (DBG) {
1006                 log("registrationConnected ::");
1007             }
1008 
1009             if (mListener != null) {
1010                 mListener.onImsConnected();
1011             }
1012         }
1013 
1014         @Override
registrationProgressing()1015         public void registrationProgressing() {
1016             if (DBG) {
1017                 log("registrationProgressing ::");
1018             }
1019 
1020             if (mListener != null) {
1021                 mListener.onImsProgressing();
1022             }
1023         }
1024 
1025         @Override
registrationDisconnected(ImsReasonInfo imsReasonInfo)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
registrationResumed()1037         public void registrationResumed() {
1038             if (DBG) {
1039                 log("registrationResumed ::");
1040             }
1041 
1042             if (mListener != null) {
1043                 mListener.onImsResumed();
1044             }
1045         }
1046 
1047         @Override
registrationSuspended()1048         public void registrationSuspended() {
1049             if (DBG) {
1050                 log("registrationSuspended ::");
1051             }
1052 
1053             if (mListener != null) {
1054                 mListener.onImsSuspended();
1055             }
1056         }
1057 
1058         @Override
registrationServiceCapabilityChanged(int serviceClass, int event)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
registrationFeatureCapabilityChanged(int serviceClass, int[] enabledFeatures, int[] disabledFeatures)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      */
getEcbmInterface(int serviceId)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