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