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