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