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