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