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