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