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