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         if (mSipService == null) {
175             IBinder b = ServiceManager.getService(Context.SIP_SERVICE);
176             mSipService = ISipService.Stub.asInterface(b);
177         }
178     }
179 
checkSipServiceConnection()180     private void checkSipServiceConnection() throws SipException {
181         createSipService();
182         if (mSipService == null) {
183             throw new SipException("SipService is dead and is restarting...", new Exception());
184         }
185     }
186 
187     /**
188      * Opens the profile for making generic SIP calls. The caller may make subsequent calls
189      * through {@link #makeAudioCall}. If one also wants to receive calls on the
190      * profile, use
191      * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}
192      * instead.
193      *
194      * @param localProfile the SIP profile to make calls from
195      * @throws SipException if the profile contains incorrect settings or
196      *      calling the SIP service results in an error
197      */
open(SipProfile localProfile)198     public void open(SipProfile localProfile) throws SipException {
199         try {
200             checkSipServiceConnection();
201             mSipService.open(localProfile, mContext.getOpPackageName());
202         } catch (RemoteException e) {
203             throw new SipException("open()", e);
204         }
205     }
206 
207     /**
208      * Opens the profile for making calls and/or receiving generic SIP calls. The caller may
209      * make subsequent calls through {@link #makeAudioCall}. If the
210      * auto-registration option is enabled in the profile, the SIP service
211      * will register the profile to the corresponding SIP provider periodically
212      * in order to receive calls from the provider. When the SIP service
213      * receives a new call, it will send out an intent with the provided action
214      * string. The intent contains a call ID extra and an offer session
215      * description string extra. Use {@link #getCallId} and
216      * {@link #getOfferSessionDescription} to retrieve those extras.
217      *
218      * @param localProfile the SIP profile to receive incoming calls for
219      * @param incomingCallPendingIntent When an incoming call is received, the
220      *      SIP service will call
221      *      {@link PendingIntent#send(Context, int, Intent)} to send back the
222      *      intent to the caller with {@link #INCOMING_CALL_RESULT_CODE} as the
223      *      result code and the intent to fill in the call ID and session
224      *      description information. It cannot be null.
225      * @param listener to listen to registration events; can be null
226      * @see #getCallId
227      * @see #getOfferSessionDescription
228      * @see #takeAudioCall
229      * @throws NullPointerException if {@code incomingCallPendingIntent} is null
230      * @throws SipException if the profile contains incorrect settings or
231      *      calling the SIP service results in an error
232      * @see #isIncomingCallIntent
233      * @see #getCallId
234      * @see #getOfferSessionDescription
235      */
open(SipProfile localProfile, PendingIntent incomingCallPendingIntent, SipRegistrationListener listener)236     public void open(SipProfile localProfile,
237             PendingIntent incomingCallPendingIntent,
238             SipRegistrationListener listener) throws SipException {
239         if (incomingCallPendingIntent == null) {
240             throw new NullPointerException(
241                     "incomingCallPendingIntent cannot be null");
242         }
243         try {
244             checkSipServiceConnection();
245             mSipService.open3(localProfile, incomingCallPendingIntent,
246                     createRelay(listener, localProfile.getUriString()),
247                     mContext.getOpPackageName());
248         } catch (RemoteException e) {
249             throw new SipException("open()", e);
250         }
251     }
252 
253     /**
254      * Sets the listener to listen to registration events. No effect if the
255      * profile has not been opened to receive calls (see
256      * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}).
257      *
258      * @param localProfileUri the URI of the profile
259      * @param listener to listen to registration events; can be null
260      * @throws SipException if calling the SIP service results in an error
261      */
setRegistrationListener(String localProfileUri, SipRegistrationListener listener)262     public void setRegistrationListener(String localProfileUri,
263             SipRegistrationListener listener) throws SipException {
264         try {
265             checkSipServiceConnection();
266             mSipService.setRegistrationListener(
267                     localProfileUri, createRelay(listener, localProfileUri),
268                     mContext.getOpPackageName());
269         } catch (RemoteException e) {
270             throw new SipException("setRegistrationListener()", e);
271         }
272     }
273 
274     /**
275      * Closes the specified profile to not make/receive calls. All the resources
276      * that were allocated to the profile are also released.
277      *
278      * @param localProfileUri the URI of the profile to close
279      * @throws SipException if calling the SIP service results in an error
280      */
close(String localProfileUri)281     public void close(String localProfileUri) throws SipException {
282         try {
283             checkSipServiceConnection();
284             mSipService.close(localProfileUri, mContext.getOpPackageName());
285         } catch (RemoteException e) {
286             throw new SipException("close()", e);
287         }
288     }
289 
290     /**
291      * Checks if the specified profile is opened in the SIP service for
292      * making and/or receiving calls.
293      *
294      * @param localProfileUri the URI of the profile in question
295      * @return true if the profile is enabled to receive calls
296      * @throws SipException if calling the SIP service results in an error
297      */
isOpened(String localProfileUri)298     public boolean isOpened(String localProfileUri) throws SipException {
299         try {
300             checkSipServiceConnection();
301             return mSipService.isOpened(localProfileUri, mContext.getOpPackageName());
302         } catch (RemoteException e) {
303             throw new SipException("isOpened()", e);
304         }
305     }
306 
307     /**
308      * Checks if the SIP service has successfully registered the profile to the
309      * SIP provider (specified in the profile) for receiving calls. Returning
310      * true from this method also implies the profile is opened
311      * ({@link #isOpened}).
312      *
313      * @param localProfileUri the URI of the profile in question
314      * @return true if the profile is registered to the SIP provider; false if
315      *        the profile has not been opened in the SIP service or the SIP
316      *        service has not yet successfully registered the profile to the SIP
317      *        provider
318      * @throws SipException if calling the SIP service results in an error
319      */
isRegistered(String localProfileUri)320     public boolean isRegistered(String localProfileUri) throws SipException {
321         try {
322             checkSipServiceConnection();
323             return mSipService.isRegistered(localProfileUri, mContext.getOpPackageName());
324         } catch (RemoteException e) {
325             throw new SipException("isRegistered()", e);
326         }
327     }
328 
329     /**
330      * Creates a {@link SipAudioCall} to make a call. The attempt will be timed
331      * out if the call is not established within {@code timeout} seconds and
332      * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
333      * will be called.
334      *
335      * @param localProfile the SIP profile to make the call from
336      * @param peerProfile the SIP profile to make the call to
337      * @param listener to listen to the call events from {@link SipAudioCall};
338      *      can be null
339      * @param timeout the timeout value in seconds. Default value (defined by
340      *        SIP protocol) is used if {@code timeout} is zero or negative.
341      * @return a {@link SipAudioCall} object
342      * @throws SipException if calling the SIP service results in an error or
343      *      VOIP API is not supported by the device
344      * @see SipAudioCall.Listener#onError
345      * @see #isVoipSupported
346      */
makeAudioCall(SipProfile localProfile, SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)347     public SipAudioCall makeAudioCall(SipProfile localProfile,
348             SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
349             throws SipException {
350         if (!isVoipSupported(mContext)) {
351             throw new SipException("VOIP API is not supported");
352         }
353         SipAudioCall call = new SipAudioCall(mContext, localProfile);
354         call.setListener(listener);
355         SipSession s = createSipSession(localProfile, null);
356         call.makeCall(peerProfile, s, timeout);
357         return call;
358     }
359 
360     /**
361      * Creates a {@link SipAudioCall} to make an audio call. The attempt will be
362      * timed out if the call is not established within {@code timeout} seconds
363      * and
364      * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
365      * will be called.
366      *
367      * @param localProfileUri URI of the SIP profile to make the call from
368      * @param peerProfileUri URI of the SIP profile to make the call to
369      * @param listener to listen to the call events from {@link SipAudioCall};
370      *      can be null
371      * @param timeout the timeout value in seconds. Default value (defined by
372      *        SIP protocol) is used if {@code timeout} is zero or negative.
373      * @return a {@link SipAudioCall} object
374      * @throws SipException if calling the SIP service results in an error or
375      *      VOIP API is not supported by the device
376      * @see SipAudioCall.Listener#onError
377      * @see #isVoipSupported
378      */
makeAudioCall(String localProfileUri, String peerProfileUri, SipAudioCall.Listener listener, int timeout)379     public SipAudioCall makeAudioCall(String localProfileUri,
380             String peerProfileUri, SipAudioCall.Listener listener, int timeout)
381             throws SipException {
382         if (!isVoipSupported(mContext)) {
383             throw new SipException("VOIP API is not supported");
384         }
385         try {
386             return makeAudioCall(
387                     new SipProfile.Builder(localProfileUri).build(),
388                     new SipProfile.Builder(peerProfileUri).build(), listener,
389                     timeout);
390         } catch (ParseException e) {
391             throw new SipException("build SipProfile", e);
392         }
393     }
394 
395     /**
396      * Creates a {@link SipAudioCall} to take an incoming call. Before the call
397      * is returned, the listener will receive a
398      * {@link SipAudioCall.Listener#onRinging}
399      * callback.
400      *
401      * @param incomingCallIntent the incoming call broadcast intent
402      * @param listener to listen to the call events from {@link SipAudioCall};
403      *      can be null
404      * @return a {@link SipAudioCall} object
405      * @throws SipException if calling the SIP service results in an error
406      */
takeAudioCall(Intent incomingCallIntent, SipAudioCall.Listener listener)407     public SipAudioCall takeAudioCall(Intent incomingCallIntent,
408             SipAudioCall.Listener listener) throws SipException {
409         if (incomingCallIntent == null) {
410             throw new SipException("Cannot retrieve session with null intent");
411         }
412 
413         String callId = getCallId(incomingCallIntent);
414         if (callId == null) {
415             throw new SipException("Call ID missing in incoming call intent");
416         }
417 
418         String offerSd = getOfferSessionDescription(incomingCallIntent);
419         if (offerSd == null) {
420             throw new SipException("Session description missing in incoming "
421                     + "call intent");
422         }
423 
424         try {
425             checkSipServiceConnection();
426             ISipSession session = mSipService.getPendingSession(callId,
427                     mContext.getOpPackageName());
428             if (session == null) {
429                 throw new SipException("No pending session for the call");
430             }
431             SipAudioCall call = new SipAudioCall(
432                     mContext, session.getLocalProfile());
433             call.attachCall(new SipSession(session), offerSd);
434             call.setListener(listener);
435             return call;
436         } catch (Throwable t) {
437             throw new SipException("takeAudioCall()", t);
438         }
439     }
440 
441     /**
442      * Checks if the intent is an incoming call broadcast intent.
443      *
444      * @param intent the intent in question
445      * @return true if the intent is an incoming call broadcast intent
446      */
isIncomingCallIntent(Intent intent)447     public static boolean isIncomingCallIntent(Intent intent) {
448         if (intent == null) return false;
449         String callId = getCallId(intent);
450         String offerSd = getOfferSessionDescription(intent);
451         return ((callId != null) && (offerSd != null));
452     }
453 
454     /**
455      * Gets the call ID from the specified incoming call broadcast intent.
456      *
457      * @param incomingCallIntent the incoming call broadcast intent
458      * @return the call ID or null if the intent does not contain it
459      */
getCallId(Intent incomingCallIntent)460     public static String getCallId(Intent incomingCallIntent) {
461         return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
462     }
463 
464     /**
465      * Gets the offer session description from the specified incoming call
466      * broadcast intent.
467      *
468      * @param incomingCallIntent the incoming call broadcast intent
469      * @return the offer session description or null if the intent does not
470      *      have it
471      */
getOfferSessionDescription(Intent incomingCallIntent)472     public static String getOfferSessionDescription(Intent incomingCallIntent) {
473         return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD);
474     }
475 
476     /**
477      * Creates an incoming call broadcast intent.
478      *
479      * @param callId the call ID of the incoming call
480      * @param sessionDescription the session description of the incoming call
481      * @return the incoming call intent
482      * @hide
483      */
createIncomingCallBroadcast(String callId, String sessionDescription)484     public static Intent createIncomingCallBroadcast(String callId,
485             String sessionDescription) {
486         Intent intent = new Intent();
487         intent.putExtra(EXTRA_CALL_ID, callId);
488         intent.putExtra(EXTRA_OFFER_SD, sessionDescription);
489         return intent;
490     }
491 
492     /**
493      * Manually registers the profile to the corresponding SIP provider for
494      * receiving calls.
495      * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is
496      * still needed to be called at least once in order for the SIP service to
497      * notify the caller with the {@link android.app.PendingIntent} when an incoming call is
498      * received.
499      *
500      * @param localProfile the SIP profile to register with
501      * @param expiryTime registration expiration time (in seconds)
502      * @param listener to listen to the registration events
503      * @throws SipException if calling the SIP service results in an error
504      */
register(SipProfile localProfile, int expiryTime, SipRegistrationListener listener)505     public void register(SipProfile localProfile, int expiryTime,
506             SipRegistrationListener listener) throws SipException {
507         try {
508             checkSipServiceConnection();
509             ISipSession session = mSipService.createSession(localProfile,
510                     createRelay(listener, localProfile.getUriString()),
511                     mContext.getOpPackageName());
512             if (session == null) {
513                 throw new SipException(
514                         "SipService.createSession() returns null");
515             }
516             session.register(expiryTime);
517         } catch (RemoteException e) {
518             throw new SipException("register()", e);
519         }
520     }
521 
522     /**
523      * Manually unregisters the profile from the corresponding SIP provider for
524      * stop receiving further calls. This may interference with the auto
525      * registration process in the SIP service if the auto-registration option
526      * in the profile is enabled.
527      *
528      * @param localProfile the SIP profile to register with
529      * @param listener to listen to the registration events
530      * @throws SipException if calling the SIP service results in an error
531      */
unregister(SipProfile localProfile, SipRegistrationListener listener)532     public void unregister(SipProfile localProfile,
533             SipRegistrationListener listener) throws SipException {
534         try {
535             checkSipServiceConnection();
536             ISipSession session = mSipService.createSession(localProfile,
537                     createRelay(listener, localProfile.getUriString()),
538                     mContext.getOpPackageName());
539             if (session == null) {
540                 throw new SipException(
541                         "SipService.createSession() returns null");
542             }
543             session.unregister();
544         } catch (RemoteException e) {
545             throw new SipException("unregister()", e);
546         }
547     }
548 
549     /**
550      * Gets the {@link SipSession} that handles the incoming call. For audio
551      * calls, consider to use {@link SipAudioCall} to handle the incoming call.
552      * See {@link #takeAudioCall}. Note that the method may be called only once
553      * for the same intent. For subsequent calls on the same intent, the method
554      * returns null.
555      *
556      * @param incomingCallIntent the incoming call broadcast intent
557      * @return the session object that handles the incoming call
558      */
getSessionFor(Intent incomingCallIntent)559     public SipSession getSessionFor(Intent incomingCallIntent)
560             throws SipException {
561         try {
562             checkSipServiceConnection();
563             String callId = getCallId(incomingCallIntent);
564             ISipSession s = mSipService.getPendingSession(callId,
565                     mContext.getOpPackageName());
566             return ((s == null) ? null : new SipSession(s));
567         } catch (RemoteException e) {
568             throw new SipException("getSessionFor()", e);
569         }
570     }
571 
createRelay( SipRegistrationListener listener, String uri)572     private static ISipSessionListener createRelay(
573             SipRegistrationListener listener, String uri) {
574         return ((listener == null) ? null : new ListenerRelay(listener, uri));
575     }
576 
577     /**
578      * Creates a {@link SipSession} with the specified profile. Use other
579      * methods, if applicable, instead of interacting with {@link SipSession}
580      * directly.
581      *
582      * @param localProfile the SIP profile the session is associated with
583      * @param listener to listen to SIP session events
584      */
createSipSession(SipProfile localProfile, SipSession.Listener listener)585     public SipSession createSipSession(SipProfile localProfile,
586             SipSession.Listener listener) throws SipException {
587         try {
588             checkSipServiceConnection();
589             ISipSession s = mSipService.createSession(localProfile, null,
590                     mContext.getOpPackageName());
591             if (s == null) {
592                 throw new SipException(
593                         "Failed to create SipSession; network unavailable?");
594             }
595             return new SipSession(s, listener);
596         } catch (RemoteException e) {
597             throw new SipException("createSipSession()", e);
598         }
599     }
600 
601     /**
602      * Gets the list of profiles hosted by the SIP service. The user information
603      * (username, password and display name) are crossed out.
604      * @hide
605      */
getListOfProfiles()606     public SipProfile[] getListOfProfiles() throws SipException {
607         try {
608             checkSipServiceConnection();
609             return mSipService.getListOfProfiles(mContext.getOpPackageName());
610         } catch (RemoteException e) {
611             return new SipProfile[0];
612         }
613     }
614 
615     private static class ListenerRelay extends SipSessionAdapter {
616         private SipRegistrationListener mListener;
617         private String mUri;
618 
619         // listener must not be null
ListenerRelay(SipRegistrationListener listener, String uri)620         public ListenerRelay(SipRegistrationListener listener, String uri) {
621             mListener = listener;
622             mUri = uri;
623         }
624 
getUri(ISipSession session)625         private String getUri(ISipSession session) {
626             try {
627                 return ((session == null)
628                         ? mUri
629                         : session.getLocalProfile().getUriString());
630             } catch (Throwable e) {
631                 // SipService died? SIP stack died?
632                 Rlog.e(TAG, "getUri(): ", e);
633                 return null;
634             }
635         }
636 
637         @Override
onRegistering(ISipSession session)638         public void onRegistering(ISipSession session) {
639             mListener.onRegistering(getUri(session));
640         }
641 
642         @Override
onRegistrationDone(ISipSession session, int duration)643         public void onRegistrationDone(ISipSession session, int duration) {
644             long expiryTime = duration;
645             if (duration > 0) expiryTime += System.currentTimeMillis();
646             mListener.onRegistrationDone(getUri(session), expiryTime);
647         }
648 
649         @Override
onRegistrationFailed(ISipSession session, int errorCode, String message)650         public void onRegistrationFailed(ISipSession session, int errorCode,
651                 String message) {
652             mListener.onRegistrationFailed(getUri(session), errorCode, message);
653         }
654 
655         @Override
onRegistrationTimeout(ISipSession session)656         public void onRegistrationTimeout(ISipSession session) {
657             mListener.onRegistrationFailed(getUri(session),
658                     SipErrorCode.TIME_OUT, "registration timed out");
659         }
660     }
661 }
662