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