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