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