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. 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 309 * @see SipAudioCall.Listener#onError 310 */ 311 public SipAudioCall makeAudioCall(SipProfile localProfile, 312 SipProfile peerProfile, SipAudioCall.Listener listener, int timeout) 313 throws SipException { 314 SipAudioCall call = new SipAudioCall(mContext, localProfile); 315 call.setListener(listener); 316 SipSession s = createSipSession(localProfile, null); 317 if (s == null) { 318 throw new SipException( 319 "Failed to create SipSession; network available?"); 320 } 321 call.makeCall(peerProfile, s, timeout); 322 return call; 323 } 324 325 /** 326 * Creates a {@link SipAudioCall} to make an audio call. The attempt will be 327 * timed out if the call is not established within {@code timeout} seconds 328 * and 329 * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 330 * will be called. 331 * 332 * @param localProfileUri URI of the SIP profile to make the call from 333 * @param peerProfileUri URI of the SIP profile to make the call to 334 * @param listener to listen to the call events from {@link SipAudioCall}; 335 * can be null 336 * @param timeout the timeout value in seconds. Default value (defined by 337 * SIP protocol) is used if {@code timeout} is zero or negative. 338 * @return a {@link SipAudioCall} object 339 * @throws SipException if calling the SIP service results in an error 340 * @see SipAudioCall.Listener#onError 341 */ 342 public SipAudioCall makeAudioCall(String localProfileUri, 343 String peerProfileUri, SipAudioCall.Listener listener, int timeout) 344 throws SipException { 345 try { 346 return makeAudioCall( 347 new SipProfile.Builder(localProfileUri).build(), 348 new SipProfile.Builder(peerProfileUri).build(), listener, 349 timeout); 350 } catch (ParseException e) { 351 throw new SipException("build SipProfile", e); 352 } 353 } 354 355 /** 356 * Creates a {@link SipAudioCall} to take an incoming call. Before the call 357 * is returned, the listener will receive a 358 * {@link SipAudioCall.Listener#onRinging} 359 * callback. 360 * 361 * @param incomingCallIntent the incoming call broadcast intent 362 * @param listener to listen to the call events from {@link SipAudioCall}; 363 * can be null 364 * @return a {@link SipAudioCall} object 365 * @throws SipException if calling the SIP service results in an error 366 */ 367 public SipAudioCall takeAudioCall(Intent incomingCallIntent, 368 SipAudioCall.Listener listener) throws SipException { 369 if (incomingCallIntent == null) return null; 370 371 String callId = getCallId(incomingCallIntent); 372 if (callId == null) { 373 throw new SipException("Call ID missing in incoming call intent"); 374 } 375 376 String offerSd = getOfferSessionDescription(incomingCallIntent); 377 if (offerSd == null) { 378 throw new SipException("Session description missing in incoming " 379 + "call intent"); 380 } 381 382 try { 383 ISipSession session = mSipService.getPendingSession(callId); 384 if (session == null) return null; 385 SipAudioCall call = new SipAudioCall( 386 mContext, session.getLocalProfile()); 387 call.attachCall(new SipSession(session), offerSd); 388 call.setListener(listener); 389 return call; 390 } catch (Throwable t) { 391 throw new SipException("takeAudioCall()", t); 392 } 393 } 394 395 /** 396 * Checks if the intent is an incoming call broadcast intent. 397 * 398 * @param intent the intent in question 399 * @return true if the intent is an incoming call broadcast intent 400 */ 401 public static boolean isIncomingCallIntent(Intent intent) { 402 if (intent == null) return false; 403 String callId = getCallId(intent); 404 String offerSd = getOfferSessionDescription(intent); 405 return ((callId != null) && (offerSd != null)); 406 } 407 408 /** 409 * Gets the call ID from the specified incoming call broadcast intent. 410 * 411 * @param incomingCallIntent the incoming call broadcast intent 412 * @return the call ID or null if the intent does not contain it 413 */ 414 public static String getCallId(Intent incomingCallIntent) { 415 return incomingCallIntent.getStringExtra(EXTRA_CALL_ID); 416 } 417 418 /** 419 * Gets the offer session description from the specified incoming call 420 * broadcast intent. 421 * 422 * @param incomingCallIntent the incoming call broadcast intent 423 * @return the offer session description or null if the intent does not 424 * have it 425 */ 426 public static String getOfferSessionDescription(Intent incomingCallIntent) { 427 return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD); 428 } 429 430 /** 431 * Creates an incoming call broadcast intent. 432 * 433 * @param callId the call ID of the incoming call 434 * @param sessionDescription the session description of the incoming call 435 * @return the incoming call intent 436 * @hide 437 */ 438 public static Intent createIncomingCallBroadcast(String callId, 439 String sessionDescription) { 440 Intent intent = new Intent(); 441 intent.putExtra(EXTRA_CALL_ID, callId); 442 intent.putExtra(EXTRA_OFFER_SD, sessionDescription); 443 return intent; 444 } 445 446 /** 447 * Manually registers the profile to the corresponding SIP provider for 448 * receiving calls. 449 * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is 450 * still needed to be called at least once in order for the SIP service to 451 * notify the caller with the {@link android.app.PendingIntent} when an incoming call is 452 * received. 453 * 454 * @param localProfile the SIP profile to register with 455 * @param expiryTime registration expiration time (in seconds) 456 * @param listener to listen to the registration events 457 * @throws SipException if calling the SIP service results in an error 458 */ 459 public void register(SipProfile localProfile, int expiryTime, 460 SipRegistrationListener listener) throws SipException { 461 try { 462 ISipSession session = mSipService.createSession(localProfile, 463 createRelay(listener, localProfile.getUriString())); 464 session.register(expiryTime); 465 } catch (RemoteException e) { 466 throw new SipException("register()", e); 467 } 468 } 469 470 /** 471 * Manually unregisters the profile from the corresponding SIP provider for 472 * stop receiving further calls. This may interference with the auto 473 * registration process in the SIP service if the auto-registration option 474 * in the profile is enabled. 475 * 476 * @param localProfile the SIP profile to register with 477 * @param listener to listen to the registration events 478 * @throws SipException if calling the SIP service results in an error 479 */ 480 public void unregister(SipProfile localProfile, 481 SipRegistrationListener listener) throws SipException { 482 try { 483 ISipSession session = mSipService.createSession(localProfile, 484 createRelay(listener, localProfile.getUriString())); 485 session.unregister(); 486 } catch (RemoteException e) { 487 throw new SipException("unregister()", e); 488 } 489 } 490 491 /** 492 * Gets the {@link SipSession} that handles the incoming call. For audio 493 * calls, consider to use {@link SipAudioCall} to handle the incoming call. 494 * See {@link #takeAudioCall}. Note that the method may be called only once 495 * for the same intent. For subsequent calls on the same intent, the method 496 * returns null. 497 * 498 * @param incomingCallIntent the incoming call broadcast intent 499 * @return the session object that handles the incoming call 500 */ 501 public SipSession getSessionFor(Intent incomingCallIntent) 502 throws SipException { 503 try { 504 String callId = getCallId(incomingCallIntent); 505 ISipSession s = mSipService.getPendingSession(callId); 506 return new SipSession(s); 507 } catch (RemoteException e) { 508 throw new SipException("getSessionFor()", e); 509 } 510 } 511 512 private static ISipSessionListener createRelay( 513 SipRegistrationListener listener, String uri) { 514 return ((listener == null) ? null : new ListenerRelay(listener, uri)); 515 } 516 517 /** 518 * Creates a {@link SipSession} with the specified profile. Use other 519 * methods, if applicable, instead of interacting with {@link SipSession} 520 * directly. 521 * 522 * @param localProfile the SIP profile the session is associated with 523 * @param listener to listen to SIP session events 524 */ 525 public SipSession createSipSession(SipProfile localProfile, 526 SipSession.Listener listener) throws SipException { 527 try { 528 ISipSession s = mSipService.createSession(localProfile, null); 529 return new SipSession(s, listener); 530 } catch (RemoteException e) { 531 throw new SipException("createSipSession()", e); 532 } 533 } 534 535 /** 536 * Gets the list of profiles hosted by the SIP service. The user information 537 * (username, password and display name) are crossed out. 538 * @hide 539 */ 540 public SipProfile[] getListOfProfiles() { 541 try { 542 return mSipService.getListOfProfiles(); 543 } catch (RemoteException e) { 544 return null; 545 } 546 } 547 548 private static class ListenerRelay extends SipSessionAdapter { 549 private SipRegistrationListener mListener; 550 private String mUri; 551 552 // listener must not be null 553 public ListenerRelay(SipRegistrationListener listener, String uri) { 554 mListener = listener; 555 mUri = uri; 556 } 557 558 private String getUri(ISipSession session) { 559 try { 560 return ((session == null) 561 ? mUri 562 : session.getLocalProfile().getUriString()); 563 } catch (Throwable e) { 564 // SipService died? SIP stack died? 565 Log.w(TAG, "getUri(): " + e); 566 return null; 567 } 568 } 569 570 @Override 571 public void onRegistering(ISipSession session) { 572 mListener.onRegistering(getUri(session)); 573 } 574 575 @Override 576 public void onRegistrationDone(ISipSession session, int duration) { 577 long expiryTime = duration; 578 if (duration > 0) expiryTime += System.currentTimeMillis(); 579 mListener.onRegistrationDone(getUri(session), expiryTime); 580 } 581 582 @Override 583 public void onRegistrationFailed(ISipSession session, int errorCode, 584 String message) { 585 mListener.onRegistrationFailed(getUri(session), errorCode, message); 586 } 587 588 @Override 589 public void onRegistrationTimeout(ISipSession session) { 590 mListener.onRegistrationFailed(getUri(session), 591 SipErrorCode.TIME_OUT, "registration timed out"); 592 } 593 } 594 } 595