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 17 package android.net.sip; 18 19 import android.app.PendingIntent; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.PackageManager; 23 import android.os.IBinder; 24 import android.os.RemoteException; 25 import android.os.ServiceManager; 26 import android.telephony.Rlog; 27 28 import java.text.ParseException; 29 30 /** 31 * Provides APIs for SIP tasks, such as initiating SIP connections, and provides access to related 32 * SIP services. This class is the starting point for any SIP actions. You can acquire an instance 33 * of it with {@link #newInstance newInstance()}.</p> 34 * <p>The APIs in this class allows you to:</p> 35 * <ul> 36 * <li>Create a {@link SipSession} to get ready for making calls or listen for incoming calls. See 37 * {@link #createSipSession createSipSession()} and {@link #getSessionFor getSessionFor()}.</li> 38 * <li>Initiate and receive generic SIP calls or audio-only SIP calls. Generic SIP calls may 39 * be video, audio, or other, and are initiated with {@link #open open()}. Audio-only SIP calls 40 * should be handled with a {@link SipAudioCall}, which you can acquire with {@link 41 * #makeAudioCall makeAudioCall()} and {@link #takeAudioCall takeAudioCall()}.</li> 42 * <li>Register and unregister with a SIP service provider, with 43 * {@link #register register()} and {@link #unregister unregister()}.</li> 44 * <li>Verify session connectivity, with {@link #isOpened isOpened()} and 45 * {@link #isRegistered isRegistered()}.</li> 46 * </ul> 47 * <p class="note"><strong>Note:</strong> Not all Android-powered devices support VOIP calls using 48 * SIP. You should always call {@link android.net.sip.SipManager#isVoipSupported 49 * isVoipSupported()} to verify that the device supports VOIP calling and {@link 50 * android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports 51 * the SIP APIs. Your application must also request the {@link 52 * android.Manifest.permission#INTERNET} and {@link android.Manifest.permission#USE_SIP} 53 * permissions.</p> 54 * 55 * <div class="special reference"> 56 * <h3>Developer Guides</h3> 57 * <p>For more information about using SIP, read the 58 * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a> 59 * developer guide.</p> 60 * </div> 61 */ 62 public class SipManager { 63 /** 64 * The result code to be sent back with the incoming call 65 * {@link PendingIntent}. 66 * @see #open(SipProfile, PendingIntent, SipRegistrationListener) 67 */ 68 public static final int INCOMING_CALL_RESULT_CODE = 101; 69 70 /** 71 * Key to retrieve the call ID from an incoming call intent. 72 * @see #open(SipProfile, PendingIntent, SipRegistrationListener) 73 */ 74 public static final String EXTRA_CALL_ID = "android:sipCallID"; 75 76 /** 77 * Key to retrieve the offered session description from an incoming call 78 * intent. 79 * @see #open(SipProfile, PendingIntent, SipRegistrationListener) 80 */ 81 public static final String EXTRA_OFFER_SD = "android:sipOfferSD"; 82 83 /** 84 * Action to broadcast when SipService is up. 85 * Internal use only. 86 * @hide 87 */ 88 public static final String ACTION_SIP_SERVICE_UP = 89 "android.net.sip.SIP_SERVICE_UP"; 90 /** 91 * Action string for the incoming call intent for the Phone app. 92 * Internal use only. 93 * @hide 94 */ 95 public static final String ACTION_SIP_INCOMING_CALL = 96 "com.android.phone.SIP_INCOMING_CALL"; 97 /** 98 * Action string for the add-phone intent. 99 * Internal use only. 100 * @hide 101 */ 102 public static final String ACTION_SIP_ADD_PHONE = 103 "com.android.phone.SIP_ADD_PHONE"; 104 /** 105 * Action string for the remove-phone intent. 106 * Internal use only. 107 * @hide 108 */ 109 public static final String ACTION_SIP_REMOVE_PHONE = 110 "com.android.phone.SIP_REMOVE_PHONE"; 111 112 /** 113 * Action string for the SIP call option configuration changed intent. 114 * This is used to communicate change to the SIP call option, triggering re-registration of 115 * the SIP phone accounts. 116 * Internal use only. 117 * @hide 118 */ 119 public static final String ACTION_SIP_CALL_OPTION_CHANGED = 120 "com.android.phone.SIP_CALL_OPTION_CHANGED"; 121 122 /** 123 * Part of the ACTION_SIP_ADD_PHONE and ACTION_SIP_REMOVE_PHONE intents. 124 * Internal use only. 125 * @hide 126 */ 127 public static final String EXTRA_LOCAL_URI = "android:localSipUri"; 128 129 private static final String TAG = "SipManager"; 130 131 private ISipService mSipService; 132 private Context mContext; 133 134 /** 135 * Creates a manager instance. Returns null if SIP API is not supported. 136 * 137 * @param context application context for creating the manager object 138 * @return the manager instance or null if SIP API is not supported 139 */ newInstance(Context context)140 public static SipManager newInstance(Context context) { 141 return (isApiSupported(context) ? new SipManager(context) : null); 142 } 143 144 /** 145 * Returns true if the SIP API is supported by the system. 146 */ isApiSupported(Context context)147 public static boolean isApiSupported(Context context) { 148 return context.getPackageManager().hasSystemFeature( 149 PackageManager.FEATURE_SIP); 150 } 151 152 /** 153 * Returns true if the system supports SIP-based VOIP API. 154 */ isVoipSupported(Context context)155 public static boolean isVoipSupported(Context context) { 156 return context.getPackageManager().hasSystemFeature( 157 PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context); 158 } 159 160 /** 161 * Returns true if SIP is only available on WIFI. 162 */ isSipWifiOnly(Context context)163 public static boolean isSipWifiOnly(Context context) { 164 return context.getResources().getBoolean( 165 com.android.internal.R.bool.config_sip_wifi_only); 166 } 167 SipManager(Context context)168 private SipManager(Context context) { 169 mContext = context; 170 createSipService(); 171 } 172 createSipService()173 private void createSipService() { 174 IBinder b = ServiceManager.getService(Context.SIP_SERVICE); 175 mSipService = ISipService.Stub.asInterface(b); 176 } 177 178 /** 179 * Opens the profile for making generic SIP calls. The caller may make subsequent calls 180 * through {@link #makeAudioCall}. If one also wants to receive calls on the 181 * profile, use 182 * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} 183 * instead. 184 * 185 * @param localProfile the SIP profile to make calls from 186 * @throws SipException if the profile contains incorrect settings or 187 * calling the SIP service results in an error 188 */ open(SipProfile localProfile)189 public void open(SipProfile localProfile) throws SipException { 190 try { 191 mSipService.open(localProfile, mContext.getOpPackageName()); 192 } catch (RemoteException e) { 193 throw new SipException("open()", e); 194 } 195 } 196 197 /** 198 * Opens the profile for making calls and/or receiving generic SIP calls. The caller may 199 * make subsequent calls through {@link #makeAudioCall}. If the 200 * auto-registration option is enabled in the profile, the SIP service 201 * will register the profile to the corresponding SIP provider periodically 202 * in order to receive calls from the provider. When the SIP service 203 * receives a new call, it will send out an intent with the provided action 204 * string. The intent contains a call ID extra and an offer session 205 * description string extra. Use {@link #getCallId} and 206 * {@link #getOfferSessionDescription} to retrieve those extras. 207 * 208 * @param localProfile the SIP profile to receive incoming calls for 209 * @param incomingCallPendingIntent When an incoming call is received, the 210 * SIP service will call 211 * {@link PendingIntent#send(Context, int, Intent)} to send back the 212 * intent to the caller with {@link #INCOMING_CALL_RESULT_CODE} as the 213 * result code and the intent to fill in the call ID and session 214 * description information. It cannot be null. 215 * @param listener to listen to registration events; can be null 216 * @see #getCallId 217 * @see #getOfferSessionDescription 218 * @see #takeAudioCall 219 * @throws NullPointerException if {@code incomingCallPendingIntent} is null 220 * @throws SipException if the profile contains incorrect settings or 221 * calling the SIP service results in an error 222 * @see #isIncomingCallIntent 223 * @see #getCallId 224 * @see #getOfferSessionDescription 225 */ open(SipProfile localProfile, PendingIntent incomingCallPendingIntent, SipRegistrationListener listener)226 public void open(SipProfile localProfile, 227 PendingIntent incomingCallPendingIntent, 228 SipRegistrationListener listener) throws SipException { 229 if (incomingCallPendingIntent == null) { 230 throw new NullPointerException( 231 "incomingCallPendingIntent cannot be null"); 232 } 233 try { 234 mSipService.open3(localProfile, incomingCallPendingIntent, 235 createRelay(listener, localProfile.getUriString()), 236 mContext.getOpPackageName()); 237 } catch (RemoteException e) { 238 throw new SipException("open()", e); 239 } 240 } 241 242 /** 243 * Sets the listener to listen to registration events. No effect if the 244 * profile has not been opened to receive calls (see 245 * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}). 246 * 247 * @param localProfileUri the URI of the profile 248 * @param listener to listen to registration events; can be null 249 * @throws SipException if calling the SIP service results in an error 250 */ setRegistrationListener(String localProfileUri, SipRegistrationListener listener)251 public void setRegistrationListener(String localProfileUri, 252 SipRegistrationListener listener) throws SipException { 253 try { 254 mSipService.setRegistrationListener( 255 localProfileUri, createRelay(listener, localProfileUri), 256 mContext.getOpPackageName()); 257 } catch (RemoteException e) { 258 throw new SipException("setRegistrationListener()", e); 259 } 260 } 261 262 /** 263 * Closes the specified profile to not make/receive calls. All the resources 264 * that were allocated to the profile are also released. 265 * 266 * @param localProfileUri the URI of the profile to close 267 * @throws SipException if calling the SIP service results in an error 268 */ close(String localProfileUri)269 public void close(String localProfileUri) throws SipException { 270 try { 271 mSipService.close(localProfileUri, mContext.getOpPackageName()); 272 } catch (RemoteException e) { 273 throw new SipException("close()", e); 274 } 275 } 276 277 /** 278 * Checks if the specified profile is opened in the SIP service for 279 * making and/or receiving calls. 280 * 281 * @param localProfileUri the URI of the profile in question 282 * @return true if the profile is enabled to receive calls 283 * @throws SipException if calling the SIP service results in an error 284 */ isOpened(String localProfileUri)285 public boolean isOpened(String localProfileUri) throws SipException { 286 try { 287 return mSipService.isOpened(localProfileUri, mContext.getOpPackageName()); 288 } catch (RemoteException e) { 289 throw new SipException("isOpened()", e); 290 } 291 } 292 293 /** 294 * Checks if the SIP service has successfully registered the profile to the 295 * SIP provider (specified in the profile) for receiving calls. Returning 296 * true from this method also implies the profile is opened 297 * ({@link #isOpened}). 298 * 299 * @param localProfileUri the URI of the profile in question 300 * @return true if the profile is registered to the SIP provider; false if 301 * the profile has not been opened in the SIP service or the SIP 302 * service has not yet successfully registered the profile to the SIP 303 * provider 304 * @throws SipException if calling the SIP service results in an error 305 */ isRegistered(String localProfileUri)306 public boolean isRegistered(String localProfileUri) throws SipException { 307 try { 308 return mSipService.isRegistered(localProfileUri, mContext.getOpPackageName()); 309 } catch (RemoteException e) { 310 throw new SipException("isRegistered()", e); 311 } 312 } 313 314 /** 315 * Creates a {@link SipAudioCall} to make a call. The attempt will be timed 316 * out if the call is not established within {@code timeout} seconds and 317 * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 318 * will be called. 319 * 320 * @param localProfile the SIP profile to make the call from 321 * @param peerProfile the SIP profile to make the call to 322 * @param listener to listen to the call events from {@link SipAudioCall}; 323 * can be null 324 * @param timeout the timeout value in seconds. Default value (defined by 325 * SIP protocol) is used if {@code timeout} is zero or negative. 326 * @return a {@link SipAudioCall} object 327 * @throws SipException if calling the SIP service results in an error or 328 * VOIP API is not supported by the device 329 * @see SipAudioCall.Listener#onError 330 * @see #isVoipSupported 331 */ makeAudioCall(SipProfile localProfile, SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)332 public SipAudioCall makeAudioCall(SipProfile localProfile, 333 SipProfile peerProfile, SipAudioCall.Listener listener, int timeout) 334 throws SipException { 335 if (!isVoipSupported(mContext)) { 336 throw new SipException("VOIP API is not supported"); 337 } 338 SipAudioCall call = new SipAudioCall(mContext, localProfile); 339 call.setListener(listener); 340 SipSession s = createSipSession(localProfile, null); 341 call.makeCall(peerProfile, s, timeout); 342 return call; 343 } 344 345 /** 346 * Creates a {@link SipAudioCall} to make an audio call. The attempt will be 347 * timed out if the call is not established within {@code timeout} seconds 348 * and 349 * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 350 * will be called. 351 * 352 * @param localProfileUri URI of the SIP profile to make the call from 353 * @param peerProfileUri URI of the SIP profile to make the call to 354 * @param listener to listen to the call events from {@link SipAudioCall}; 355 * can be null 356 * @param timeout the timeout value in seconds. Default value (defined by 357 * SIP protocol) is used if {@code timeout} is zero or negative. 358 * @return a {@link SipAudioCall} object 359 * @throws SipException if calling the SIP service results in an error or 360 * VOIP API is not supported by the device 361 * @see SipAudioCall.Listener#onError 362 * @see #isVoipSupported 363 */ makeAudioCall(String localProfileUri, String peerProfileUri, SipAudioCall.Listener listener, int timeout)364 public SipAudioCall makeAudioCall(String localProfileUri, 365 String peerProfileUri, SipAudioCall.Listener listener, int timeout) 366 throws SipException { 367 if (!isVoipSupported(mContext)) { 368 throw new SipException("VOIP API is not supported"); 369 } 370 try { 371 return makeAudioCall( 372 new SipProfile.Builder(localProfileUri).build(), 373 new SipProfile.Builder(peerProfileUri).build(), listener, 374 timeout); 375 } catch (ParseException e) { 376 throw new SipException("build SipProfile", e); 377 } 378 } 379 380 /** 381 * Creates a {@link SipAudioCall} to take an incoming call. Before the call 382 * is returned, the listener will receive a 383 * {@link SipAudioCall.Listener#onRinging} 384 * callback. 385 * 386 * @param incomingCallIntent the incoming call broadcast intent 387 * @param listener to listen to the call events from {@link SipAudioCall}; 388 * can be null 389 * @return a {@link SipAudioCall} object 390 * @throws SipException if calling the SIP service results in an error 391 */ takeAudioCall(Intent incomingCallIntent, SipAudioCall.Listener listener)392 public SipAudioCall takeAudioCall(Intent incomingCallIntent, 393 SipAudioCall.Listener listener) throws SipException { 394 if (incomingCallIntent == null) { 395 throw new SipException("Cannot retrieve session with null intent"); 396 } 397 398 String callId = getCallId(incomingCallIntent); 399 if (callId == null) { 400 throw new SipException("Call ID missing in incoming call intent"); 401 } 402 403 String offerSd = getOfferSessionDescription(incomingCallIntent); 404 if (offerSd == null) { 405 throw new SipException("Session description missing in incoming " 406 + "call intent"); 407 } 408 409 try { 410 ISipSession session = mSipService.getPendingSession(callId, 411 mContext.getOpPackageName()); 412 if (session == null) { 413 throw new SipException("No pending session for the call"); 414 } 415 SipAudioCall call = new SipAudioCall( 416 mContext, session.getLocalProfile()); 417 call.attachCall(new SipSession(session), offerSd); 418 call.setListener(listener); 419 return call; 420 } catch (Throwable t) { 421 throw new SipException("takeAudioCall()", t); 422 } 423 } 424 425 /** 426 * Checks if the intent is an incoming call broadcast intent. 427 * 428 * @param intent the intent in question 429 * @return true if the intent is an incoming call broadcast intent 430 */ isIncomingCallIntent(Intent intent)431 public static boolean isIncomingCallIntent(Intent intent) { 432 if (intent == null) return false; 433 String callId = getCallId(intent); 434 String offerSd = getOfferSessionDescription(intent); 435 return ((callId != null) && (offerSd != null)); 436 } 437 438 /** 439 * Gets the call ID from the specified incoming call broadcast intent. 440 * 441 * @param incomingCallIntent the incoming call broadcast intent 442 * @return the call ID or null if the intent does not contain it 443 */ getCallId(Intent incomingCallIntent)444 public static String getCallId(Intent incomingCallIntent) { 445 return incomingCallIntent.getStringExtra(EXTRA_CALL_ID); 446 } 447 448 /** 449 * Gets the offer session description from the specified incoming call 450 * broadcast intent. 451 * 452 * @param incomingCallIntent the incoming call broadcast intent 453 * @return the offer session description or null if the intent does not 454 * have it 455 */ getOfferSessionDescription(Intent incomingCallIntent)456 public static String getOfferSessionDescription(Intent incomingCallIntent) { 457 return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD); 458 } 459 460 /** 461 * Creates an incoming call broadcast intent. 462 * 463 * @param callId the call ID of the incoming call 464 * @param sessionDescription the session description of the incoming call 465 * @return the incoming call intent 466 * @hide 467 */ createIncomingCallBroadcast(String callId, String sessionDescription)468 public static Intent createIncomingCallBroadcast(String callId, 469 String sessionDescription) { 470 Intent intent = new Intent(); 471 intent.putExtra(EXTRA_CALL_ID, callId); 472 intent.putExtra(EXTRA_OFFER_SD, sessionDescription); 473 return intent; 474 } 475 476 /** 477 * Manually registers the profile to the corresponding SIP provider for 478 * receiving calls. 479 * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is 480 * still needed to be called at least once in order for the SIP service to 481 * notify the caller with the {@link android.app.PendingIntent} when an incoming call is 482 * received. 483 * 484 * @param localProfile the SIP profile to register with 485 * @param expiryTime registration expiration time (in seconds) 486 * @param listener to listen to the registration events 487 * @throws SipException if calling the SIP service results in an error 488 */ register(SipProfile localProfile, int expiryTime, SipRegistrationListener listener)489 public void register(SipProfile localProfile, int expiryTime, 490 SipRegistrationListener listener) throws SipException { 491 try { 492 ISipSession session = mSipService.createSession(localProfile, 493 createRelay(listener, localProfile.getUriString()), 494 mContext.getOpPackageName()); 495 if (session == null) { 496 throw new SipException( 497 "SipService.createSession() returns null"); 498 } 499 session.register(expiryTime); 500 } catch (RemoteException e) { 501 throw new SipException("register()", e); 502 } 503 } 504 505 /** 506 * Manually unregisters the profile from the corresponding SIP provider for 507 * stop receiving further calls. This may interference with the auto 508 * registration process in the SIP service if the auto-registration option 509 * in the profile is enabled. 510 * 511 * @param localProfile the SIP profile to register with 512 * @param listener to listen to the registration events 513 * @throws SipException if calling the SIP service results in an error 514 */ unregister(SipProfile localProfile, SipRegistrationListener listener)515 public void unregister(SipProfile localProfile, 516 SipRegistrationListener listener) throws SipException { 517 try { 518 ISipSession session = mSipService.createSession(localProfile, 519 createRelay(listener, localProfile.getUriString()), 520 mContext.getOpPackageName()); 521 if (session == null) { 522 throw new SipException( 523 "SipService.createSession() returns null"); 524 } 525 session.unregister(); 526 } catch (RemoteException e) { 527 throw new SipException("unregister()", e); 528 } 529 } 530 531 /** 532 * Gets the {@link SipSession} that handles the incoming call. For audio 533 * calls, consider to use {@link SipAudioCall} to handle the incoming call. 534 * See {@link #takeAudioCall}. Note that the method may be called only once 535 * for the same intent. For subsequent calls on the same intent, the method 536 * returns null. 537 * 538 * @param incomingCallIntent the incoming call broadcast intent 539 * @return the session object that handles the incoming call 540 */ getSessionFor(Intent incomingCallIntent)541 public SipSession getSessionFor(Intent incomingCallIntent) 542 throws SipException { 543 try { 544 String callId = getCallId(incomingCallIntent); 545 ISipSession s = mSipService.getPendingSession(callId, 546 mContext.getOpPackageName()); 547 return ((s == null) ? null : new SipSession(s)); 548 } catch (RemoteException e) { 549 throw new SipException("getSessionFor()", e); 550 } 551 } 552 createRelay( SipRegistrationListener listener, String uri)553 private static ISipSessionListener createRelay( 554 SipRegistrationListener listener, String uri) { 555 return ((listener == null) ? null : new ListenerRelay(listener, uri)); 556 } 557 558 /** 559 * Creates a {@link SipSession} with the specified profile. Use other 560 * methods, if applicable, instead of interacting with {@link SipSession} 561 * directly. 562 * 563 * @param localProfile the SIP profile the session is associated with 564 * @param listener to listen to SIP session events 565 */ createSipSession(SipProfile localProfile, SipSession.Listener listener)566 public SipSession createSipSession(SipProfile localProfile, 567 SipSession.Listener listener) throws SipException { 568 try { 569 ISipSession s = mSipService.createSession(localProfile, null, 570 mContext.getOpPackageName()); 571 if (s == null) { 572 throw new SipException( 573 "Failed to create SipSession; network unavailable?"); 574 } 575 return new SipSession(s, listener); 576 } catch (RemoteException e) { 577 throw new SipException("createSipSession()", e); 578 } 579 } 580 581 /** 582 * Gets the list of profiles hosted by the SIP service. The user information 583 * (username, password and display name) are crossed out. 584 * @hide 585 */ getListOfProfiles()586 public SipProfile[] getListOfProfiles() { 587 try { 588 return mSipService.getListOfProfiles(mContext.getOpPackageName()); 589 } catch (RemoteException e) { 590 return new SipProfile[0]; 591 } 592 } 593 594 private static class ListenerRelay extends SipSessionAdapter { 595 private SipRegistrationListener mListener; 596 private String mUri; 597 598 // listener must not be null ListenerRelay(SipRegistrationListener listener, String uri)599 public ListenerRelay(SipRegistrationListener listener, String uri) { 600 mListener = listener; 601 mUri = uri; 602 } 603 getUri(ISipSession session)604 private String getUri(ISipSession session) { 605 try { 606 return ((session == null) 607 ? mUri 608 : session.getLocalProfile().getUriString()); 609 } catch (Throwable e) { 610 // SipService died? SIP stack died? 611 Rlog.e(TAG, "getUri(): ", e); 612 return null; 613 } 614 } 615 616 @Override onRegistering(ISipSession session)617 public void onRegistering(ISipSession session) { 618 mListener.onRegistering(getUri(session)); 619 } 620 621 @Override onRegistrationDone(ISipSession session, int duration)622 public void onRegistrationDone(ISipSession session, int duration) { 623 long expiryTime = duration; 624 if (duration > 0) expiryTime += System.currentTimeMillis(); 625 mListener.onRegistrationDone(getUri(session), expiryTime); 626 } 627 628 @Override onRegistrationFailed(ISipSession session, int errorCode, String message)629 public void onRegistrationFailed(ISipSession session, int errorCode, 630 String message) { 631 mListener.onRegistrationFailed(getUri(session), errorCode, message); 632 } 633 634 @Override onRegistrationTimeout(ISipSession session)635 public void onRegistrationTimeout(ISipSession session) { 636 mListener.onRegistrationFailed(getUri(session), 637 SipErrorCode.TIME_OUT, "registration timed out"); 638 } 639 } 640 } 641