blob: 1d6705592e0b589b7108d912d90d204fcc883d00 [file] [log] [blame]
Chung-yih Wang363c2ab2010-08-05 10:21:20 +08001/*
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
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080019import android.content.Context;
20import android.media.AudioManager;
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080021import android.net.rtp.AudioCodec;
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080022import android.net.rtp.AudioGroup;
23import android.net.rtp.AudioStream;
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080024import android.net.rtp.RtpStream;
25import android.net.sip.SimpleSessionDescription.Media;
26import android.net.wifi.WifiManager;
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080027import android.os.Message;
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080028import android.os.RemoteException;
repo sync20935612011-06-28 15:25:44 +080029import android.text.TextUtils;
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080030import android.util.Log;
31
32import java.io.IOException;
33import java.net.InetAddress;
34import java.net.UnknownHostException;
35import java.util.ArrayList;
36import java.util.HashMap;
37import java.util.List;
38import java.util.Map;
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080039
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080040/**
Scott Main02b1d682010-10-22 11:29:57 -070041 * Handles an Internet audio call over SIP. You can instantiate this class with {@link SipManager},
42 * using {@link SipManager#makeAudioCall makeAudioCall()} and {@link SipManager#takeAudioCall
43 * takeAudioCall()}.
Hung-ying Tyane87b6442010-10-18 19:45:59 +080044 *
Scott Main02b1d682010-10-22 11:29:57 -070045 * <p class="note"><strong>Note:</strong> Using this class require the
Hung-ying Tyane87b6442010-10-18 19:45:59 +080046 * {@link android.Manifest.permission#INTERNET} and
Joe Fernandez3aef8e1d2011-12-20 10:38:34 -080047 * {@link android.Manifest.permission#USE_SIP} permissions. In addition, {@link
Scott Main02b1d682010-10-22 11:29:57 -070048 * #startAudio} requires the
Hung-ying Tyane87b6442010-10-18 19:45:59 +080049 * {@link android.Manifest.permission#RECORD_AUDIO},
Scott Main02b1d682010-10-22 11:29:57 -070050 * {@link android.Manifest.permission#ACCESS_WIFI_STATE}, and
51 * {@link android.Manifest.permission#WAKE_LOCK} permissions; and {@link #setSpeakerMode
52 * setSpeakerMode()} requires the
53 * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p>
Joe Fernandez3aef8e1d2011-12-20 10:38:34 -080054 *
55 * <div class="special reference">
56 * <h3>Developer Guides</h3>
57 * <p>For more information about using SIP, read the
58 * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a>
59 * developer guide.</p>
60 * </div>
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080061 */
Hung-ying Tyan3a4197e2010-09-24 23:27:40 +080062public class SipAudioCall {
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080063 private static final String TAG = SipAudioCall.class.getSimpleName();
64 private static final boolean RELEASE_SOCKET = true;
65 private static final boolean DONT_RELEASE_SOCKET = false;
66 private static final int SESSION_TIMEOUT = 5; // in seconds
repo sync307f15f2011-07-12 08:30:20 +080067 private static final int TRANSFER_TIMEOUT = 15; // in seconds
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080068
Scott Main02b1d682010-10-22 11:29:57 -070069 /** Listener for events relating to a SIP call, such as when a call is being
70 * recieved ("on ringing") or a call is outgoing ("on calling").
71 * <p>Many of these events are also received by {@link SipSession.Listener}.</p>
72 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080073 public static class Listener {
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080074 /**
75 * Called when the call object is ready to make another call.
Hung-ying Tyan08faac32010-09-16 04:11:32 +080076 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080077 *
78 * @param call the call object that is ready to make another call
79 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080080 public void onReadyToCall(SipAudioCall call) {
81 onChanged(call);
82 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080083
84 /**
85 * Called when a request is sent out to initiate a new call.
Hung-ying Tyan08faac32010-09-16 04:11:32 +080086 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080087 *
88 * @param call the call object that carries out the audio call
89 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080090 public void onCalling(SipAudioCall call) {
91 onChanged(call);
92 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080093
94 /**
95 * Called when a new call comes in.
Hung-ying Tyan08faac32010-09-16 04:11:32 +080096 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080097 *
98 * @param call the call object that carries out the audio call
99 * @param caller the SIP profile of the caller
100 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800101 public void onRinging(SipAudioCall call, SipProfile caller) {
102 onChanged(call);
103 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800104
105 /**
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800106 * Called when a RINGING response is received for the INVITE request
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800107 * sent. The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800108 *
109 * @param call the call object that carries out the audio call
110 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800111 public void onRingingBack(SipAudioCall call) {
112 onChanged(call);
113 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800114
115 /**
116 * Called when the session is established.
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800117 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800118 *
119 * @param call the call object that carries out the audio call
120 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800121 public void onCallEstablished(SipAudioCall call) {
122 onChanged(call);
123 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800124
125 /**
126 * Called when the session is terminated.
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800127 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800128 *
129 * @param call the call object that carries out the audio call
130 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800131 public void onCallEnded(SipAudioCall call) {
132 onChanged(call);
133 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800134
135 /**
136 * Called when the peer is busy during session initialization.
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800137 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800138 *
139 * @param call the call object that carries out the audio call
140 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800141 public void onCallBusy(SipAudioCall call) {
142 onChanged(call);
143 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800144
145 /**
146 * Called when the call is on hold.
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800147 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800148 *
149 * @param call the call object that carries out the audio call
150 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800151 public void onCallHeld(SipAudioCall call) {
152 onChanged(call);
153 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800154
155 /**
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800156 * Called when an error occurs. The default implementation is no op.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800157 *
158 * @param call the call object that carries out the audio call
Hung-ying Tyan13f62702010-09-14 21:28:12 +0800159 * @param errorCode error code of this error
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800160 * @param errorMessage error message
Hung-ying Tyan97963792010-09-17 16:58:51 +0800161 * @see SipErrorCode
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800162 */
Hung-ying Tyan97963792010-09-17 16:58:51 +0800163 public void onError(SipAudioCall call, int errorCode,
Hung-ying Tyan903e1032010-09-09 20:07:14 +0800164 String errorMessage) {
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800165 // no-op
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800166 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800167
168 /**
169 * Called when an event occurs and the corresponding callback is not
170 * overridden. The default implementation is no op. Error events are
171 * not re-directed to this callback and are handled in {@link #onError}.
172 */
173 public void onChanged(SipAudioCall call) {
174 // no-op
175 }
176 }
177
178 private Context mContext;
179 private SipProfile mLocalProfile;
180 private SipAudioCall.Listener mListener;
181 private SipSession mSipSession;
repo sync1aceda32011-06-23 19:40:36 +0800182 private SipSession mTransferringSession;
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800183
184 private long mSessionId = System.currentTimeMillis();
185 private String mPeerSd;
186
187 private AudioStream mAudioStream;
188 private AudioGroup mAudioGroup;
189
190 private boolean mInCall = false;
191 private boolean mMuted = false;
192 private boolean mHold = false;
193
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800194 private SipProfile mPendingCallRequest;
195 private WifiManager mWm;
196 private WifiManager.WifiLock mWifiHighPerfLock;
197
198 private int mErrorCode = SipErrorCode.NO_ERROR;
199 private String mErrorMessage;
200
201 /**
202 * Creates a call object with the local SIP profile.
203 * @param context the context for accessing system services such as
204 * ringtone, audio, WIFI etc
205 */
206 public SipAudioCall(Context context, SipProfile localProfile) {
207 mContext = context;
208 mLocalProfile = localProfile;
209 mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800210 }
211
212 /**
213 * Sets the listener to listen to the audio call events. The method calls
Scott Main02b1d682010-10-22 11:29:57 -0700214 * {@link #setListener setListener(listener, false)}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800215 *
216 * @param listener to listen to the audio call events of this object
217 * @see #setListener(Listener, boolean)
218 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800219 public void setListener(SipAudioCall.Listener listener) {
220 setListener(listener, false);
221 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800222
223 /**
224 * Sets the listener to listen to the audio call events. A
225 * {@link SipAudioCall} can only hold one listener at a time. Subsequent
226 * calls to this method override the previous listener.
227 *
228 * @param listener to listen to the audio call events of this object
229 * @param callbackImmediately set to true if the caller wants to be called
230 * back immediately on the current state
231 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800232 public void setListener(SipAudioCall.Listener listener,
233 boolean callbackImmediately) {
234 mListener = listener;
235 try {
236 if ((listener == null) || !callbackImmediately) {
237 // do nothing
238 } else if (mErrorCode != SipErrorCode.NO_ERROR) {
239 listener.onError(this, mErrorCode, mErrorMessage);
240 } else if (mInCall) {
241 if (mHold) {
242 listener.onCallHeld(this);
243 } else {
244 listener.onCallEstablished(this);
245 }
246 } else {
247 int state = getState();
248 switch (state) {
249 case SipSession.State.READY_TO_CALL:
250 listener.onReadyToCall(this);
251 break;
252 case SipSession.State.INCOMING_CALL:
253 listener.onRinging(this, getPeerProfile());
254 break;
255 case SipSession.State.OUTGOING_CALL:
256 listener.onCalling(this);
257 break;
258 case SipSession.State.OUTGOING_CALL_RING_BACK:
259 listener.onRingingBack(this);
260 break;
261 }
262 }
263 } catch (Throwable t) {
264 Log.e(TAG, "setListener()", t);
265 }
266 }
267
268 /**
269 * Checks if the call is established.
270 *
271 * @return true if the call is established
272 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800273 public boolean isInCall() {
274 synchronized (this) {
275 return mInCall;
276 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800277 }
278
279 /**
280 * Checks if the call is on hold.
281 *
282 * @return true if the call is on hold
283 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800284 public boolean isOnHold() {
285 synchronized (this) {
286 return mHold;
287 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800288 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800289
290 /**
Hung-ying Tyanafa583e2010-09-17 15:40:31 +0800291 * Closes this object. This object is not usable after being closed.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800292 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800293 public void close() {
294 close(true);
295 }
296
297 private synchronized void close(boolean closeRtp) {
298 if (closeRtp) stopCall(RELEASE_SOCKET);
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800299
300 mInCall = false;
301 mHold = false;
302 mSessionId = System.currentTimeMillis();
303 mErrorCode = SipErrorCode.NO_ERROR;
304 mErrorMessage = null;
305
306 if (mSipSession != null) {
307 mSipSession.setListener(null);
308 mSipSession = null;
309 }
310 }
311
312 /**
313 * Gets the local SIP profile.
314 *
315 * @return the local SIP profile
316 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800317 public SipProfile getLocalProfile() {
318 synchronized (this) {
319 return mLocalProfile;
320 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800321 }
322
323 /**
324 * Gets the peer's SIP profile.
325 *
326 * @return the peer's SIP profile
327 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800328 public SipProfile getPeerProfile() {
329 synchronized (this) {
330 return (mSipSession == null) ? null : mSipSession.getPeerProfile();
331 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800332 }
333
334 /**
335 * Gets the state of the {@link SipSession} that carries this call.
336 * The value returned must be one of the states in {@link SipSession.State}.
337 *
338 * @return the session state
339 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800340 public int getState() {
341 synchronized (this) {
342 if (mSipSession == null) return SipSession.State.READY_TO_CALL;
343 return mSipSession.getState();
344 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800345 }
346
347
348 /**
349 * Gets the {@link SipSession} that carries this call.
350 *
351 * @return the session object that carries this call
352 * @hide
353 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800354 public SipSession getSipSession() {
355 synchronized (this) {
356 return mSipSession;
357 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800358 }
359
repo sync1aceda32011-06-23 19:40:36 +0800360 private synchronized void transferToNewSession() {
361 if (mTransferringSession == null) return;
362 SipSession origin = mSipSession;
363 mSipSession = mTransferringSession;
364 mTransferringSession = null;
365
366 // stop the replaced call.
367 if (mAudioStream != null) {
368 mAudioStream.join(null);
369 } else {
370 try {
371 mAudioStream = new AudioStream(InetAddress.getByName(
372 getLocalIp()));
373 } catch (Throwable t) {
374 Log.i(TAG, "transferToNewSession(): " + t);
375 }
376 }
377 if (origin != null) origin.endCall();
378 startAudio();
379 }
380
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800381 private SipSession.Listener createListener() {
382 return new SipSession.Listener() {
383 @Override
384 public void onCalling(SipSession session) {
385 Log.d(TAG, "calling... " + session);
386 Listener listener = mListener;
387 if (listener != null) {
388 try {
389 listener.onCalling(SipAudioCall.this);
390 } catch (Throwable t) {
391 Log.i(TAG, "onCalling(): " + t);
392 }
393 }
394 }
395
396 @Override
397 public void onRingingBack(SipSession session) {
398 Log.d(TAG, "sip call ringing back: " + session);
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800399 Listener listener = mListener;
400 if (listener != null) {
401 try {
402 listener.onRingingBack(SipAudioCall.this);
403 } catch (Throwable t) {
404 Log.i(TAG, "onRingingBack(): " + t);
405 }
406 }
407 }
408
409 @Override
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800410 public void onRinging(SipSession session,
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800411 SipProfile peerProfile, String sessionDescription) {
repo sync20935612011-06-28 15:25:44 +0800412 // this callback is triggered only for reinvite.
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800413 synchronized (SipAudioCall.this) {
414 if ((mSipSession == null) || !mInCall
415 || !session.getCallId().equals(
416 mSipSession.getCallId())) {
417 // should not happen
418 session.endCall();
419 return;
420 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800421
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800422 // session changing request
423 try {
424 String answer = createAnswer(sessionDescription).encode();
425 mSipSession.answerCall(answer, SESSION_TIMEOUT);
426 } catch (Throwable e) {
427 Log.e(TAG, "onRinging()", e);
428 session.endCall();
429 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800430 }
431 }
432
433 @Override
434 public void onCallEstablished(SipSession session,
435 String sessionDescription) {
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800436 mPeerSd = sessionDescription;
437 Log.v(TAG, "onCallEstablished()" + mPeerSd);
438
repo sync1aceda32011-06-23 19:40:36 +0800439 // TODO: how to notify the UI that the remote party is changed
440 if ((mTransferringSession != null)
441 && (session == mTransferringSession)) {
442 transferToNewSession();
443 return;
444 }
445
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800446 Listener listener = mListener;
447 if (listener != null) {
448 try {
449 if (mHold) {
450 listener.onCallHeld(SipAudioCall.this);
451 } else {
452 listener.onCallEstablished(SipAudioCall.this);
453 }
454 } catch (Throwable t) {
455 Log.i(TAG, "onCallEstablished(): " + t);
456 }
457 }
458 }
459
460 @Override
461 public void onCallEnded(SipSession session) {
repo sync1aceda32011-06-23 19:40:36 +0800462 Log.d(TAG, "sip call ended: " + session + " mSipSession:" + mSipSession);
463 // reset the trasnferring session if it is the one.
464 if (session == mTransferringSession) {
465 mTransferringSession = null;
466 return;
467 }
468 // or ignore the event if the original session is being
469 // transferred to the new one.
470 if ((mTransferringSession != null) ||
471 (session != mSipSession)) return;
472
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800473 Listener listener = mListener;
474 if (listener != null) {
475 try {
476 listener.onCallEnded(SipAudioCall.this);
477 } catch (Throwable t) {
478 Log.i(TAG, "onCallEnded(): " + t);
479 }
480 }
481 close();
482 }
483
484 @Override
485 public void onCallBusy(SipSession session) {
486 Log.d(TAG, "sip call busy: " + session);
487 Listener listener = mListener;
488 if (listener != null) {
489 try {
490 listener.onCallBusy(SipAudioCall.this);
491 } catch (Throwable t) {
492 Log.i(TAG, "onCallBusy(): " + t);
493 }
494 }
495 close(false);
496 }
497
498 @Override
499 public void onCallChangeFailed(SipSession session, int errorCode,
500 String message) {
501 Log.d(TAG, "sip call change failed: " + message);
502 mErrorCode = errorCode;
503 mErrorMessage = message;
504 Listener listener = mListener;
505 if (listener != null) {
506 try {
507 listener.onError(SipAudioCall.this, mErrorCode,
508 message);
509 } catch (Throwable t) {
510 Log.i(TAG, "onCallBusy(): " + t);
511 }
512 }
513 }
514
515 @Override
516 public void onError(SipSession session, int errorCode,
517 String message) {
518 SipAudioCall.this.onError(errorCode, message);
519 }
520
521 @Override
522 public void onRegistering(SipSession session) {
523 // irrelevant
524 }
525
526 @Override
527 public void onRegistrationTimeout(SipSession session) {
528 // irrelevant
529 }
530
531 @Override
532 public void onRegistrationFailed(SipSession session, int errorCode,
533 String message) {
534 // irrelevant
535 }
536
537 @Override
538 public void onRegistrationDone(SipSession session, int duration) {
539 // irrelevant
540 }
repo sync1aceda32011-06-23 19:40:36 +0800541
542 @Override
543 public void onCallTransferring(SipSession newSession,
544 String sessionDescription) {
545 Log.v(TAG, "onCallTransferring mSipSession:"
546 + mSipSession + " newSession:" + newSession);
547 mTransferringSession = newSession;
repo sync1aceda32011-06-23 19:40:36 +0800548 try {
repo sync307f15f2011-07-12 08:30:20 +0800549 if (sessionDescription == null) {
550 newSession.makeCall(newSession.getPeerProfile(),
551 createOffer().encode(), TRANSFER_TIMEOUT);
552 } else {
553 String answer = createAnswer(sessionDescription).encode();
554 newSession.answerCall(answer, SESSION_TIMEOUT);
555 }
repo sync1aceda32011-06-23 19:40:36 +0800556 } catch (Throwable e) {
557 Log.e(TAG, "onCallTransferring()", e);
558 newSession.endCall();
559 }
560 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800561 };
562 }
563
564 private void onError(int errorCode, String message) {
565 Log.d(TAG, "sip session error: "
566 + SipErrorCode.toString(errorCode) + ": " + message);
567 mErrorCode = errorCode;
568 mErrorMessage = message;
569 Listener listener = mListener;
570 if (listener != null) {
571 try {
572 listener.onError(this, errorCode, message);
573 } catch (Throwable t) {
574 Log.i(TAG, "onError(): " + t);
575 }
576 }
577 synchronized (this) {
578 if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST)
579 || !isInCall()) {
580 close(true);
581 }
582 }
583 }
584
585 /**
586 * Attaches an incoming call to this call object.
587 *
588 * @param session the session that receives the incoming call
589 * @param sessionDescription the session description of the incoming call
590 * @throws SipException if the SIP service fails to attach this object to
Hung-ying Tyan5bd37822010-12-20 19:08:24 +0800591 * the session or VOIP API is not supported by the device
592 * @see SipManager#isVoipSupported
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800593 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800594 public void attachCall(SipSession session, String sessionDescription)
595 throws SipException {
Hung-ying Tyan5bd37822010-12-20 19:08:24 +0800596 if (!SipManager.isVoipSupported(mContext)) {
597 throw new SipException("VOIP API is not supported");
598 }
599
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800600 synchronized (this) {
601 mSipSession = session;
602 mPeerSd = sessionDescription;
603 Log.v(TAG, "attachCall()" + mPeerSd);
604 try {
605 session.setListener(createListener());
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800606 } catch (Throwable e) {
607 Log.e(TAG, "attachCall()", e);
608 throwSipException(e);
609 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800610 }
611 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800612
613 /**
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800614 * Initiates an audio call to the specified profile. The attempt will be
615 * timed out if the call is not established within {@code timeout} seconds
Scott Main02b1d682010-10-22 11:29:57 -0700616 * and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800617 * will be called.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800618 *
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800619 * @param peerProfile the SIP profile to make the call to
Hung-ying Tyan3a4197e2010-09-24 23:27:40 +0800620 * @param sipSession the {@link SipSession} for carrying out the call
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800621 * @param timeout the timeout value in seconds. Default value (defined by
622 * SIP protocol) is used if {@code timeout} is zero or negative.
Scott Main02b1d682010-10-22 11:29:57 -0700623 * @see Listener#onError
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800624 * @throws SipException if the SIP service fails to create a session for the
Hung-ying Tyan5bd37822010-12-20 19:08:24 +0800625 * call or VOIP API is not supported by the device
626 * @see SipManager#isVoipSupported
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800627 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800628 public void makeCall(SipProfile peerProfile, SipSession sipSession,
629 int timeout) throws SipException {
Hung-ying Tyan5bd37822010-12-20 19:08:24 +0800630 if (!SipManager.isVoipSupported(mContext)) {
631 throw new SipException("VOIP API is not supported");
632 }
633
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800634 synchronized (this) {
635 mSipSession = sipSession;
636 try {
637 mAudioStream = new AudioStream(InetAddress.getByName(
638 getLocalIp()));
639 sipSession.setListener(createListener());
640 sipSession.makeCall(peerProfile, createOffer().encode(),
641 timeout);
642 } catch (IOException e) {
643 throw new SipException("makeCall()", e);
644 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800645 }
646 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800647
648 /**
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800649 * Ends a call.
650 * @throws SipException if the SIP service fails to end the call
Hung-ying Tyanafa583e2010-09-17 15:40:31 +0800651 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800652 public void endCall() throws SipException {
653 synchronized (this) {
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800654 stopCall(RELEASE_SOCKET);
655 mInCall = false;
Hung-ying Tyanafa583e2010-09-17 15:40:31 +0800656
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800657 // perform the above local ops first and then network op
658 if (mSipSession != null) mSipSession.endCall();
659 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800660 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800661
662 /**
Hung-ying Tyan286bb5a2010-09-14 20:43:54 +0800663 * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800664 * called. The attempt will be timed out if the call is not established
665 * within {@code timeout} seconds and
Scott Main02b1d682010-10-22 11:29:57 -0700666 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800667 * will be called.
668 *
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800669 * @param timeout the timeout value in seconds. Default value (defined by
670 * SIP protocol) is used if {@code timeout} is zero or negative.
Scott Main02b1d682010-10-22 11:29:57 -0700671 * @see Listener#onError
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800672 * @throws SipException if the SIP service fails to hold the call
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800673 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800674 public void holdCall(int timeout) throws SipException {
675 synchronized (this) {
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800676 if (mHold) return;
Hung-ying Tyan5bd37822010-12-20 19:08:24 +0800677 if (mSipSession == null) {
678 throw new SipException("Not in a call to hold call");
679 }
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800680 mSipSession.changeCall(createHoldOffer().encode(), timeout);
681 mHold = true;
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800682 setAudioGroupMode();
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800683 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800684 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800685
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800686 /**
687 * Answers a call. The attempt will be timed out if the call is not
688 * established within {@code timeout} seconds and
Scott Main02b1d682010-10-22 11:29:57 -0700689 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800690 * will be called.
691 *
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800692 * @param timeout the timeout value in seconds. Default value (defined by
693 * SIP protocol) is used if {@code timeout} is zero or negative.
Scott Main02b1d682010-10-22 11:29:57 -0700694 * @see Listener#onError
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800695 * @throws SipException if the SIP service fails to answer the call
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800696 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800697 public void answerCall(int timeout) throws SipException {
698 synchronized (this) {
Hung-ying Tyan5bd37822010-12-20 19:08:24 +0800699 if (mSipSession == null) {
700 throw new SipException("No call to answer");
701 }
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800702 try {
703 mAudioStream = new AudioStream(InetAddress.getByName(
704 getLocalIp()));
705 mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
706 } catch (IOException e) {
707 throw new SipException("answerCall()", e);
708 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800709 }
710 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800711
712 /**
713 * Continues a call that's on hold. When succeeds,
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800714 * {@link Listener#onCallEstablished} is called. The attempt will be timed
715 * out if the call is not established within {@code timeout} seconds and
Scott Main02b1d682010-10-22 11:29:57 -0700716 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800717 * will be called.
718 *
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800719 * @param timeout the timeout value in seconds. Default value (defined by
720 * SIP protocol) is used if {@code timeout} is zero or negative.
Scott Main02b1d682010-10-22 11:29:57 -0700721 * @see Listener#onError
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800722 * @throws SipException if the SIP service fails to unhold the call
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800723 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800724 public void continueCall(int timeout) throws SipException {
725 synchronized (this) {
726 if (!mHold) return;
727 mSipSession.changeCall(createContinueOffer().encode(), timeout);
728 mHold = false;
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800729 setAudioGroupMode();
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800730 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800731 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800732
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800733 private SimpleSessionDescription createOffer() {
734 SimpleSessionDescription offer =
735 new SimpleSessionDescription(mSessionId, getLocalIp());
736 AudioCodec[] codecs = AudioCodec.getCodecs();
737 Media media = offer.newMedia(
738 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
739 for (AudioCodec codec : AudioCodec.getCodecs()) {
740 media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
741 }
742 media.setRtpPayload(127, "telephone-event/8000", "0-15");
743 return offer;
744 }
745
746 private SimpleSessionDescription createAnswer(String offerSd) {
repo sync20935612011-06-28 15:25:44 +0800747 if (TextUtils.isEmpty(offerSd)) return createOffer();
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800748 SimpleSessionDescription offer =
749 new SimpleSessionDescription(offerSd);
750 SimpleSessionDescription answer =
751 new SimpleSessionDescription(mSessionId, getLocalIp());
752 AudioCodec codec = null;
753 for (Media media : offer.getMedia()) {
754 if ((codec == null) && (media.getPort() > 0)
755 && "audio".equals(media.getType())
756 && "RTP/AVP".equals(media.getProtocol())) {
757 // Find the first audio codec we supported.
758 for (int type : media.getRtpPayloadTypes()) {
759 codec = AudioCodec.getCodec(type, media.getRtpmap(type),
760 media.getFmtp(type));
761 if (codec != null) {
762 break;
763 }
764 }
765 if (codec != null) {
766 Media reply = answer.newMedia(
767 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
768 reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
769
770 // Check if DTMF is supported in the same media.
771 for (int type : media.getRtpPayloadTypes()) {
772 String rtpmap = media.getRtpmap(type);
773 if ((type != codec.type) && (rtpmap != null)
774 && rtpmap.startsWith("telephone-event")) {
775 reply.setRtpPayload(
776 type, rtpmap, media.getFmtp(type));
777 }
778 }
779
780 // Handle recvonly and sendonly.
781 if (media.getAttribute("recvonly") != null) {
782 answer.setAttribute("sendonly", "");
783 } else if(media.getAttribute("sendonly") != null) {
784 answer.setAttribute("recvonly", "");
785 } else if(offer.getAttribute("recvonly") != null) {
786 answer.setAttribute("sendonly", "");
787 } else if(offer.getAttribute("sendonly") != null) {
788 answer.setAttribute("recvonly", "");
789 }
790 continue;
791 }
792 }
793 // Reject the media.
794 Media reply = answer.newMedia(
795 media.getType(), 0, 1, media.getProtocol());
796 for (String format : media.getFormats()) {
797 reply.setFormat(format, null);
798 }
799 }
800 if (codec == null) {
801 throw new IllegalStateException("Reject SDP: no suitable codecs");
802 }
803 return answer;
804 }
805
806 private SimpleSessionDescription createHoldOffer() {
807 SimpleSessionDescription offer = createContinueOffer();
808 offer.setAttribute("sendonly", "");
809 return offer;
810 }
811
812 private SimpleSessionDescription createContinueOffer() {
813 SimpleSessionDescription offer =
814 new SimpleSessionDescription(mSessionId, getLocalIp());
815 Media media = offer.newMedia(
816 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
817 AudioCodec codec = mAudioStream.getCodec();
818 media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
819 int dtmfType = mAudioStream.getDtmfType();
820 if (dtmfType != -1) {
821 media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15");
822 }
823 return offer;
824 }
825
826 private void grabWifiHighPerfLock() {
827 if (mWifiHighPerfLock == null) {
828 Log.v(TAG, "acquire wifi high perf lock");
829 mWifiHighPerfLock = ((WifiManager)
830 mContext.getSystemService(Context.WIFI_SERVICE))
831 .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG);
832 mWifiHighPerfLock.acquire();
833 }
834 }
835
836 private void releaseWifiHighPerfLock() {
837 if (mWifiHighPerfLock != null) {
838 Log.v(TAG, "release wifi high perf lock");
839 mWifiHighPerfLock.release();
840 mWifiHighPerfLock = null;
841 }
842 }
843
844 private boolean isWifiOn() {
845 return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
846 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800847
848 /** Toggles mute. */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800849 public void toggleMute() {
850 synchronized (this) {
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800851 mMuted = !mMuted;
852 setAudioGroupMode();
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800853 }
854 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800855
856 /**
857 * Checks if the call is muted.
858 *
859 * @return true if the call is muted
860 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800861 public boolean isMuted() {
862 synchronized (this) {
863 return mMuted;
864 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800865 }
866
Hung-ying Tyane87b6442010-10-18 19:45:59 +0800867 /**
868 * Puts the device to speaker mode.
Scott Main02b1d682010-10-22 11:29:57 -0700869 * <p class="note"><strong>Note:</strong> Requires the
870 * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p>
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800871 *
872 * @param speakerMode set true to enable speaker mode; false to disable
Hung-ying Tyane87b6442010-10-18 19:45:59 +0800873 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800874 public void setSpeakerMode(boolean speakerMode) {
875 synchronized (this) {
876 ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
877 .setSpeakerphoneOn(speakerMode);
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800878 setAudioGroupMode();
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800879 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800880 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800881
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800882 private boolean isSpeakerOn() {
883 return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
884 .isSpeakerphoneOn();
885 }
886
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800887 /**
Scott Main02b1d682010-10-22 11:29:57 -0700888 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
889 * event 0--9 maps to decimal
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800890 * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
891 * flash to 16. Currently, event flash is not supported.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800892 *
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800893 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
894 * inputs.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800895 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800896 public void sendDtmf(int code) {
897 sendDtmf(code, null);
898 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800899
900 /**
Scott Main02b1d682010-10-22 11:29:57 -0700901 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
902 * event 0--9 maps to decimal
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800903 * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
904 * flash to 16. Currently, event flash is not supported.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800905 *
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800906 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
907 * inputs.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800908 * @param result the result message to send when done
909 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800910 public void sendDtmf(int code, Message result) {
911 synchronized (this) {
912 AudioGroup audioGroup = getAudioGroup();
913 if ((audioGroup != null) && (mSipSession != null)
914 && (SipSession.State.IN_CALL == getState())) {
915 Log.v(TAG, "send DTMF: " + code);
916 audioGroup.sendDtmf(code);
917 }
918 if (result != null) result.sendToTarget();
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800919 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800920 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800921
922 /**
923 * Gets the {@link AudioStream} object used in this call. The object
924 * represents the RTP stream that carries the audio data to and from the
925 * peer. The object may not be created before the call is established. And
926 * it is undefined after the call ends or the {@link #close} method is
927 * called.
928 *
929 * @return the {@link AudioStream} object or null if the RTP stream has not
930 * yet been set up
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800931 * @hide
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800932 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800933 public AudioStream getAudioStream() {
934 synchronized (this) {
935 return mAudioStream;
936 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800937 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800938
939 /**
940 * Gets the {@link AudioGroup} object which the {@link AudioStream} object
941 * joins. The group object may not exist before the call is established.
942 * Also, the {@code AudioStream} may change its group during a call (e.g.,
943 * after the call is held/un-held). Finally, the {@code AudioGroup} object
944 * returned by this method is undefined after the call ends or the
Hung-ying Tyan3294d442010-08-18 14:37:59 +0800945 * {@link #close} method is called. If a group object is set by
946 * {@link #setAudioGroup(AudioGroup)}, then this method returns that object.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800947 *
948 * @return the {@link AudioGroup} object or null if the RTP stream has not
949 * yet been set up
950 * @see #getAudioStream
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800951 * @hide
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800952 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800953 public AudioGroup getAudioGroup() {
954 synchronized (this) {
955 if (mAudioGroup != null) return mAudioGroup;
956 return ((mAudioStream == null) ? null : mAudioStream.getGroup());
957 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800958 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800959
960 /**
Hung-ying Tyan3294d442010-08-18 14:37:59 +0800961 * Sets the {@link AudioGroup} object which the {@link AudioStream} object
962 * joins. If {@code audioGroup} is null, then the {@code AudioGroup} object
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800963 * will be dynamically created when needed. Note that the mode of the
964 * {@code AudioGroup} is not changed according to the audio settings (i.e.,
965 * hold, mute, speaker phone) of this object. This is mainly used to merge
966 * multiple {@code SipAudioCall} objects to form a conference call. The
967 * settings of the first object (that merges others) override others'.
Hung-ying Tyan3294d442010-08-18 14:37:59 +0800968 *
969 * @see #getAudioStream
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800970 * @hide
Hung-ying Tyan3294d442010-08-18 14:37:59 +0800971 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800972 public void setAudioGroup(AudioGroup group) {
973 synchronized (this) {
974 if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
975 mAudioStream.join(group);
976 }
977 mAudioGroup = group;
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800978 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800979 }
Hung-ying Tyan3294d442010-08-18 14:37:59 +0800980
981 /**
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800982 * Starts the audio for the established call. This method should be called
983 * after {@link Listener#onCallEstablished} is called.
Scott Main02b1d682010-10-22 11:29:57 -0700984 * <p class="note"><strong>Note:</strong> Requires the
Hung-ying Tyane87b6442010-10-18 19:45:59 +0800985 * {@link android.Manifest.permission#RECORD_AUDIO},
986 * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and
Scott Main02b1d682010-10-22 11:29:57 -0700987 * {@link android.Manifest.permission#WAKE_LOCK} permissions.</p>
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800988 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800989 public void startAudio() {
990 try {
991 startAudioInternal();
992 } catch (UnknownHostException e) {
993 onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage());
994 } catch (Throwable e) {
995 onError(SipErrorCode.CLIENT_ERROR, e.getMessage());
996 }
997 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800998
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800999 private synchronized void startAudioInternal() throws UnknownHostException {
1000 if (mPeerSd == null) {
1001 Log.v(TAG, "startAudioInternal() mPeerSd = null");
1002 throw new IllegalStateException("mPeerSd = null");
1003 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +08001004
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001005 stopCall(DONT_RELEASE_SOCKET);
1006 mInCall = true;
Chung-yih Wang363c2ab2010-08-05 10:21:20 +08001007
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001008 // Run exact the same logic in createAnswer() to setup mAudioStream.
1009 SimpleSessionDescription offer =
1010 new SimpleSessionDescription(mPeerSd);
1011 AudioStream stream = mAudioStream;
1012 AudioCodec codec = null;
1013 for (Media media : offer.getMedia()) {
1014 if ((codec == null) && (media.getPort() > 0)
1015 && "audio".equals(media.getType())
1016 && "RTP/AVP".equals(media.getProtocol())) {
1017 // Find the first audio codec we supported.
1018 for (int type : media.getRtpPayloadTypes()) {
1019 codec = AudioCodec.getCodec(
1020 type, media.getRtpmap(type), media.getFmtp(type));
1021 if (codec != null) {
1022 break;
1023 }
1024 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +08001025
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001026 if (codec != null) {
1027 // Associate with the remote host.
1028 String address = media.getAddress();
1029 if (address == null) {
1030 address = offer.getAddress();
1031 }
1032 stream.associate(InetAddress.getByName(address),
1033 media.getPort());
1034
1035 stream.setDtmfType(-1);
1036 stream.setCodec(codec);
1037 // Check if DTMF is supported in the same media.
1038 for (int type : media.getRtpPayloadTypes()) {
1039 String rtpmap = media.getRtpmap(type);
1040 if ((type != codec.type) && (rtpmap != null)
1041 && rtpmap.startsWith("telephone-event")) {
1042 stream.setDtmfType(type);
1043 }
1044 }
1045
1046 // Handle recvonly and sendonly.
1047 if (mHold) {
1048 stream.setMode(RtpStream.MODE_NORMAL);
1049 } else if (media.getAttribute("recvonly") != null) {
1050 stream.setMode(RtpStream.MODE_SEND_ONLY);
1051 } else if(media.getAttribute("sendonly") != null) {
1052 stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
1053 } else if(offer.getAttribute("recvonly") != null) {
1054 stream.setMode(RtpStream.MODE_SEND_ONLY);
1055 } else if(offer.getAttribute("sendonly") != null) {
1056 stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
1057 } else {
1058 stream.setMode(RtpStream.MODE_NORMAL);
1059 }
1060 break;
1061 }
1062 }
1063 }
1064 if (codec == null) {
1065 throw new IllegalStateException("Reject SDP: no suitable codecs");
1066 }
1067
1068 if (isWifiOn()) grabWifiHighPerfLock();
1069
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001070 // AudioGroup logic:
1071 AudioGroup audioGroup = getAudioGroup();
1072 if (mHold) {
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001073 // don't create an AudioGroup here; doing so will fail if
1074 // there's another AudioGroup out there that's active
1075 } else {
1076 if (audioGroup == null) audioGroup = new AudioGroup();
1077 stream.join(audioGroup);
Hung-ying Tyanfa814632010-10-25 17:23:10 +08001078 }
1079 setAudioGroupMode();
1080 }
1081
1082 // set audio group mode based on current audio configuration
1083 private void setAudioGroupMode() {
1084 AudioGroup audioGroup = getAudioGroup();
1085 if (audioGroup != null) {
1086 if (mHold) {
1087 audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
1088 } else if (mMuted) {
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001089 audioGroup.setMode(AudioGroup.MODE_MUTED);
Hung-ying Tyanfa814632010-10-25 17:23:10 +08001090 } else if (isSpeakerOn()) {
1091 audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001092 } else {
1093 audioGroup.setMode(AudioGroup.MODE_NORMAL);
1094 }
1095 }
1096 }
1097
1098 private void stopCall(boolean releaseSocket) {
1099 Log.d(TAG, "stop audiocall");
1100 releaseWifiHighPerfLock();
1101 if (mAudioStream != null) {
1102 mAudioStream.join(null);
1103
1104 if (releaseSocket) {
1105 mAudioStream.release();
1106 mAudioStream = null;
1107 }
1108 }
1109 }
1110
1111 private String getLocalIp() {
1112 return mSipSession.getLocalIp();
1113 }
1114
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001115 private void throwSipException(Throwable throwable) throws SipException {
1116 if (throwable instanceof SipException) {
1117 throw (SipException) throwable;
1118 } else {
1119 throw new SipException("", throwable);
1120 }
1121 }
1122
1123 private SipProfile getPeerProfile(SipSession session) {
1124 return session.getPeerProfile();
1125 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +08001126}