ImsManager.java revision 7da5a11accec318cc3da2087fb3a891db4f661f7
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.content.Context; 21import android.content.Intent; 22import android.content.IntentFilter; 23import android.os.IBinder; 24import android.os.IBinder.DeathRecipient; 25import android.os.Message; 26import android.os.Process; 27import android.os.RemoteException; 28import android.os.ServiceManager; 29import android.telephony.Rlog; 30 31import com.android.ims.internal.IImsCallSession; 32import com.android.ims.internal.IImsRegistrationListener; 33import com.android.ims.internal.IImsService; 34import com.android.ims.internal.IImsUt; 35import com.android.ims.internal.ImsCallSession; 36import com.android.ims.internal.IImsConfig; 37 38 39import java.util.HashMap; 40 41/** 42 * Provides APIs for IMS services, such as initiating IMS calls, and provides access to 43 * the operator's IMS network. This class is the starting point for any IMS actions. 44 * You can acquire an instance of it with {@link #getInstance getInstance()}.</p> 45 * <p>The APIs in this class allows you to:</p> 46 * 47 * @hide 48 */ 49public class ImsManager { 50 /** 51 * For accessing the IMS related service. 52 * Internal use only. 53 * @hide 54 */ 55 private static final String IMS_SERVICE = "ims"; 56 57 /** 58 * The result code to be sent back with the incoming call {@link PendingIntent}. 59 * @see #open(PendingIntent, ImsConnectionStateListener) 60 */ 61 public static final int INCOMING_CALL_RESULT_CODE = 101; 62 63 /** 64 * Key to retrieve the call ID from an incoming call intent. 65 * @see #open(PendingIntent, ImsConnectionStateListener) 66 */ 67 public static final String EXTRA_CALL_ID = "android:imsCallID"; 68 69 /** 70 * Action to broadcast when ImsService is up. 71 * Internal use only. 72 * @hide 73 */ 74 public static final String ACTION_IMS_SERVICE_UP = 75 "com.android.ims.IMS_SERVICE_UP"; 76 77 /** 78 * Action to broadcast when ImsService is down. 79 * Internal use only. 80 * @hide 81 */ 82 public static final String ACTION_IMS_SERVICE_DOWN = 83 "com.android.ims.IMS_SERVICE_DOWN"; 84 85 /** 86 * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents. 87 * A long value; the subId corresponding to the IMS service coming up or down. 88 * Internal use only. 89 * @hide 90 */ 91 public static final String EXTRA_SUBID = "android:subid"; 92 93 /** 94 * Action for the incoming call intent for the Phone app. 95 * Internal use only. 96 * @hide 97 */ 98 public static final String ACTION_IMS_INCOMING_CALL = 99 "com.android.ims.IMS_INCOMING_CALL"; 100 101 /** 102 * Part of the ACTION_IMS_INCOMING_CALL intents. 103 * An integer value; service identifier obtained from {@link ImsManager#open}. 104 * Internal use only. 105 * @hide 106 */ 107 public static final String EXTRA_SERVICE_ID = "android:imsServiceId"; 108 109 /** 110 * Part of the ACTION_IMS_INCOMING_CALL intents. 111 * An boolean value; Flag to indicate that the incoming call is a normal call or call for USSD. 112 * The value "true" indicates that the incoming call is for USSD. 113 * Internal use only. 114 * @hide 115 */ 116 public static final String EXTRA_USSD = "android:ussd"; 117 118 119 120 private static final String TAG = "ImsManager"; 121 private static final boolean DBG = true; 122 123 private static HashMap<Long, ImsManager> sImsManagerInstances = 124 new HashMap<Long, ImsManager>(); 125 126 private Context mContext; 127 private long mSubId; 128 private IImsService mImsService = null; 129 private ImsServiceDeathRecipient mDeathRecipient = new ImsServiceDeathRecipient(); 130 // Ut interface for the supplementary service configuration 131 private ImsUt mUt = null; 132 // Interface to get/set ims config items 133 private ImsConfig mConfig = null; 134 135 /** 136 * Gets a manager instance. 137 * 138 * @param context application context for creating the manager object 139 * @param subId the subscription ID for the IMS Service 140 * @return the manager instance corresponding to the subId 141 */ 142 public static ImsManager getInstance(Context context, long subId) { 143 synchronized (sImsManagerInstances) { 144 if (sImsManagerInstances.containsKey(subId)) 145 return sImsManagerInstances.get(subId); 146 147 ImsManager mgr = new ImsManager(context, subId); 148 sImsManagerInstances.put(subId, mgr); 149 150 return mgr; 151 } 152 } 153 154 private ImsManager(Context context, long subId) { 155 mContext = context; 156 mSubId = subId; 157 createImsService(true); 158 } 159 160 /** 161 * Opens the IMS service for making calls and/or receiving generic IMS calls. 162 * The caller may make subsquent calls through {@link #makeCall}. 163 * The IMS service will register the device to the operator's network with the credentials 164 * (from ISIM) periodically in order to receive calls from the operator's network. 165 * When the IMS service receives a new call, it will send out an intent with 166 * the provided action string. 167 * The intent contains a call ID extra {@link getCallId} and it can be used to take a call. 168 * 169 * @param serviceClass a service class specified in {@link ImsServiceClass} 170 * For VoLTE service, it MUST be a {@link ImsServiceClass#MMTEL}. 171 * @param incomingCallPendingIntent When an incoming call is received, 172 * the IMS service will call {@link PendingIntent#send(Context, int, Intent)} to 173 * send back the intent to the caller with {@link #INCOMING_CALL_RESULT_CODE} 174 * as the result code and the intent to fill in the call ID; It cannot be null 175 * @param listener To listen to IMS registration events; It cannot be null 176 * @return identifier (greater than 0) for the specified service 177 * @throws NullPointerException if {@code incomingCallPendingIntent} 178 * or {@code listener} is null 179 * @throws ImsException if calling the IMS service results in an error 180 * @see #getCallId 181 * @see #getServiceId 182 */ 183 public int open(int serviceClass, PendingIntent incomingCallPendingIntent, 184 ImsConnectionStateListener listener) throws ImsException { 185 checkAndThrowExceptionIfServiceUnavailable(); 186 187 if (incomingCallPendingIntent == null) { 188 throw new NullPointerException("incomingCallPendingIntent can't be null"); 189 } 190 191 if (listener == null) { 192 throw new NullPointerException("listener can't be null"); 193 } 194 195 int result = 0; 196 197 try { 198 result = mImsService.open(serviceClass, incomingCallPendingIntent, 199 createRegistrationListenerProxy(serviceClass, listener)); 200 } catch (RemoteException e) { 201 throw new ImsException("open()", e, 202 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 203 } 204 205 if (result <= 0) { 206 // If the return value is a minus value, 207 // it means that an error occurred in the service. 208 // So, it needs to convert to the reason code specified in ImsReasonInfo. 209 throw new ImsException("open()", (result * (-1))); 210 } 211 212 return result; 213 } 214 215 /** 216 * Closes the specified service ({@link ImsServiceClass}) not to make/receive calls. 217 * All the resources that were allocated to the service are also released. 218 * 219 * @param serviceId a service id to be closed which is obtained from {@link ImsManager#open} 220 * @throws ImsException if calling the IMS service results in an error 221 */ 222 public void close(int serviceId) throws ImsException { 223 checkAndThrowExceptionIfServiceUnavailable(); 224 225 try { 226 mImsService.close(serviceId); 227 } catch (RemoteException e) { 228 throw new ImsException("close()", e, 229 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 230 } finally { 231 mUt = null; 232 mConfig = null; 233 } 234 } 235 236 /** 237 * Gets the configuration interface to provision / withdraw the supplementary service settings. 238 * 239 * @param serviceId a service id which is obtained from {@link ImsManager#open} 240 * @return the Ut interface instance 241 * @throws ImsException if getting the Ut interface results in an error 242 */ 243 public ImsUtInterface getSupplementaryServiceConfiguration(int serviceId) 244 throws ImsException { 245 // FIXME: manage the multiple Ut interfaces based on the service id 246 if (mUt == null) { 247 checkAndThrowExceptionIfServiceUnavailable(); 248 249 try { 250 IImsUt iUt = mImsService.getUtInterface(serviceId); 251 252 if (iUt == null) { 253 throw new ImsException("getSupplementaryServiceConfiguration()", 254 ImsReasonInfo.CODE_UT_NOT_SUPPORTED); 255 } 256 257 mUt = new ImsUt(iUt); 258 } catch (RemoteException e) { 259 throw new ImsException("getSupplementaryServiceConfiguration()", e, 260 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 261 } 262 } 263 264 return mUt; 265 } 266 267 /** 268 * Checks if the IMS service has successfully registered to the IMS network 269 * with the specified service & call type. 270 * 271 * @param serviceId a service id which is obtained from {@link ImsManager#open} 272 * @param serviceType a service type that is specified in {@link ImsCallProfile} 273 * {@link ImsCallProfile#SERVICE_TYPE_NORMAL} 274 * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY} 275 * @param callType a call type that is specified in {@link ImsCallProfile} 276 * {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO} 277 * {@link ImsCallProfile#CALL_TYPE_VOICE} 278 * {@link ImsCallProfile#CALL_TYPE_VT} 279 * {@link ImsCallProfile#CALL_TYPE_VS} 280 * @return true if the specified service id is connected to the IMS network; 281 * false otherwise 282 * @throws ImsException if calling the IMS service results in an error 283 */ 284 public boolean isConnected(int serviceId, int serviceType, int callType) 285 throws ImsException { 286 checkAndThrowExceptionIfServiceUnavailable(); 287 288 try { 289 return mImsService.isConnected(serviceId, serviceType, callType); 290 } catch (RemoteException e) { 291 throw new ImsException("isServiceConnected()", e, 292 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 293 } 294 } 295 296 /** 297 * Checks if the specified IMS service is opend. 298 * 299 * @param serviceId a service id which is obtained from {@link ImsManager#open} 300 * @return true if the specified service id is opened; false otherwise 301 * @throws ImsException if calling the IMS service results in an error 302 */ 303 public boolean isOpened(int serviceId) throws ImsException { 304 checkAndThrowExceptionIfServiceUnavailable(); 305 306 try { 307 return mImsService.isOpened(serviceId); 308 } catch (RemoteException e) { 309 throw new ImsException("isOpened()", e, 310 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 311 } 312 } 313 314 /** 315 * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state. 316 * 317 * @param serviceId a service id which is obtained from {@link ImsManager#open} 318 * @param serviceType a service type that is specified in {@link ImsCallProfile} 319 * {@link ImsCallProfile#SERVICE_TYPE_NONE} 320 * {@link ImsCallProfile#SERVICE_TYPE_NORMAL} 321 * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY} 322 * @param callType a call type that is specified in {@link ImsCallProfile} 323 * {@link ImsCallProfile#CALL_TYPE_VOICE} 324 * {@link ImsCallProfile#CALL_TYPE_VT} 325 * {@link ImsCallProfile#CALL_TYPE_VT_TX} 326 * {@link ImsCallProfile#CALL_TYPE_VT_RX} 327 * {@link ImsCallProfile#CALL_TYPE_VT_NODIR} 328 * {@link ImsCallProfile#CALL_TYPE_VS} 329 * {@link ImsCallProfile#CALL_TYPE_VS_TX} 330 * {@link ImsCallProfile#CALL_TYPE_VS_RX} 331 * @return a {@link ImsCallProfile} object 332 * @throws ImsException if calling the IMS service results in an error 333 */ 334 public ImsCallProfile createCallProfile(int serviceId, 335 int serviceType, int callType) throws ImsException { 336 checkAndThrowExceptionIfServiceUnavailable(); 337 338 try { 339 return mImsService.createCallProfile(serviceId, serviceType, callType); 340 } catch (RemoteException e) { 341 throw new ImsException("createCallProfile()", e, 342 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 343 } 344 } 345 346 /** 347 * Creates a {@link ImsCall} to make a call. 348 * 349 * @param serviceId a service id which is obtained from {@link ImsManager#open} 350 * @param profile a call profile to make the call 351 * (it contains service type, call type, media information, etc.) 352 * @param participants participants to invite the conference call 353 * @param listener listen to the call events from {@link ImsCall} 354 * @return a {@link ImsCall} object 355 * @throws ImsException if calling the IMS service results in an error 356 */ 357 public ImsCall makeCall(int serviceId, ImsCallProfile profile, String[] callees, 358 ImsCall.Listener listener) throws ImsException { 359 if (DBG) { 360 log("makeCall :: serviceId=" + serviceId 361 + ", profile=" + profile + ", callees=" + callees); 362 } 363 364 checkAndThrowExceptionIfServiceUnavailable(); 365 366 ImsCall call = new ImsCall(mContext, profile); 367 368 call.setListener(listener); 369 ImsCallSession session = createCallSession(serviceId, profile); 370 371 if ((callees != null) && (callees.length == 1)) { 372 call.start(session, callees[0]); 373 } else { 374 call.start(session, callees); 375 } 376 377 return call; 378 } 379 380 /** 381 * Creates a {@link ImsCall} to take an incoming call. 382 * 383 * @param serviceId a service id which is obtained from {@link ImsManager#open} 384 * @param incomingCallIntent the incoming call broadcast intent 385 * @param listener to listen to the call events from {@link ImsCall} 386 * @return a {@link ImsCall} object 387 * @throws ImsException if calling the IMS service results in an error 388 */ 389 public ImsCall takeCall(int serviceId, Intent incomingCallIntent, 390 ImsCall.Listener listener) throws ImsException { 391 if (DBG) { 392 log("takeCall :: serviceId=" + serviceId 393 + ", incomingCall=" + incomingCallIntent); 394 } 395 396 checkAndThrowExceptionIfServiceUnavailable(); 397 398 if (incomingCallIntent == null) { 399 throw new ImsException("Can't retrieve session with null intent", 400 ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT); 401 } 402 403 int incomingServiceId = getServiceId(incomingCallIntent); 404 405 if (serviceId != incomingServiceId) { 406 throw new ImsException("Service id is mismatched in the incoming call intent", 407 ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT); 408 } 409 410 String callId = getCallId(incomingCallIntent); 411 412 if (callId == null) { 413 throw new ImsException("Call ID missing in the incoming call intent", 414 ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT); 415 } 416 417 try { 418 IImsCallSession session = mImsService.getPendingCallSession(serviceId, callId); 419 420 if (session == null) { 421 throw new ImsException("No pending session for the call", 422 ImsReasonInfo.CODE_LOCAL_NO_PENDING_CALL); 423 } 424 425 ImsCall call = new ImsCall(mContext, session.getCallProfile()); 426 427 call.attachSession(new ImsCallSession(session)); 428 call.setListener(listener); 429 430 return call; 431 } catch (Throwable t) { 432 throw new ImsException("takeCall()", t, ImsReasonInfo.CODE_UNSPECIFIED); 433 } 434 } 435 436 /** 437 * Gets the config interface to get/set service/capability parameters. 438 * 439 * @return the ImsConfig instance. 440 * @throws ImsException if getting the setting interface results in an error. 441 */ 442 public ImsConfig getConfigInterface() throws ImsException { 443 444 if (mConfig == null) { 445 checkAndThrowExceptionIfServiceUnavailable(); 446 447 try { 448 IImsConfig config = mImsService.getConfigInterface(); 449 if (config == null) { 450 throw new ImsException("getConfigInterface()", 451 ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); 452 } 453 mConfig = new ImsConfig(config); 454 } catch (RemoteException e) { 455 throw new ImsException("getConfigInterface()", e, 456 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 457 } 458 } 459 if (DBG) log("getConfigInterface(), mConfig= " + mConfig); 460 return mConfig; 461 } 462 463 /** 464 * Gets the call ID from the specified incoming call broadcast intent. 465 * 466 * @param incomingCallIntent the incoming call broadcast intent 467 * @return the call ID or null if the intent does not contain it 468 */ 469 private static String getCallId(Intent incomingCallIntent) { 470 if (incomingCallIntent == null) { 471 return null; 472 } 473 474 return incomingCallIntent.getStringExtra(EXTRA_CALL_ID); 475 } 476 477 /** 478 * Gets the service type from the specified incoming call broadcast intent. 479 * 480 * @param incomingCallIntent the incoming call broadcast intent 481 * @return the service identifier or -1 if the intent does not contain it 482 */ 483 private static int getServiceId(Intent incomingCallIntent) { 484 if (incomingCallIntent == null) { 485 return (-1); 486 } 487 488 return incomingCallIntent.getIntExtra(EXTRA_SERVICE_ID, -1); 489 } 490 491 /** 492 * Binds the IMS service only if the service is not created. 493 */ 494 private void checkAndThrowExceptionIfServiceUnavailable() 495 throws ImsException { 496 if (mImsService == null) { 497 createImsService(true); 498 499 if (mImsService == null) { 500 throw new ImsException("Service is unavailable", 501 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 502 } 503 } 504 } 505 506 private static String getImsServiceName(long subId) { 507 // TODO: MSIM implementation needs to decide on service name as a function of subId 508 // or value derived from subId (slot ID?) 509 return IMS_SERVICE; 510 } 511 512 /** 513 * Binds the IMS service to make/receive the call. 514 */ 515 private void createImsService(boolean checkService) { 516 if (checkService) { 517 IBinder binder = ServiceManager.checkService(getImsServiceName(mSubId)); 518 519 if (binder == null) { 520 return; 521 } 522 } 523 524 IBinder b = ServiceManager.getService(getImsServiceName(mSubId)); 525 526 if (b != null) { 527 try { 528 b.linkToDeath(mDeathRecipient, 0); 529 } catch (RemoteException e) { 530 } 531 } 532 533 mImsService = IImsService.Stub.asInterface(b); 534 } 535 536 /** 537 * Creates a {@link ImsCallSession} with the specified call profile. 538 * Use other methods, if applicable, instead of interacting with 539 * {@link ImsCallSession} directly. 540 * 541 * @param serviceId a service id which is obtained from {@link ImsManager#open} 542 * @param profile a call profile to make the call 543 */ 544 private ImsCallSession createCallSession(int serviceId, 545 ImsCallProfile profile) throws ImsException { 546 try { 547 return new ImsCallSession(mImsService.createCallSession(serviceId, profile, null)); 548 } catch (RemoteException e) { 549 return null; 550 } 551 } 552 553 private ImsRegistrationListenerProxy createRegistrationListenerProxy(int serviceClass, 554 ImsConnectionStateListener listener) { 555 ImsRegistrationListenerProxy proxy = 556 new ImsRegistrationListenerProxy(serviceClass, listener); 557 return proxy; 558 } 559 560 private void log(String s) { 561 Rlog.d(TAG, s); 562 } 563 564 private void loge(String s) { 565 Rlog.e(TAG, s); 566 } 567 568 private void loge(String s, Throwable t) { 569 Rlog.e(TAG, s, t); 570 } 571 572 /** 573 * Used for turning on IMS.if its off already 574 */ 575 public void turnOnIms() throws ImsException { 576 try { 577 mImsService.turnOnIms(); 578 } catch (RemoteException e) { 579 throw new ImsException("turnOnIms() ", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 580 } 581 } 582 583 /** 584 * Used for turning off IMS completely in order to make the device CSFB'ed. 585 * Once turned off, all calls will be over CS. 586 */ 587 public void turnOffIms() throws ImsException { 588 try { 589 mImsService.turnOffIms(); 590 } catch (RemoteException e) { 591 throw new ImsException("turnOffIms() ", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 592 } 593 } 594 595 /** 596 * Death recipient class for monitoring IMS service. 597 */ 598 private class ImsServiceDeathRecipient implements IBinder.DeathRecipient { 599 @Override 600 public void binderDied() { 601 mImsService = null; 602 mUt = null; 603 mConfig = null; 604 605 if (mContext != null) { 606 Intent intent = new Intent(ACTION_IMS_SERVICE_DOWN); 607 intent.putExtra(EXTRA_SUBID, mSubId); 608 mContext.sendBroadcast(new Intent(intent)); 609 } 610 } 611 } 612 613 /** 614 * Adapter class for {@link IImsRegistrationListener}. 615 */ 616 private class ImsRegistrationListenerProxy extends IImsRegistrationListener.Stub { 617 private int mServiceClass; 618 private ImsConnectionStateListener mListener; 619 620 public ImsRegistrationListenerProxy(int serviceClass, 621 ImsConnectionStateListener listener) { 622 mServiceClass = serviceClass; 623 mListener = listener; 624 } 625 626 public boolean isSameProxy(int serviceClass) { 627 return (mServiceClass == serviceClass); 628 } 629 630 @Override 631 public void registrationConnected() { 632 if (DBG) { 633 log("registrationConnected ::"); 634 } 635 636 if (mListener != null) { 637 mListener.onImsConnected(); 638 } 639 } 640 641 @Override 642 public void registrationDisconnected() { 643 if (DBG) { 644 log("registrationDisconnected ::"); 645 } 646 647 if (mListener != null) { 648 mListener.onImsDisconnected(); 649 } 650 } 651 652 @Override 653 public void registrationResumed() { 654 if (DBG) { 655 log("registrationResumed ::"); 656 } 657 658 if (mListener != null) { 659 mListener.onImsResumed(); 660 } 661 } 662 663 @Override 664 public void registrationSuspended() { 665 if (DBG) { 666 log("registrationSuspended ::"); 667 } 668 669 if (mListener != null) { 670 mListener.onImsSuspended(); 671 } 672 } 673 674 @Override 675 public void registrationServiceCapabilityChanged(int serviceClass, int event) { 676 log("registrationServiceCapabilityChanged :: serviceClass=" + 677 serviceClass + ", event=" + event); 678 679 if (mListener != null) { 680 mListener.onImsConnected(); 681 } 682 } 683 684 @Override 685 public void registrationFeatureCapabilityChanged(int serviceClass, 686 int[] enabledFeatures, int[] disabledFeatures) { 687 log("registrationFeatureCapabilityChanged :: serviceClass=" + 688 serviceClass); 689 } 690 691 } 692} 693