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