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