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