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