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