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