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