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