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