SipManager.java revision 9db99a4dc10ac0d5d3751f03ea51c0fed217d2f8
1/* 2 * Copyright (C) 2010 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 android.net.sip; 18 19import android.app.PendingIntent; 20import android.content.Context; 21import android.content.Intent; 22import android.content.pm.PackageManager; 23import android.os.IBinder; 24import android.os.Looper; 25import android.os.RemoteException; 26import android.os.ServiceManager; 27import android.util.Log; 28 29import java.text.ParseException; 30 31/** 32 * The class provides API for various SIP related tasks. Specifically, the API 33 * allows an application to: 34 * <ul> 35 * <li>open a {@link SipProfile} to get ready for making outbound calls or have 36 * the background SIP service listen to incoming calls and broadcast them 37 * with registered command string. See 38 * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}, 39 * {@link #open(SipProfile)}, {@link #close}, {@link #isOpened} and 40 * {@link #isRegistered}. It also facilitates handling of the incoming call 41 * broadcast intent. See 42 * {@link #isIncomingCallIntent}, {@link #getCallId}, 43 * {@link #getOfferSessionDescription} and {@link #takeAudioCall}.</li> 44 * <li>make/take SIP-based audio calls. See 45 * {@link #makeAudioCall} and {@link #takeAudioCall}.</li> 46 * <li>register/unregister with a SIP service provider manually. See 47 * {@link #register} and {@link #unregister}.</li> 48 * <li>process SIP events directly with a {@link SipSession} created by 49 * {@link #createSipSession}.</li> 50 * </ul> 51 * @hide 52 */ 53public class SipManager { 54 /** 55 * The result code to be sent back with the incoming call 56 * {@link PendingIntent}. 57 * @see #open(SipProfile, PendingIntent, SipRegistrationListener) 58 */ 59 public static final int INCOMING_CALL_RESULT_CODE = 101; 60 61 /** Part of the incoming call intent. */ 62 public static final String EXTRA_CALL_ID = "android:sipCallID"; 63 64 /** Part of the incoming call intent. */ 65 public static final String EXTRA_OFFER_SD = "android:sipOfferSD"; 66 67 /** 68 * Action to broadcast when SipService is up. 69 * Internal use only. 70 * @hide 71 */ 72 public static final String ACTION_SIP_SERVICE_UP = 73 "android.net.sip.SIP_SERVICE_UP"; 74 /** 75 * Action string for the incoming call intent for the Phone app. 76 * Internal use only. 77 * @hide 78 */ 79 public static final String ACTION_SIP_INCOMING_CALL = 80 "com.android.phone.SIP_INCOMING_CALL"; 81 /** 82 * Action string for the add-phone intent. 83 * Internal use only. 84 * @hide 85 */ 86 public static final String ACTION_SIP_ADD_PHONE = 87 "com.android.phone.SIP_ADD_PHONE"; 88 /** 89 * Action string for the remove-phone intent. 90 * Internal use only. 91 * @hide 92 */ 93 public static final String ACTION_SIP_REMOVE_PHONE = 94 "com.android.phone.SIP_REMOVE_PHONE"; 95 /** 96 * Part of the ACTION_SIP_ADD_PHONE and ACTION_SIP_REMOVE_PHONE intents. 97 * Internal use only. 98 * @hide 99 */ 100 public static final String EXTRA_LOCAL_URI = "android:localSipUri"; 101 102 private static final String TAG = "SipManager"; 103 104 private ISipService mSipService; 105 private Context mContext; 106 107 /** 108 * Creates a manager instance. Returns null if SIP API is not supported. 109 * 110 * @param context application context for creating the manager object 111 * @return the manager instance or null if SIP API is not supported 112 */ 113 public static SipManager newInstance(Context context) { 114 return (isApiSupported(context) ? new SipManager(context) : null); 115 } 116 117 /** 118 * Returns true if the SIP API is supported by the system. 119 */ 120 public static boolean isApiSupported(Context context) { 121 return true; 122 /* TODO: uncomment this before ship 123 return context.getPackageManager().hasSystemFeature( 124 PackageManager.FEATURE_SIP); 125 */ 126 } 127 128 /** 129 * Returns true if the system supports SIP-based VoIP. 130 */ 131 public static boolean isVoipSupported(Context context) { 132 return true; 133 /* TODO: uncomment this before ship 134 return context.getPackageManager().hasSystemFeature( 135 PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context); 136 */ 137 } 138 139 /** 140 * Returns true if SIP is only available on WIFI. 141 */ 142 public static boolean isSipWifiOnly(Context context) { 143 return context.getResources().getBoolean( 144 com.android.internal.R.bool.config_sip_wifi_only); 145 } 146 147 private SipManager(Context context) { 148 mContext = context; 149 createSipService(); 150 } 151 152 private void createSipService() { 153 IBinder b = ServiceManager.getService(Context.SIP_SERVICE); 154 mSipService = ISipService.Stub.asInterface(b); 155 } 156 157 /** 158 * Opens the profile for making calls. The caller may make subsequent calls 159 * through {@link #makeAudioCall}. If one also wants to receive calls on the 160 * profile, use 161 * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} 162 * instead. 163 * 164 * @param localProfile the SIP profile to make calls from 165 * @throws SipException if the profile contains incorrect settings or 166 * calling the SIP service results in an error 167 */ 168 public void open(SipProfile localProfile) throws SipException { 169 try { 170 mSipService.open(localProfile); 171 } catch (RemoteException e) { 172 throw new SipException("open()", e); 173 } 174 } 175 176 /** 177 * Opens the profile for making calls and/or receiving calls. The caller may 178 * make subsequent calls through {@link #makeAudioCall}. If the 179 * auto-registration option is enabled in the profile, the SIP service 180 * will register the profile to the corresponding SIP provider periodically 181 * in order to receive calls from the provider. 182 * 183 * @param localProfile the SIP profile to receive incoming calls for 184 * @param incomingCallPendingIntent When an incoming call is received, the 185 * SIP service will call 186 * {@link PendingIntent#send(Context, int, Intent)} to send back the 187 * intent to the caller with {@link #INCOMING_CALL_RESULT_CODE} as the 188 * result code and the intent to fill in the call ID and session 189 * description information. It cannot be null. 190 * @param listener to listen to registration events; can be null 191 * @see #getCallId 192 * @see #getOfferSessionDescription 193 * @see #takeAudioCall 194 * @throws NullPointerException if {@code incomingCallPendingIntent} is null 195 * @throws SipException if the profile contains incorrect settings or 196 * calling the SIP service results in an error 197 */ 198 public void open(SipProfile localProfile, 199 PendingIntent incomingCallPendingIntent, 200 SipRegistrationListener listener) throws SipException { 201 if (incomingCallPendingIntent == null) { 202 throw new NullPointerException( 203 "incomingCallPendingIntent cannot be null"); 204 } 205 try { 206 mSipService.open3(localProfile, incomingCallPendingIntent, 207 createRelay(listener, localProfile.getUriString())); 208 } catch (RemoteException e) { 209 throw new SipException("open()", e); 210 } 211 } 212 213 /** 214 * Sets the listener to listen to registration events. No effect if the 215 * profile has not been opened to receive calls (see 216 * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}). 217 * 218 * @param localProfileUri the URI of the profile 219 * @param listener to listen to registration events; can be null 220 * @throws SipException if calling the SIP service results in an error 221 */ 222 public void setRegistrationListener(String localProfileUri, 223 SipRegistrationListener listener) throws SipException { 224 try { 225 mSipService.setRegistrationListener( 226 localProfileUri, createRelay(listener, localProfileUri)); 227 } catch (RemoteException e) { 228 throw new SipException("setRegistrationListener()", e); 229 } 230 } 231 232 /** 233 * Closes the specified profile to not make/receive calls. All the resources 234 * that were allocated to the profile are also released. 235 * 236 * @param localProfileUri the URI of the profile to close 237 * @throws SipException if calling the SIP service results in an error 238 */ 239 public void close(String localProfileUri) throws SipException { 240 try { 241 mSipService.close(localProfileUri); 242 } catch (RemoteException e) { 243 throw new SipException("close()", e); 244 } 245 } 246 247 /** 248 * Checks if the specified profile is opened in the SIP service for 249 * making and/or receiving calls. 250 * 251 * @param localProfileUri the URI of the profile in question 252 * @return true if the profile is enabled to receive calls 253 * @throws SipException if calling the SIP service results in an error 254 */ 255 public boolean isOpened(String localProfileUri) throws SipException { 256 try { 257 return mSipService.isOpened(localProfileUri); 258 } catch (RemoteException e) { 259 throw new SipException("isOpened()", e); 260 } 261 } 262 263 /** 264 * Checks if the SIP service has successfully registered the profile to the 265 * SIP provider (specified in the profile) for receiving calls. Returning 266 * true from this method also implies the profile is opened 267 * ({@link #isOpened}). 268 * 269 * @param localProfileUri the URI of the profile in question 270 * @return true if the profile is registered to the SIP provider; false if 271 * the profile has not been opened in the SIP service or the SIP 272 * service has not yet successfully registered the profile to the SIP 273 * provider 274 * @throws SipException if calling the SIP service results in an error 275 */ 276 public boolean isRegistered(String localProfileUri) throws SipException { 277 try { 278 return mSipService.isRegistered(localProfileUri); 279 } catch (RemoteException e) { 280 throw new SipException("isRegistered()", e); 281 } 282 } 283 284 /** 285 * Creates a {@link SipAudioCall} to make a call. The attempt will be timed 286 * out if the call is not established within {@code timeout} seconds and 287 * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 288 * will be called. 289 * 290 * @param localProfile the SIP profile to make the call from 291 * @param peerProfile the SIP profile to make the call to 292 * @param listener to listen to the call events from {@link SipAudioCall}; 293 * can be null 294 * @param timeout the timeout value in seconds 295 * @return a {@link SipAudioCall} object 296 * @throws SipException if calling the SIP service results in an error 297 * @see SipAudioCall.Listener.onError 298 */ 299 public SipAudioCall makeAudioCall(SipProfile localProfile, 300 SipProfile peerProfile, SipAudioCall.Listener listener, int timeout) 301 throws SipException { 302 SipAudioCall call = new SipAudioCall(mContext, localProfile); 303 call.setListener(listener); 304 SipSession s = createSipSession(localProfile, null); 305 if (s == null) { 306 throw new SipException( 307 "Failed to create SipSession; network available?"); 308 } 309 call.makeCall(peerProfile, s, timeout); 310 return call; 311 } 312 313 /** 314 * Creates a {@link SipAudioCall} to make an audio call. The attempt will be 315 * timed out if the call is not established within {@code timeout} seconds 316 * and 317 * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 318 * will be called. 319 * 320 * @param localProfileUri URI of the SIP profile to make the call from 321 * @param peerProfileUri URI of the SIP profile to make the call to 322 * @param listener to listen to the call events from {@link SipAudioCall}; 323 * can be null 324 * @param timeout the timeout value in seconds 325 * @return a {@link SipAudioCall} object 326 * @throws SipException if calling the SIP service results in an error 327 * @see SipAudioCall.Listener.onError 328 */ 329 public SipAudioCall makeAudioCall(String localProfileUri, 330 String peerProfileUri, SipAudioCall.Listener listener, int timeout) 331 throws SipException { 332 try { 333 return makeAudioCall( 334 new SipProfile.Builder(localProfileUri).build(), 335 new SipProfile.Builder(peerProfileUri).build(), listener, 336 timeout); 337 } catch (ParseException e) { 338 throw new SipException("build SipProfile", e); 339 } 340 } 341 342 /** 343 * The method calls {@code takeAudioCall(incomingCallIntent, 344 * listener, true}. 345 * 346 * @see #takeAudioCall(Intent, SipAudioCall.Listener, boolean) 347 */ 348 public SipAudioCall takeAudioCall(Intent incomingCallIntent, 349 SipAudioCall.Listener listener) throws SipException { 350 return takeAudioCall(incomingCallIntent, listener, true); 351 } 352 353 /** 354 * Creates a {@link SipAudioCall} to take an incoming call. Before the call 355 * is returned, the listener will receive a 356 * {@link SipAudioCall.Listener#onRinging} 357 * callback. 358 * 359 * @param incomingCallIntent the incoming call broadcast intent 360 * @param listener to listen to the call events from {@link SipAudioCall}; 361 * can be null 362 * @return a {@link SipAudioCall} object 363 * @throws SipException if calling the SIP service results in an error 364 */ 365 public SipAudioCall takeAudioCall(Intent incomingCallIntent, 366 SipAudioCall.Listener listener, boolean ringtoneEnabled) 367 throws SipException { 368 if (incomingCallIntent == null) return null; 369 370 String callId = getCallId(incomingCallIntent); 371 if (callId == null) { 372 throw new SipException("Call ID missing in incoming call intent"); 373 } 374 375 String offerSd = getOfferSessionDescription(incomingCallIntent); 376 if (offerSd == null) { 377 throw new SipException("Session description missing in incoming " 378 + "call intent"); 379 } 380 381 try { 382 ISipSession session = mSipService.getPendingSession(callId); 383 if (session == null) return null; 384 SipAudioCall call = new SipAudioCall( 385 mContext, session.getLocalProfile()); 386 call.setRingtoneEnabled(ringtoneEnabled); 387 call.attachCall(new SipSession(session), offerSd); 388 call.setListener(listener); 389 return call; 390 } catch (Throwable t) { 391 throw new SipException("takeAudioCall()", t); 392 } 393 } 394 395 /** 396 * Checks if the intent is an incoming call broadcast intent. 397 * 398 * @param intent the intent in question 399 * @return true if the intent is an incoming call broadcast intent 400 */ 401 public static boolean isIncomingCallIntent(Intent intent) { 402 if (intent == null) return false; 403 String callId = getCallId(intent); 404 String offerSd = getOfferSessionDescription(intent); 405 return ((callId != null) && (offerSd != null)); 406 } 407 408 /** 409 * Gets the call ID from the specified incoming call broadcast intent. 410 * 411 * @param incomingCallIntent the incoming call broadcast intent 412 * @return the call ID or null if the intent does not contain it 413 */ 414 public static String getCallId(Intent incomingCallIntent) { 415 return incomingCallIntent.getStringExtra(EXTRA_CALL_ID); 416 } 417 418 /** 419 * Gets the offer session description from the specified incoming call 420 * broadcast intent. 421 * 422 * @param incomingCallIntent the incoming call broadcast intent 423 * @return the offer session description or null if the intent does not 424 * have it 425 */ 426 public static String getOfferSessionDescription(Intent incomingCallIntent) { 427 return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD); 428 } 429 430 /** 431 * Creates an incoming call broadcast intent. 432 * 433 * @param callId the call ID of the incoming call 434 * @param sessionDescription the session description of the incoming call 435 * @return the incoming call intent 436 * @hide 437 */ 438 public static Intent createIncomingCallBroadcast(String callId, 439 String sessionDescription) { 440 Intent intent = new Intent(); 441 intent.putExtra(EXTRA_CALL_ID, callId); 442 intent.putExtra(EXTRA_OFFER_SD, sessionDescription); 443 return intent; 444 } 445 446 /** 447 * Manually registers the profile to the corresponding SIP provider for 448 * receiving calls. 449 * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is 450 * still needed to be called at least once in order for the SIP service to 451 * notify the caller with the {@code PendingIntent} when an incoming call is 452 * received. 453 * 454 * @param localProfile the SIP profile to register with 455 * @param expiryTime registration expiration time (in seconds) 456 * @param listener to listen to the registration events 457 * @throws SipException if calling the SIP service results in an error 458 */ 459 public void register(SipProfile localProfile, int expiryTime, 460 SipRegistrationListener listener) throws SipException { 461 try { 462 ISipSession session = mSipService.createSession(localProfile, 463 createRelay(listener, localProfile.getUriString())); 464 session.register(expiryTime); 465 } catch (RemoteException e) { 466 throw new SipException("register()", e); 467 } 468 } 469 470 /** 471 * Manually unregisters the profile from the corresponding SIP provider for 472 * stop receiving further calls. This may interference with the auto 473 * registration process in the SIP service if the auto-registration option 474 * in the profile is enabled. 475 * 476 * @param localProfile the SIP profile to register with 477 * @param listener to listen to the registration events 478 * @throws SipException if calling the SIP service results in an error 479 */ 480 public void unregister(SipProfile localProfile, 481 SipRegistrationListener listener) throws SipException { 482 try { 483 ISipSession session = mSipService.createSession(localProfile, 484 createRelay(listener, localProfile.getUriString())); 485 session.unregister(); 486 } catch (RemoteException e) { 487 throw new SipException("unregister()", e); 488 } 489 } 490 491 /** 492 * Gets the {@link ISipSession} that handles the incoming call. For audio 493 * calls, consider to use {@link SipAudioCall} to handle the incoming call. 494 * See {@link #takeAudioCall}. Note that the method may be called only once 495 * for the same intent. For subsequent calls on the same intent, the method 496 * returns null. 497 * 498 * @param incomingCallIntent the incoming call broadcast intent 499 * @return the session object that handles the incoming call 500 */ 501 public ISipSession getSessionFor(Intent incomingCallIntent) 502 throws SipException { 503 try { 504 String callId = getCallId(incomingCallIntent); 505 return mSipService.getPendingSession(callId); 506 } catch (RemoteException e) { 507 throw new SipException("getSessionFor()", e); 508 } 509 } 510 511 private static ISipSessionListener createRelay( 512 SipRegistrationListener listener, String uri) { 513 return ((listener == null) ? null : new ListenerRelay(listener, uri)); 514 } 515 516 /** 517 * Creates a {@link ISipSession} with the specified profile. Use other 518 * methods, if applicable, instead of interacting with {@link ISipSession} 519 * directly. 520 * 521 * @param localProfile the SIP profile the session is associated with 522 * @param listener to listen to SIP session events 523 */ 524 public SipSession createSipSession(SipProfile localProfile, 525 SipSession.Listener listener) throws SipException { 526 try { 527 ISipSession s = mSipService.createSession(localProfile, null); 528 return new SipSession(s, listener); 529 } catch (RemoteException e) { 530 throw new SipException("createSipSession()", e); 531 } 532 } 533 534 /** 535 * Gets the list of profiles hosted by the SIP service. The user information 536 * (username, password and display name) are crossed out. 537 * @hide 538 */ 539 public SipProfile[] getListOfProfiles() { 540 try { 541 return mSipService.getListOfProfiles(); 542 } catch (RemoteException e) { 543 return null; 544 } 545 } 546 547 private static class ListenerRelay extends SipSessionAdapter { 548 private SipRegistrationListener mListener; 549 private String mUri; 550 551 // listener must not be null 552 public ListenerRelay(SipRegistrationListener listener, String uri) { 553 mListener = listener; 554 mUri = uri; 555 } 556 557 private String getUri(ISipSession session) { 558 try { 559 return ((session == null) 560 ? mUri 561 : session.getLocalProfile().getUriString()); 562 } catch (Throwable e) { 563 // SipService died? SIP stack died? 564 Log.w(TAG, "getUri(): " + e); 565 return null; 566 } 567 } 568 569 @Override 570 public void onRegistering(ISipSession session) { 571 mListener.onRegistering(getUri(session)); 572 } 573 574 @Override 575 public void onRegistrationDone(ISipSession session, int duration) { 576 long expiryTime = duration; 577 if (duration > 0) expiryTime += System.currentTimeMillis(); 578 mListener.onRegistrationDone(getUri(session), expiryTime); 579 } 580 581 @Override 582 public void onRegistrationFailed(ISipSession session, int errorCode, 583 String message) { 584 mListener.onRegistrationFailed(getUri(session), errorCode, message); 585 } 586 587 @Override 588 public void onRegistrationTimeout(ISipSession session) { 589 mListener.onRegistrationFailed(getUri(session), 590 SipErrorCode.TIME_OUT, "registration timed out"); 591 } 592 } 593} 594