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