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