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