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