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