SipManager.java revision 5bd3782f244212cd8ef51bf9f3578869b08b4e18
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
17package android.net.sip;
18
19import android.app.PendingIntent;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.PackageManager;
23import android.os.IBinder;
24import android.os.Looper;
25import android.os.RemoteException;
26import android.os.ServiceManager;
27import android.util.Log;
28
29import 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 */
56public 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     */
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     */
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     */
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     */
146    public static boolean isSipWifiOnly(Context context) {
147        return context.getResources().getBoolean(
148                com.android.internal.R.bool.config_sip_wifi_only);
149    }
150
151    private SipManager(Context context) {
152        mContext = context;
153        createSipService();
154    }
155
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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            session.register(expiryTime);
475        } catch (RemoteException e) {
476            throw new SipException("register()", e);
477        }
478    }
479
480    /**
481     * Manually unregisters the profile from the corresponding SIP provider for
482     * stop receiving further calls. This may interference with the auto
483     * registration process in the SIP service if the auto-registration option
484     * in the profile is enabled.
485     *
486     * @param localProfile the SIP profile to register with
487     * @param listener to listen to the registration events
488     * @throws SipException if calling the SIP service results in an error
489     */
490    public void unregister(SipProfile localProfile,
491            SipRegistrationListener listener) throws SipException {
492        try {
493            ISipSession session = mSipService.createSession(localProfile,
494                    createRelay(listener, localProfile.getUriString()));
495            session.unregister();
496        } catch (RemoteException e) {
497            throw new SipException("unregister()", e);
498        }
499    }
500
501    /**
502     * Gets the {@link SipSession} that handles the incoming call. For audio
503     * calls, consider to use {@link SipAudioCall} to handle the incoming call.
504     * See {@link #takeAudioCall}. Note that the method may be called only once
505     * for the same intent. For subsequent calls on the same intent, the method
506     * returns null.
507     *
508     * @param incomingCallIntent the incoming call broadcast intent
509     * @return the session object that handles the incoming call
510     */
511    public SipSession getSessionFor(Intent incomingCallIntent)
512            throws SipException {
513        try {
514            String callId = getCallId(incomingCallIntent);
515            ISipSession s = mSipService.getPendingSession(callId);
516            return new SipSession(s);
517        } catch (RemoteException e) {
518            throw new SipException("getSessionFor()", e);
519        }
520    }
521
522    private static ISipSessionListener createRelay(
523            SipRegistrationListener listener, String uri) {
524        return ((listener == null) ? null : new ListenerRelay(listener, uri));
525    }
526
527    /**
528     * Creates a {@link SipSession} with the specified profile. Use other
529     * methods, if applicable, instead of interacting with {@link SipSession}
530     * directly.
531     *
532     * @param localProfile the SIP profile the session is associated with
533     * @param listener to listen to SIP session events
534     */
535    public SipSession createSipSession(SipProfile localProfile,
536            SipSession.Listener listener) throws SipException {
537        try {
538            ISipSession s = mSipService.createSession(localProfile, null);
539            if (s == null) {
540                throw new SipException(
541                        "Failed to create SipSession; network unavailable?");
542            }
543            return new SipSession(s, listener);
544        } catch (RemoteException e) {
545            throw new SipException("createSipSession()", e);
546        }
547    }
548
549    /**
550     * Gets the list of profiles hosted by the SIP service. The user information
551     * (username, password and display name) are crossed out.
552     * @hide
553     */
554    public SipProfile[] getListOfProfiles() {
555        try {
556            return mSipService.getListOfProfiles();
557        } catch (RemoteException e) {
558            return new SipProfile[0];
559        }
560    }
561
562    private static class ListenerRelay extends SipSessionAdapter {
563        private SipRegistrationListener mListener;
564        private String mUri;
565
566        // listener must not be null
567        public ListenerRelay(SipRegistrationListener listener, String uri) {
568            mListener = listener;
569            mUri = uri;
570        }
571
572        private String getUri(ISipSession session) {
573            try {
574                return ((session == null)
575                        ? mUri
576                        : session.getLocalProfile().getUriString());
577            } catch (Throwable e) {
578                // SipService died? SIP stack died?
579                Log.w(TAG, "getUri(): " + e);
580                return null;
581            }
582        }
583
584        @Override
585        public void onRegistering(ISipSession session) {
586            mListener.onRegistering(getUri(session));
587        }
588
589        @Override
590        public void onRegistrationDone(ISipSession session, int duration) {
591            long expiryTime = duration;
592            if (duration > 0) expiryTime += System.currentTimeMillis();
593            mListener.onRegistrationDone(getUri(session), expiryTime);
594        }
595
596        @Override
597        public void onRegistrationFailed(ISipSession session, int errorCode,
598                String message) {
599            mListener.onRegistrationFailed(getUri(session), errorCode, message);
600        }
601
602        @Override
603        public void onRegistrationTimeout(ISipSession session) {
604            mListener.onRegistrationFailed(getUri(session),
605                    SipErrorCode.TIME_OUT, "registration timed out");
606        }
607    }
608}
609