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