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