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