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