SipManager.java revision 08faac3c26e12863858e1534985dd950193f755f
19f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu/*
29f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu * Copyright (C) 2010 The Android Open Source Project
39f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu *
49f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu * Licensed under the Apache License, Version 2.0 (the "License");
59f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu * you may not use this file except in compliance with the License.
69f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu * You may obtain a copy of the License at
79f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu *
89f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu *      http://www.apache.org/licenses/LICENSE-2.0
99f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu *
109f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu * Unless required by applicable law or agreed to in writing, software
119f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu * distributed under the License is distributed on an "AS IS" BASIS,
129f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu * See the License for the specific language governing permissions and
149f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu * limitations under the License.
159f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu */
169f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu
179f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapupackage android.net.sip;
189f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu
199f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapuimport android.app.PendingIntent;
209f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapuimport android.content.Context;
219f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapuimport android.content.Intent;
229f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapuimport android.content.pm.PackageManager;
239f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapuimport android.os.IBinder;
249f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapuimport android.os.Looper;
259f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapuimport android.os.RemoteException;
269f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapuimport android.os.ServiceManager;
279f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapuimport android.util.Log;
28ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu
29ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapuimport java.text.ParseException;
309f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu
31ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu/**
32ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu * The class provides API for various SIP related tasks. Specifically, the API
33ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu * allows an application to:
34ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu * <ul>
35ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu * <li>open a {@link SipProfile} to get ready for making outbound calls or have
369f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu *      the background SIP service listen to incoming calls and broadcast them
379f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu *      with registered command string. See
389f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu *      {@link #open(SipProfile, PendingIntent, SipRegistrationListener)},
399f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu *      {@link #open(SipProfile)}, {@link #close}, {@link #isOpened} and
409f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu *      {@link #isRegistered}. It also facilitates handling of the incoming call
419f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu *      broadcast intent. See
429f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu *      {@link #isIncomingCallIntent}, {@link #getCallId},
439f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu *      {@link #getOfferSessionDescription} and {@link #takeAudioCall}.</li>
44ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu * <li>make/take SIP-based audio calls. See
45ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu *      {@link #makeAudioCall} and {@link #takeAudioCall}.</li>
46ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu * <li>register/unregister with a SIP service provider manually. See
47ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu *      {@link #register} and {@link #unregister}.</li>
48ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu * <li>process SIP events directly with a {@link SipSession} created by
49ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu *      {@link #createSipSession}.</li>
50ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu * </ul>
51ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu * {@code SipManager} can only be instantiated if SIP API is supported by the
52ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu * device. (See {@link #isApiSupported}).
53ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu */
54ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapupublic class SipManager {
55ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    /**
56ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * The result code to be sent back with the incoming call
57ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * {@link PendingIntent}.
58ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
59ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     */
60ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    public static final int INCOMING_CALL_RESULT_CODE = 101;
61ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu
629f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu    /**
639f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu     * Key to retrieve the call ID from an incoming call intent.
649f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu     * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
659f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu     */
66ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    public static final String EXTRA_CALL_ID = "android:sipCallID";
67ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu
68ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    /**
69ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * Key to retrieve the offered session description from an incoming call
70ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * intent.
71ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
72ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     */
73ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
74ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu
75ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    /**
76ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * Action to broadcast when SipService is up.
779f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu     * Internal use only.
789f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu     * @hide
799f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu     */
809f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu    public static final String ACTION_SIP_SERVICE_UP =
81ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu            "android.net.sip.SIP_SERVICE_UP";
82ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    /**
83ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * Action string for the incoming call intent for the Phone app.
84ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * Internal use only.
85ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * @hide
86ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     */
87ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    public static final String ACTION_SIP_INCOMING_CALL =
88ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu            "com.android.phone.SIP_INCOMING_CALL";
89ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    /**
90ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * Action string for the add-phone intent.
91ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * Internal use only.
929f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu     * @hide
939f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu     */
949f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu    public static final String ACTION_SIP_ADD_PHONE =
959f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu            "com.android.phone.SIP_ADD_PHONE";
96ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    /**
97ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * Action string for the remove-phone intent.
98ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * Internal use only.
99ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * @hide
100ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     */
101ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    public static final String ACTION_SIP_REMOVE_PHONE =
102ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu            "com.android.phone.SIP_REMOVE_PHONE";
103ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    /**
104ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * Part of the ACTION_SIP_ADD_PHONE and ACTION_SIP_REMOVE_PHONE intents.
105ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * Internal use only.
106ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * @hide
1079f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu     */
1089f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu    public static final String EXTRA_LOCAL_URI = "android:localSipUri";
1099f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu
1109f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu    private static final String TAG = "SipManager";
111ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu
112ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    private ISipService mSipService;
113ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    private Context mContext;
114ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu
115ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    /**
116ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * Creates a manager instance. Returns null if SIP API is not supported.
117ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     *
118ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * @param context application context for creating the manager object
119ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * @return the manager instance or null if SIP API is not supported
120ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     */
121ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    public static SipManager newInstance(Context context) {
1229f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu        return (isApiSupported(context) ? new SipManager(context) : null);
1239f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu    }
1249f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu
1259f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu    /**
126ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     * Returns true if the SIP API is supported by the system.
127ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu     */
128ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    public static boolean isApiSupported(Context context) {
129ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu        return true;
130ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu        /* TODO: uncomment this before ship
131ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu        return context.getPackageManager().hasSystemFeature(
132ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu                PackageManager.FEATURE_SIP);
133ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu         */
134ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    }
135ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu
136ea196fc50e9711207eddb1f546fec1463b615fc0Suchi Amalapurapu    /**
1379f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu     * Returns true if the system supports SIP-based VoIP.
1389f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu     */
1399f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu    public static boolean isVoipSupported(Context context) {
1409f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu        return true;
1419f2f87d92b3c23680fa7c24dbccf67976d0968d3Suchi Amalapurapu        /* TODO: uncomment this before ship
142        return context.getPackageManager().hasSystemFeature(
143                PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context);
144         */
145    }
146
147    /**
148     * Returns true if SIP is only available on WIFI.
149     */
150    public static boolean isSipWifiOnly(Context context) {
151        return context.getResources().getBoolean(
152                com.android.internal.R.bool.config_sip_wifi_only);
153    }
154
155    private SipManager(Context context) {
156        mContext = context;
157        createSipService();
158    }
159
160    private void createSipService() {
161        IBinder b = ServiceManager.getService(Context.SIP_SERVICE);
162        mSipService = ISipService.Stub.asInterface(b);
163    }
164
165    /**
166     * Opens the profile for making calls. The caller may make subsequent calls
167     * through {@link #makeAudioCall}. If one also wants to receive calls on the
168     * profile, use
169     * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}
170     * instead.
171     *
172     * @param localProfile the SIP profile to make calls from
173     * @throws SipException if the profile contains incorrect settings or
174     *      calling the SIP service results in an error
175     */
176    public void open(SipProfile localProfile) throws SipException {
177        try {
178            mSipService.open(localProfile);
179        } catch (RemoteException e) {
180            throw new SipException("open()", e);
181        }
182    }
183
184    /**
185     * Opens the profile for making calls and/or receiving calls. The caller may
186     * make subsequent calls through {@link #makeAudioCall}. If the
187     * auto-registration option is enabled in the profile, the SIP service
188     * will register the profile to the corresponding SIP provider periodically
189     * in order to receive calls from the provider. When the SIP service
190     * receives a new call, it will send out an intent with the provided action
191     * string. The intent contains a call ID extra and an offer session
192     * description string extra. Use {@link #getCallId} and
193     * {@link #getOfferSessionDescription} to retrieve those extras.
194     *
195     * @param localProfile the SIP profile to receive incoming calls for
196     * @param incomingCallPendingIntent When an incoming call is received, the
197     *      SIP service will call
198     *      {@link PendingIntent#send(Context, int, Intent)} to send back the
199     *      intent to the caller with {@link #INCOMING_CALL_RESULT_CODE} as the
200     *      result code and the intent to fill in the call ID and session
201     *      description information. It cannot be null.
202     * @param listener to listen to registration events; can be null
203     * @see #getCallId
204     * @see #getOfferSessionDescription
205     * @see #takeAudioCall
206     * @throws NullPointerException if {@code incomingCallPendingIntent} is null
207     * @throws SipException if the profile contains incorrect settings or
208     *      calling the SIP service results in an error
209     * @see #isIncomingCallIntent
210     * @see #getCallId
211     * @see #getOfferSessionDescription
212     */
213    public void open(SipProfile localProfile,
214            PendingIntent incomingCallPendingIntent,
215            SipRegistrationListener listener) throws SipException {
216        if (incomingCallPendingIntent == null) {
217            throw new NullPointerException(
218                    "incomingCallPendingIntent cannot be null");
219        }
220        try {
221            mSipService.open3(localProfile, incomingCallPendingIntent,
222                    createRelay(listener, localProfile.getUriString()));
223        } catch (RemoteException e) {
224            throw new SipException("open()", e);
225        }
226    }
227
228    /**
229     * Sets the listener to listen to registration events. No effect if the
230     * profile has not been opened to receive calls (see
231     * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}).
232     *
233     * @param localProfileUri the URI of the profile
234     * @param listener to listen to registration events; can be null
235     * @throws SipException if calling the SIP service results in an error
236     */
237    public void setRegistrationListener(String localProfileUri,
238            SipRegistrationListener listener) throws SipException {
239        try {
240            mSipService.setRegistrationListener(
241                    localProfileUri, createRelay(listener, localProfileUri));
242        } catch (RemoteException e) {
243            throw new SipException("setRegistrationListener()", e);
244        }
245    }
246
247    /**
248     * Closes the specified profile to not make/receive calls. All the resources
249     * that were allocated to the profile are also released.
250     *
251     * @param localProfileUri the URI of the profile to close
252     * @throws SipException if calling the SIP service results in an error
253     */
254    public void close(String localProfileUri) throws SipException {
255        try {
256            mSipService.close(localProfileUri);
257        } catch (RemoteException e) {
258            throw new SipException("close()", e);
259        }
260    }
261
262    /**
263     * Checks if the specified profile is opened in the SIP service for
264     * making and/or receiving calls.
265     *
266     * @param localProfileUri the URI of the profile in question
267     * @return true if the profile is enabled to receive calls
268     * @throws SipException if calling the SIP service results in an error
269     */
270    public boolean isOpened(String localProfileUri) throws SipException {
271        try {
272            return mSipService.isOpened(localProfileUri);
273        } catch (RemoteException e) {
274            throw new SipException("isOpened()", e);
275        }
276    }
277
278    /**
279     * Checks if the SIP service has successfully registered the profile to the
280     * SIP provider (specified in the profile) for receiving calls. Returning
281     * true from this method also implies the profile is opened
282     * ({@link #isOpened}).
283     *
284     * @param localProfileUri the URI of the profile in question
285     * @return true if the profile is registered to the SIP provider; false if
286     *        the profile has not been opened in the SIP service or the SIP
287     *        service has not yet successfully registered the profile to the SIP
288     *        provider
289     * @throws SipException if calling the SIP service results in an error
290     */
291    public boolean isRegistered(String localProfileUri) throws SipException {
292        try {
293            return mSipService.isRegistered(localProfileUri);
294        } catch (RemoteException e) {
295            throw new SipException("isRegistered()", e);
296        }
297    }
298
299    /**
300     * Creates a {@link SipAudioCall} to make a call. The attempt will be timed
301     * out if the call is not established within {@code timeout} seconds and
302     * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
303     * will be called.
304     *
305     * @param localProfile the SIP profile to make the call from
306     * @param peerProfile the SIP profile to make the call to
307     * @param listener to listen to the call events from {@link SipAudioCall};
308     *      can be null
309     * @param timeout the timeout value in seconds. Default value (defined by
310     *        SIP protocol) is used if {@code timeout} is zero or negative.
311     * @return a {@link SipAudioCall} object
312     * @throws SipException if calling the SIP service results in an error
313     * @see SipAudioCall.Listener.onError
314     */
315    public SipAudioCall makeAudioCall(SipProfile localProfile,
316            SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
317            throws SipException {
318        SipAudioCall call = new SipAudioCall(mContext, localProfile);
319        call.setListener(listener);
320        SipSession s = createSipSession(localProfile, null);
321        if (s == null) {
322            throw new SipException(
323                    "Failed to create SipSession; network available?");
324        }
325        call.makeCall(peerProfile, s, timeout);
326        return call;
327    }
328
329    /**
330     * Creates a {@link SipAudioCall} to make an audio call. The attempt will be
331     * timed out if the call is not established within {@code timeout} seconds
332     * and
333     * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
334     * will be called.
335     *
336     * @param localProfileUri URI of the SIP profile to make the call from
337     * @param peerProfileUri URI of the SIP profile to make the call to
338     * @param listener to listen to the call events from {@link SipAudioCall};
339     *      can be null
340     * @param timeout the timeout value in seconds. Default value (defined by
341     *        SIP protocol) is used if {@code timeout} is zero or negative.
342     * @return a {@link SipAudioCall} object
343     * @throws SipException if calling the SIP service results in an error
344     * @see SipAudioCall.Listener.onError
345     */
346    public SipAudioCall makeAudioCall(String localProfileUri,
347            String peerProfileUri, SipAudioCall.Listener listener, int timeout)
348            throws SipException {
349        try {
350            return makeAudioCall(
351                    new SipProfile.Builder(localProfileUri).build(),
352                    new SipProfile.Builder(peerProfileUri).build(), listener,
353                    timeout);
354        } catch (ParseException e) {
355            throw new SipException("build SipProfile", e);
356        }
357    }
358
359    /**
360     * The method calls {@code takeAudioCall(incomingCallIntent,
361     * listener, true}.
362     *
363     * @see #takeAudioCall(Intent, SipAudioCall.Listener, boolean)
364     */
365    public SipAudioCall takeAudioCall(Intent incomingCallIntent,
366            SipAudioCall.Listener listener) throws SipException {
367        return takeAudioCall(incomingCallIntent, listener, true);
368    }
369
370    /**
371     * Creates a {@link SipAudioCall} to take an incoming call. Before the call
372     * is returned, the listener will receive a
373     * {@link SipAudioCall.Listener#onRinging}
374     * callback.
375     *
376     * @param incomingCallIntent the incoming call broadcast intent
377     * @param listener to listen to the call events from {@link SipAudioCall};
378     *      can be null
379     * @return a {@link SipAudioCall} object
380     * @throws SipException if calling the SIP service results in an error
381     */
382    public SipAudioCall takeAudioCall(Intent incomingCallIntent,
383            SipAudioCall.Listener listener, boolean ringtoneEnabled)
384            throws SipException {
385        if (incomingCallIntent == null) return null;
386
387        String callId = getCallId(incomingCallIntent);
388        if (callId == null) {
389            throw new SipException("Call ID missing in incoming call intent");
390        }
391
392        String offerSd = getOfferSessionDescription(incomingCallIntent);
393        if (offerSd == null) {
394            throw new SipException("Session description missing in incoming "
395                    + "call intent");
396        }
397
398        try {
399            ISipSession session = mSipService.getPendingSession(callId);
400            if (session == null) return null;
401            SipAudioCall call = new SipAudioCall(
402                    mContext, session.getLocalProfile());
403            call.setRingtoneEnabled(ringtoneEnabled);
404            call.attachCall(new SipSession(session), offerSd);
405            call.setListener(listener);
406            return call;
407        } catch (Throwable t) {
408            throw new SipException("takeAudioCall()", t);
409        }
410    }
411
412    /**
413     * Checks if the intent is an incoming call broadcast intent.
414     *
415     * @param intent the intent in question
416     * @return true if the intent is an incoming call broadcast intent
417     */
418    public static boolean isIncomingCallIntent(Intent intent) {
419        if (intent == null) return false;
420        String callId = getCallId(intent);
421        String offerSd = getOfferSessionDescription(intent);
422        return ((callId != null) && (offerSd != null));
423    }
424
425    /**
426     * Gets the call ID from the specified incoming call broadcast intent.
427     *
428     * @param incomingCallIntent the incoming call broadcast intent
429     * @return the call ID or null if the intent does not contain it
430     */
431    public static String getCallId(Intent incomingCallIntent) {
432        return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
433    }
434
435    /**
436     * Gets the offer session description from the specified incoming call
437     * broadcast intent.
438     *
439     * @param incomingCallIntent the incoming call broadcast intent
440     * @return the offer session description or null if the intent does not
441     *      have it
442     */
443    public static String getOfferSessionDescription(Intent incomingCallIntent) {
444        return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD);
445    }
446
447    /**
448     * Creates an incoming call broadcast intent.
449     *
450     * @param callId the call ID of the incoming call
451     * @param sessionDescription the session description of the incoming call
452     * @return the incoming call intent
453     * @hide
454     */
455    public static Intent createIncomingCallBroadcast(String callId,
456            String sessionDescription) {
457        Intent intent = new Intent();
458        intent.putExtra(EXTRA_CALL_ID, callId);
459        intent.putExtra(EXTRA_OFFER_SD, sessionDescription);
460        return intent;
461    }
462
463    /**
464     * Manually registers the profile to the corresponding SIP provider for
465     * receiving calls.
466     * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is
467     * still needed to be called at least once in order for the SIP service to
468     * notify the caller with the {@code PendingIntent} when an incoming call is
469     * received.
470     *
471     * @param localProfile the SIP profile to register with
472     * @param expiryTime registration expiration time (in seconds)
473     * @param listener to listen to the registration events
474     * @throws SipException if calling the SIP service results in an error
475     */
476    public void register(SipProfile localProfile, int expiryTime,
477            SipRegistrationListener listener) throws SipException {
478        try {
479            ISipSession session = mSipService.createSession(localProfile,
480                    createRelay(listener, localProfile.getUriString()));
481            session.register(expiryTime);
482        } catch (RemoteException e) {
483            throw new SipException("register()", e);
484        }
485    }
486
487    /**
488     * Manually unregisters the profile from the corresponding SIP provider for
489     * stop receiving further calls. This may interference with the auto
490     * registration process in the SIP service if the auto-registration option
491     * in the profile is enabled.
492     *
493     * @param localProfile the SIP profile to register with
494     * @param listener to listen to the registration events
495     * @throws SipException if calling the SIP service results in an error
496     */
497    public void unregister(SipProfile localProfile,
498            SipRegistrationListener listener) throws SipException {
499        try {
500            ISipSession session = mSipService.createSession(localProfile,
501                    createRelay(listener, localProfile.getUriString()));
502            session.unregister();
503        } catch (RemoteException e) {
504            throw new SipException("unregister()", e);
505        }
506    }
507
508    /**
509     * Gets the {@link SipSession} that handles the incoming call. For audio
510     * calls, consider to use {@link SipAudioCall} to handle the incoming call.
511     * See {@link #takeAudioCall}. Note that the method may be called only once
512     * for the same intent. For subsequent calls on the same intent, the method
513     * returns null.
514     *
515     * @param incomingCallIntent the incoming call broadcast intent
516     * @return the session object that handles the incoming call
517     */
518    public SipSession getSessionFor(Intent incomingCallIntent)
519            throws SipException {
520        try {
521            String callId = getCallId(incomingCallIntent);
522            ISipSession s = mSipService.getPendingSession(callId);
523            return new SipSession(s);
524        } catch (RemoteException e) {
525            throw new SipException("getSessionFor()", e);
526        }
527    }
528
529    private static ISipSessionListener createRelay(
530            SipRegistrationListener listener, String uri) {
531        return ((listener == null) ? null : new ListenerRelay(listener, uri));
532    }
533
534    /**
535     * Creates a {@link SipSession} with the specified profile. Use other
536     * methods, if applicable, instead of interacting with {@link SipSession}
537     * directly.
538     *
539     * @param localProfile the SIP profile the session is associated with
540     * @param listener to listen to SIP session events
541     */
542    public SipSession createSipSession(SipProfile localProfile,
543            SipSession.Listener listener) throws SipException {
544        try {
545            ISipSession s = mSipService.createSession(localProfile, null);
546            return new SipSession(s, listener);
547        } catch (RemoteException e) {
548            throw new SipException("createSipSession()", e);
549        }
550    }
551
552    /**
553     * Gets the list of profiles hosted by the SIP service. The user information
554     * (username, password and display name) are crossed out.
555     * @hide
556     */
557    public SipProfile[] getListOfProfiles() {
558        try {
559            return mSipService.getListOfProfiles();
560        } catch (RemoteException e) {
561            return null;
562        }
563    }
564
565    private static class ListenerRelay extends SipSessionAdapter {
566        private SipRegistrationListener mListener;
567        private String mUri;
568
569        // listener must not be null
570        public ListenerRelay(SipRegistrationListener listener, String uri) {
571            mListener = listener;
572            mUri = uri;
573        }
574
575        private String getUri(ISipSession session) {
576            try {
577                return ((session == null)
578                        ? mUri
579                        : session.getLocalProfile().getUriString());
580            } catch (Throwable e) {
581                // SipService died? SIP stack died?
582                Log.w(TAG, "getUri(): " + e);
583                return null;
584            }
585        }
586
587        @Override
588        public void onRegistering(ISipSession session) {
589            mListener.onRegistering(getUri(session));
590        }
591
592        @Override
593        public void onRegistrationDone(ISipSession session, int duration) {
594            long expiryTime = duration;
595            if (duration > 0) expiryTime += System.currentTimeMillis();
596            mListener.onRegistrationDone(getUri(session), expiryTime);
597        }
598
599        @Override
600        public void onRegistrationFailed(ISipSession session, int errorCode,
601                String message) {
602            mListener.onRegistrationFailed(getUri(session), errorCode, message);
603        }
604
605        @Override
606        public void onRegistrationTimeout(ISipSession session) {
607            mListener.onRegistrationFailed(getUri(session),
608                    SipErrorCode.TIME_OUT, "registration timed out");
609        }
610    }
611}
612