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