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