ImsManager.java revision 8ca52ff89d9f43cf1ff04d1d607a0b904e749a82
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 imsManager.updateVolteFeatureValue(); 465 imsManager.updateVideoCallFeatureValue(); 466 imsManager.updateWfcFeatureAndProvisionedValues(); 467 imsManager.mConfigUpdated = true; 468 } catch (ImsException e) { 469 loge("updateImsServiceConfig: " + e); 470 imsManager.mConfigUpdated = false; 471 } 472 } 473 } 474 475 private void updateVolteFeatureValue() throws ImsException { 476 boolean available = isVolteEnabledByPlatform(mContext); 477 boolean enabled = isEnhanced4gLteModeSettingEnabledByUser(mContext); 478 boolean isNonTty = isNonTtyOrTtyOnVolteEnabled(mContext); 479 480 log("updateVolteFeatureValue: available = " + available 481 + ", enabled = " + enabled 482 + ", nonTTY = " + isNonTty); 483 484 getConfigInterface().setFeatureValue( 485 ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE, 486 TelephonyManager.NETWORK_TYPE_LTE, 487 (available && enabled && isNonTty) ? 488 ImsConfig.FeatureValueConstants.ON : 489 ImsConfig.FeatureValueConstants.OFF, 490 null); 491 } 492 493 private void updateVideoCallFeatureValue() throws ImsException { 494 boolean available = isVtEnabledByPlatform(mContext); 495 SharedPreferences sharedPrefs = 496 PreferenceManager.getDefaultSharedPreferences(mContext); 497 boolean enabled = isEnhanced4gLteModeSettingEnabledByUser(mContext) && 498 sharedPrefs.getBoolean(PREF_ENABLE_VIDEO_CALLING_KEY, true); 499 boolean isNonTty = Settings.Secure.getInt(mContext.getContentResolver(), 500 Settings.Secure.PREFERRED_TTY_MODE, TelecomManager.TTY_MODE_OFF) 501 == TelecomManager.TTY_MODE_OFF; 502 503 log("updateVideoCallFeatureValue: available = " + available 504 + ", enabled = " + enabled 505 + ", nonTTY = " + isNonTty); 506 507 getConfigInterface().setFeatureValue( 508 ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE, 509 TelephonyManager.NETWORK_TYPE_LTE, 510 (available && enabled && isNonTty) ? 511 ImsConfig.FeatureValueConstants.ON : 512 ImsConfig.FeatureValueConstants.OFF, 513 null); 514 } 515 516 private void updateWfcFeatureAndProvisionedValues() throws ImsException { 517 boolean available = isWfcEnabledByPlatform(mContext); 518 boolean enabled = isWfcEnabledByUser(mContext); 519 int mode = getWfcMode(mContext); 520 boolean roaming = isWfcRoamingEnabledByUser(mContext); 521 522 log("updateWfcFeatureAndProvisionedValues: available = " + available 523 + ", enabled = " + enabled 524 + ", mode = " + mode 525 + ", roaming = " + roaming); 526 527 getConfigInterface().setFeatureValue( 528 ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE, 529 TelephonyManager.NETWORK_TYPE_LTE, 530 (available && enabled) ? 531 ImsConfig.FeatureValueConstants.ON : 532 ImsConfig.FeatureValueConstants.OFF, 533 null); 534 535 if (!available || !enabled) { 536 mode = ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED; 537 roaming = false; 538 } 539 setWfcModeInternal(mContext, mode); 540 setWfcRoamingSettingInternal(mContext, roaming); 541 } 542 543 private ImsManager(Context context, int phoneId) { 544 mContext = context; 545 mPhoneId = phoneId; 546 createImsService(true); 547 } 548 549 /* 550 * Returns a flag indicating whether the IMS service is available. 551 */ 552 public boolean isServiceAvailable() { 553 if (mImsService != null) { 554 return true; 555 } 556 557 IBinder binder = ServiceManager.checkService(getImsServiceName(mPhoneId)); 558 if (binder != null) { 559 return true; 560 } 561 562 return false; 563 } 564 565 /** 566 * Opens the IMS service for making calls and/or receiving generic IMS calls. 567 * The caller may make subsquent calls through {@link #makeCall}. 568 * The IMS service will register the device to the operator's network with the credentials 569 * (from ISIM) periodically in order to receive calls from the operator's network. 570 * When the IMS service receives a new call, it will send out an intent with 571 * the provided action string. 572 * The intent contains a call ID extra {@link getCallId} and it can be used to take a call. 573 * 574 * @param serviceClass a service class specified in {@link ImsServiceClass} 575 * For VoLTE service, it MUST be a {@link ImsServiceClass#MMTEL}. 576 * @param incomingCallPendingIntent When an incoming call is received, 577 * the IMS service will call {@link PendingIntent#send(Context, int, Intent)} to 578 * send back the intent to the caller with {@link #INCOMING_CALL_RESULT_CODE} 579 * as the result code and the intent to fill in the call ID; It cannot be null 580 * @param listener To listen to IMS registration events; It cannot be null 581 * @return identifier (greater than 0) for the specified service 582 * @throws NullPointerException if {@code incomingCallPendingIntent} 583 * or {@code listener} is null 584 * @throws ImsException if calling the IMS service results in an error 585 * @see #getCallId 586 * @see #getServiceId 587 */ 588 public int open(int serviceClass, PendingIntent incomingCallPendingIntent, 589 ImsConnectionStateListener listener) throws ImsException { 590 checkAndThrowExceptionIfServiceUnavailable(); 591 592 if (incomingCallPendingIntent == null) { 593 throw new NullPointerException("incomingCallPendingIntent can't be null"); 594 } 595 596 if (listener == null) { 597 throw new NullPointerException("listener can't be null"); 598 } 599 600 int result = 0; 601 602 try { 603 result = mImsService.open(mPhoneId, serviceClass, incomingCallPendingIntent, 604 createRegistrationListenerProxy(serviceClass, listener)); 605 } catch (RemoteException e) { 606 throw new ImsException("open()", e, 607 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 608 } 609 610 if (result <= 0) { 611 // If the return value is a minus value, 612 // it means that an error occurred in the service. 613 // So, it needs to convert to the reason code specified in ImsReasonInfo. 614 throw new ImsException("open()", (result * (-1))); 615 } 616 617 return result; 618 } 619 620 /** 621 * Closes the specified service ({@link ImsServiceClass}) not to make/receive calls. 622 * All the resources that were allocated to the service are also released. 623 * 624 * @param serviceId a service id to be closed which is obtained from {@link ImsManager#open} 625 * @throws ImsException if calling the IMS service results in an error 626 */ 627 public void close(int serviceId) throws ImsException { 628 checkAndThrowExceptionIfServiceUnavailable(); 629 630 try { 631 mImsService.close(serviceId); 632 } catch (RemoteException e) { 633 throw new ImsException("close()", e, 634 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 635 } finally { 636 mUt = null; 637 mConfig = null; 638 mEcbm = null; 639 } 640 } 641 642 /** 643 * Gets the configuration interface to provision / withdraw the supplementary service settings. 644 * 645 * @param serviceId a service id which is obtained from {@link ImsManager#open} 646 * @return the Ut interface instance 647 * @throws ImsException if getting the Ut interface results in an error 648 */ 649 public ImsUtInterface getSupplementaryServiceConfiguration(int serviceId) 650 throws ImsException { 651 // FIXME: manage the multiple Ut interfaces based on the service id 652 if (mUt == null) { 653 checkAndThrowExceptionIfServiceUnavailable(); 654 655 try { 656 IImsUt iUt = mImsService.getUtInterface(serviceId); 657 658 if (iUt == null) { 659 throw new ImsException("getSupplementaryServiceConfiguration()", 660 ImsReasonInfo.CODE_UT_NOT_SUPPORTED); 661 } 662 663 mUt = new ImsUt(iUt); 664 } catch (RemoteException e) { 665 throw new ImsException("getSupplementaryServiceConfiguration()", e, 666 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 667 } 668 } 669 670 return mUt; 671 } 672 673 /** 674 * Checks if the IMS service has successfully registered to the IMS network 675 * with the specified service & call type. 676 * 677 * @param serviceId a service id which is obtained from {@link ImsManager#open} 678 * @param serviceType a service type that is specified in {@link ImsCallProfile} 679 * {@link ImsCallProfile#SERVICE_TYPE_NORMAL} 680 * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY} 681 * @param callType a call type that is specified in {@link ImsCallProfile} 682 * {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO} 683 * {@link ImsCallProfile#CALL_TYPE_VOICE} 684 * {@link ImsCallProfile#CALL_TYPE_VT} 685 * {@link ImsCallProfile#CALL_TYPE_VS} 686 * @return true if the specified service id is connected to the IMS network; 687 * false otherwise 688 * @throws ImsException if calling the IMS service results in an error 689 */ 690 public boolean isConnected(int serviceId, int serviceType, int callType) 691 throws ImsException { 692 checkAndThrowExceptionIfServiceUnavailable(); 693 694 try { 695 return mImsService.isConnected(serviceId, serviceType, callType); 696 } catch (RemoteException e) { 697 throw new ImsException("isServiceConnected()", e, 698 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 699 } 700 } 701 702 /** 703 * Checks if the specified IMS service is opend. 704 * 705 * @param serviceId a service id which is obtained from {@link ImsManager#open} 706 * @return true if the specified service id is opened; false otherwise 707 * @throws ImsException if calling the IMS service results in an error 708 */ 709 public boolean isOpened(int serviceId) throws ImsException { 710 checkAndThrowExceptionIfServiceUnavailable(); 711 712 try { 713 return mImsService.isOpened(serviceId); 714 } catch (RemoteException e) { 715 throw new ImsException("isOpened()", e, 716 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 717 } 718 } 719 720 /** 721 * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state. 722 * 723 * @param serviceId a service id which is obtained from {@link ImsManager#open} 724 * @param serviceType a service type that is specified in {@link ImsCallProfile} 725 * {@link ImsCallProfile#SERVICE_TYPE_NONE} 726 * {@link ImsCallProfile#SERVICE_TYPE_NORMAL} 727 * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY} 728 * @param callType a call type that is specified in {@link ImsCallProfile} 729 * {@link ImsCallProfile#CALL_TYPE_VOICE} 730 * {@link ImsCallProfile#CALL_TYPE_VT} 731 * {@link ImsCallProfile#CALL_TYPE_VT_TX} 732 * {@link ImsCallProfile#CALL_TYPE_VT_RX} 733 * {@link ImsCallProfile#CALL_TYPE_VT_NODIR} 734 * {@link ImsCallProfile#CALL_TYPE_VS} 735 * {@link ImsCallProfile#CALL_TYPE_VS_TX} 736 * {@link ImsCallProfile#CALL_TYPE_VS_RX} 737 * @return a {@link ImsCallProfile} object 738 * @throws ImsException if calling the IMS service results in an error 739 */ 740 public ImsCallProfile createCallProfile(int serviceId, 741 int serviceType, int callType) throws ImsException { 742 checkAndThrowExceptionIfServiceUnavailable(); 743 744 try { 745 return mImsService.createCallProfile(serviceId, serviceType, callType); 746 } catch (RemoteException e) { 747 throw new ImsException("createCallProfile()", e, 748 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 749 } 750 } 751 752 /** 753 * Creates a {@link ImsCall} to make a call. 754 * 755 * @param serviceId a service id which is obtained from {@link ImsManager#open} 756 * @param profile a call profile to make the call 757 * (it contains service type, call type, media information, etc.) 758 * @param participants participants to invite the conference call 759 * @param listener listen to the call events from {@link ImsCall} 760 * @return a {@link ImsCall} object 761 * @throws ImsException if calling the IMS service results in an error 762 */ 763 public ImsCall makeCall(int serviceId, ImsCallProfile profile, String[] callees, 764 ImsCall.Listener listener) throws ImsException { 765 if (DBG) { 766 log("makeCall :: serviceId=" + serviceId 767 + ", profile=" + profile + ", callees=" + callees); 768 } 769 770 checkAndThrowExceptionIfServiceUnavailable(); 771 772 ImsCall call = new ImsCall(mContext, profile); 773 774 call.setListener(listener); 775 ImsCallSession session = createCallSession(serviceId, profile); 776 777 if ((callees != null) && (callees.length == 1)) { 778 call.start(session, callees[0]); 779 } else { 780 call.start(session, callees); 781 } 782 783 return call; 784 } 785 786 /** 787 * Creates a {@link ImsCall} to take an incoming call. 788 * 789 * @param serviceId a service id which is obtained from {@link ImsManager#open} 790 * @param incomingCallIntent the incoming call broadcast intent 791 * @param listener to 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 takeCall(int serviceId, Intent incomingCallIntent, 796 ImsCall.Listener listener) throws ImsException { 797 if (DBG) { 798 log("takeCall :: serviceId=" + serviceId 799 + ", incomingCall=" + incomingCallIntent); 800 } 801 802 checkAndThrowExceptionIfServiceUnavailable(); 803 804 if (incomingCallIntent == null) { 805 throw new ImsException("Can't retrieve session with null intent", 806 ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT); 807 } 808 809 int incomingServiceId = getServiceId(incomingCallIntent); 810 811 if (serviceId != incomingServiceId) { 812 throw new ImsException("Service id is mismatched in the incoming call intent", 813 ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT); 814 } 815 816 String callId = getCallId(incomingCallIntent); 817 818 if (callId == null) { 819 throw new ImsException("Call ID missing in the incoming call intent", 820 ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT); 821 } 822 823 try { 824 IImsCallSession session = mImsService.getPendingCallSession(serviceId, callId); 825 826 if (session == null) { 827 throw new ImsException("No pending session for the call", 828 ImsReasonInfo.CODE_LOCAL_NO_PENDING_CALL); 829 } 830 831 ImsCall call = new ImsCall(mContext, session.getCallProfile()); 832 833 call.attachSession(new ImsCallSession(session)); 834 call.setListener(listener); 835 836 return call; 837 } catch (Throwable t) { 838 throw new ImsException("takeCall()", t, ImsReasonInfo.CODE_UNSPECIFIED); 839 } 840 } 841 842 /** 843 * Gets the config interface to get/set service/capability parameters. 844 * 845 * @return the ImsConfig instance. 846 * @throws ImsException if getting the setting interface results in an error. 847 */ 848 public ImsConfig getConfigInterface() throws ImsException { 849 850 if (mConfig == null) { 851 checkAndThrowExceptionIfServiceUnavailable(); 852 853 try { 854 IImsConfig config = mImsService.getConfigInterface(mPhoneId); 855 if (config == null) { 856 throw new ImsException("getConfigInterface()", 857 ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); 858 } 859 mConfig = new ImsConfig(config, mContext); 860 } catch (RemoteException e) { 861 throw new ImsException("getConfigInterface()", e, 862 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 863 } 864 } 865 if (DBG) log("getConfigInterface(), mConfig= " + mConfig); 866 return mConfig; 867 } 868 869 public void setUiTTYMode(Context context, int serviceId, int uiTtyMode, Message onComplete) 870 throws ImsException { 871 872 checkAndThrowExceptionIfServiceUnavailable(); 873 874 try { 875 mImsService.setUiTTYMode(serviceId, uiTtyMode, onComplete); 876 } catch (RemoteException e) { 877 throw new ImsException("setTTYMode()", e, 878 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 879 } 880 881 if (!getBooleanCarrierConfig(context, 882 CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL)) { 883 setAdvanced4GMode((uiTtyMode == TelecomManager.TTY_MODE_OFF) && 884 isEnhanced4gLteModeSettingEnabledByUser(context)); 885 } 886 } 887 888 /** 889 * Get the boolean config from carrier config manager. 890 * 891 * @param context the context to get carrier service 892 * @param key config key defined in CarrierConfigManager 893 * @return boolean value of corresponding key. 894 */ 895 private static boolean getBooleanCarrierConfig(Context context, String key) { 896 CarrierConfigManager configManager = (CarrierConfigManager) context.getSystemService( 897 Context.CARRIER_CONFIG_SERVICE); 898 PersistableBundle b = null; 899 if (configManager != null) { 900 b = configManager.getConfig(); 901 } 902 if (b != null) { 903 return b.getBoolean(key); 904 } else { 905 // Return static default defined in CarrierConfigManager. 906 return CarrierConfigManager.getDefaultConfig().getBoolean(key); 907 } 908 } 909 910 /** 911 * Gets the call ID from the specified incoming call broadcast intent. 912 * 913 * @param incomingCallIntent the incoming call broadcast intent 914 * @return the call ID or null if the intent does not contain it 915 */ 916 private static String getCallId(Intent incomingCallIntent) { 917 if (incomingCallIntent == null) { 918 return null; 919 } 920 921 return incomingCallIntent.getStringExtra(EXTRA_CALL_ID); 922 } 923 924 /** 925 * Gets the service type from the specified incoming call broadcast intent. 926 * 927 * @param incomingCallIntent the incoming call broadcast intent 928 * @return the service identifier or -1 if the intent does not contain it 929 */ 930 private static int getServiceId(Intent incomingCallIntent) { 931 if (incomingCallIntent == null) { 932 return (-1); 933 } 934 935 return incomingCallIntent.getIntExtra(EXTRA_SERVICE_ID, -1); 936 } 937 938 /** 939 * Binds the IMS service only if the service is not created. 940 */ 941 private void checkAndThrowExceptionIfServiceUnavailable() 942 throws ImsException { 943 if (mImsService == null) { 944 createImsService(true); 945 946 if (mImsService == null) { 947 throw new ImsException("Service is unavailable", 948 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 949 } 950 } 951 } 952 953 private static String getImsServiceName(int phoneId) { 954 // TODO: MSIM implementation needs to decide on service name as a function of phoneId 955 return IMS_SERVICE; 956 } 957 958 /** 959 * Binds the IMS service to make/receive the call. 960 */ 961 private void createImsService(boolean checkService) { 962 if (checkService) { 963 IBinder binder = ServiceManager.checkService(getImsServiceName(mPhoneId)); 964 965 if (binder == null) { 966 return; 967 } 968 } 969 970 IBinder b = ServiceManager.getService(getImsServiceName(mPhoneId)); 971 972 if (b != null) { 973 try { 974 b.linkToDeath(mDeathRecipient, 0); 975 } catch (RemoteException e) { 976 } 977 } 978 979 mImsService = IImsService.Stub.asInterface(b); 980 } 981 982 /** 983 * Creates a {@link ImsCallSession} with the specified call profile. 984 * Use other methods, if applicable, instead of interacting with 985 * {@link ImsCallSession} directly. 986 * 987 * @param serviceId a service id which is obtained from {@link ImsManager#open} 988 * @param profile a call profile to make the call 989 */ 990 private ImsCallSession createCallSession(int serviceId, 991 ImsCallProfile profile) throws ImsException { 992 try { 993 return new ImsCallSession(mImsService.createCallSession(serviceId, profile, null)); 994 } catch (RemoteException e) { 995 return null; 996 } 997 } 998 999 private ImsRegistrationListenerProxy createRegistrationListenerProxy(int serviceClass, 1000 ImsConnectionStateListener listener) { 1001 ImsRegistrationListenerProxy proxy = 1002 new ImsRegistrationListenerProxy(serviceClass, listener); 1003 return proxy; 1004 } 1005 1006 private static void log(String s) { 1007 Rlog.d(TAG, s); 1008 } 1009 1010 private static void loge(String s) { 1011 Rlog.e(TAG, s); 1012 } 1013 1014 private static void loge(String s, Throwable t) { 1015 Rlog.e(TAG, s, t); 1016 } 1017 1018 /** 1019 * Used for turning on IMS.if its off already 1020 */ 1021 private void turnOnIms() throws ImsException { 1022 checkAndThrowExceptionIfServiceUnavailable(); 1023 1024 try { 1025 mImsService.turnOnIms(mPhoneId); 1026 } catch (RemoteException e) { 1027 throw new ImsException("turnOnIms() ", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 1028 } 1029 } 1030 1031 private boolean isImsTurnOffAllowed() { 1032 return getBooleanCarrierConfig(mContext, 1033 CarrierConfigManager.KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL) 1034 && (!isWfcEnabledByPlatform(mContext) 1035 || !isWfcEnabledByUser(mContext)); 1036 } 1037 1038 private void setAdvanced4GMode(boolean turnOn) throws ImsException { 1039 checkAndThrowExceptionIfServiceUnavailable(); 1040 1041 try { 1042 ImsConfig config = getConfigInterface(); 1043 if (config != null && (turnOn || !isImsTurnOffAllowed())) { 1044 config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE, 1045 TelephonyManager.NETWORK_TYPE_LTE, turnOn ? 1 : 0, null); 1046 if (isVtEnabledByPlatform(mContext)) { 1047 // TODO: once VT is available on platform: 1048 // - replace the '1' with the current user configuration of VT. 1049 // - separate exception checks for setFeatureValue() failures for VoLTE and VT. 1050 // I.e. if VoLTE fails still try to configure VT. 1051 config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE, 1052 TelephonyManager.NETWORK_TYPE_LTE, turnOn ? 1 : 0, null); 1053 } 1054 } 1055 } catch (ImsException e) { 1056 log("setAdvanced4GMode() : " + e); 1057 } 1058 if (turnOn) { 1059 turnOnIms(); 1060 } else if (isImsTurnOffAllowed()) { 1061 log("setAdvanced4GMode() : imsServiceAllowTurnOff -> turnOffIms"); 1062 turnOffIms(); 1063 } 1064 } 1065 1066 /** 1067 * Used for turning off IMS completely in order to make the device CSFB'ed. 1068 * Once turned off, all calls will be over CS. 1069 */ 1070 private void turnOffIms() throws ImsException { 1071 checkAndThrowExceptionIfServiceUnavailable(); 1072 1073 try { 1074 mImsService.turnOffIms(mPhoneId); 1075 } catch (RemoteException e) { 1076 throw new ImsException("turnOffIms() ", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 1077 } 1078 } 1079 1080 /** 1081 * Death recipient class for monitoring IMS service. 1082 */ 1083 private class ImsServiceDeathRecipient implements IBinder.DeathRecipient { 1084 @Override 1085 public void binderDied() { 1086 mImsService = null; 1087 mUt = null; 1088 mConfig = null; 1089 mEcbm = null; 1090 1091 if (mContext != null) { 1092 Intent intent = new Intent(ACTION_IMS_SERVICE_DOWN); 1093 intent.putExtra(EXTRA_PHONE_ID, mPhoneId); 1094 mContext.sendBroadcast(new Intent(intent)); 1095 } 1096 } 1097 } 1098 1099 /** 1100 * Adapter class for {@link IImsRegistrationListener}. 1101 */ 1102 private class ImsRegistrationListenerProxy extends IImsRegistrationListener.Stub { 1103 private int mServiceClass; 1104 private ImsConnectionStateListener mListener; 1105 1106 public ImsRegistrationListenerProxy(int serviceClass, 1107 ImsConnectionStateListener listener) { 1108 mServiceClass = serviceClass; 1109 mListener = listener; 1110 } 1111 1112 public boolean isSameProxy(int serviceClass) { 1113 return (mServiceClass == serviceClass); 1114 } 1115 1116 @Override 1117 public void registrationConnected() { 1118 if (DBG) { 1119 log("registrationConnected ::"); 1120 } 1121 1122 if (mListener != null) { 1123 mListener.onImsConnected(); 1124 } 1125 } 1126 1127 @Override 1128 public void registrationProgressing() { 1129 if (DBG) { 1130 log("registrationProgressing ::"); 1131 } 1132 1133 if (mListener != null) { 1134 mListener.onImsProgressing(); 1135 } 1136 } 1137 1138 @Override 1139 public void registrationDisconnected(ImsReasonInfo imsReasonInfo) { 1140 if (DBG) { 1141 log("registrationDisconnected :: imsReasonInfo" + imsReasonInfo); 1142 } 1143 1144 if (mListener != null) { 1145 mListener.onImsDisconnected(imsReasonInfo); 1146 } 1147 } 1148 1149 @Override 1150 public void registrationResumed() { 1151 if (DBG) { 1152 log("registrationResumed ::"); 1153 } 1154 1155 if (mListener != null) { 1156 mListener.onImsResumed(); 1157 } 1158 } 1159 1160 @Override 1161 public void registrationSuspended() { 1162 if (DBG) { 1163 log("registrationSuspended ::"); 1164 } 1165 1166 if (mListener != null) { 1167 mListener.onImsSuspended(); 1168 } 1169 } 1170 1171 @Override 1172 public void registrationServiceCapabilityChanged(int serviceClass, int event) { 1173 log("registrationServiceCapabilityChanged :: serviceClass=" + 1174 serviceClass + ", event=" + event); 1175 1176 if (mListener != null) { 1177 mListener.onImsConnected(); 1178 } 1179 } 1180 1181 @Override 1182 public void registrationFeatureCapabilityChanged(int serviceClass, 1183 int[] enabledFeatures, int[] disabledFeatures) { 1184 log("registrationFeatureCapabilityChanged :: serviceClass=" + 1185 serviceClass); 1186 if (mListener != null) { 1187 mListener.onFeatureCapabilityChanged(serviceClass, 1188 enabledFeatures, disabledFeatures); 1189 } 1190 } 1191 1192 @Override 1193 public void voiceMessageCountUpdate(int count) { 1194 log("voiceMessageCountUpdate :: count=" + count); 1195 1196 if (mListener != null) { 1197 mListener.onVoiceMessageCountChanged(count); 1198 } 1199 } 1200 1201 } 1202 /** 1203 * Gets the ECBM interface to request ECBM exit. 1204 * 1205 * @param serviceId a service id which is obtained from {@link ImsManager#open} 1206 * @return the ECBM interface instance 1207 * @throws ImsException if getting the ECBM interface results in an error 1208 */ 1209 public ImsEcbm getEcbmInterface(int serviceId) throws ImsException { 1210 if (mEcbm == null) { 1211 checkAndThrowExceptionIfServiceUnavailable(); 1212 1213 try { 1214 IImsEcbm iEcbm = mImsService.getEcbmInterface(serviceId); 1215 1216 if (iEcbm == null) { 1217 throw new ImsException("getEcbmInterface()", 1218 ImsReasonInfo.CODE_ECBM_NOT_SUPPORTED); 1219 } 1220 mEcbm = new ImsEcbm(iEcbm); 1221 } catch (RemoteException e) { 1222 throw new ImsException("getEcbmInterface()", e, 1223 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 1224 } 1225 } 1226 return mEcbm; 1227 } 1228} 1229