blob: fcdbd2cef6bfad01537ce65ad4749effd9a62549 [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
Scott Main02b1d682010-10-22 11:29:57 -070047 * {@link android.Manifest.permission#USE_SIP} permissions.<br/><br/>In addition, {@link
48 * #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>
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080054 */
Hung-ying Tyan3a4197e2010-09-24 23:27:40 +080055public class SipAudioCall {
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080056 private static final String TAG = SipAudioCall.class.getSimpleName();
57 private static final boolean RELEASE_SOCKET = true;
58 private static final boolean DONT_RELEASE_SOCKET = false;
59 private static final int SESSION_TIMEOUT = 5; // in seconds
repo sync307f15f2011-07-12 08:30:20 +080060 private static final int TRANSFER_TIMEOUT = 15; // in seconds
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080061
Scott Main02b1d682010-10-22 11:29:57 -070062 /** Listener for events relating to a SIP call, such as when a call is being
63 * recieved ("on ringing") or a call is outgoing ("on calling").
64 * <p>Many of these events are also received by {@link SipSession.Listener}.</p>
65 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080066 public static class Listener {
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080067 /**
68 * Called when the call object is ready to make another call.
Hung-ying Tyan08faac32010-09-16 04:11:32 +080069 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080070 *
71 * @param call the call object that is ready to make another call
72 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080073 public void onReadyToCall(SipAudioCall call) {
74 onChanged(call);
75 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080076
77 /**
78 * Called when a request is sent out to initiate a new call.
Hung-ying Tyan08faac32010-09-16 04:11:32 +080079 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080080 *
81 * @param call the call object that carries out the audio call
82 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080083 public void onCalling(SipAudioCall call) {
84 onChanged(call);
85 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080086
87 /**
88 * Called when a new call comes in.
Hung-ying Tyan08faac32010-09-16 04:11:32 +080089 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080090 *
91 * @param call the call object that carries out the audio call
92 * @param caller the SIP profile of the caller
93 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080094 public void onRinging(SipAudioCall call, SipProfile caller) {
95 onChanged(call);
96 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +080097
98 /**
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080099 * Called when a RINGING response is received for the INVITE request
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800100 * sent. The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800101 *
102 * @param call the call object that carries out the audio call
103 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800104 public void onRingingBack(SipAudioCall call) {
105 onChanged(call);
106 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800107
108 /**
109 * Called when the session is established.
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800110 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800111 *
112 * @param call the call object that carries out the audio call
113 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800114 public void onCallEstablished(SipAudioCall call) {
115 onChanged(call);
116 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800117
118 /**
119 * Called when the session is terminated.
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800120 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800121 *
122 * @param call the call object that carries out the audio call
123 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800124 public void onCallEnded(SipAudioCall call) {
125 onChanged(call);
126 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800127
128 /**
129 * Called when the peer is busy during session initialization.
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800130 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800131 *
132 * @param call the call object that carries out the audio call
133 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800134 public void onCallBusy(SipAudioCall call) {
135 onChanged(call);
136 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800137
138 /**
139 * Called when the call is on hold.
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800140 * The default implementation calls {@link #onChanged}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800141 *
142 * @param call the call object that carries out the audio call
143 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800144 public void onCallHeld(SipAudioCall call) {
145 onChanged(call);
146 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800147
148 /**
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800149 * Called when an error occurs. The default implementation is no op.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800150 *
151 * @param call the call object that carries out the audio call
Hung-ying Tyan13f62702010-09-14 21:28:12 +0800152 * @param errorCode error code of this error
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800153 * @param errorMessage error message
Hung-ying Tyan97963792010-09-17 16:58:51 +0800154 * @see SipErrorCode
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800155 */
Hung-ying Tyan97963792010-09-17 16:58:51 +0800156 public void onError(SipAudioCall call, int errorCode,
Hung-ying Tyan903e1032010-09-09 20:07:14 +0800157 String errorMessage) {
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800158 // no-op
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800159 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800160
161 /**
162 * Called when an event occurs and the corresponding callback is not
163 * overridden. The default implementation is no op. Error events are
164 * not re-directed to this callback and are handled in {@link #onError}.
165 */
166 public void onChanged(SipAudioCall call) {
167 // no-op
168 }
169 }
170
171 private Context mContext;
172 private SipProfile mLocalProfile;
173 private SipAudioCall.Listener mListener;
174 private SipSession mSipSession;
repo sync1aceda32011-06-23 19:40:36 +0800175 private SipSession mTransferringSession;
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800176
177 private long mSessionId = System.currentTimeMillis();
178 private String mPeerSd;
179
180 private AudioStream mAudioStream;
181 private AudioGroup mAudioGroup;
182
183 private boolean mInCall = false;
184 private boolean mMuted = false;
185 private boolean mHold = false;
186
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800187 private SipProfile mPendingCallRequest;
188 private WifiManager mWm;
189 private WifiManager.WifiLock mWifiHighPerfLock;
190
191 private int mErrorCode = SipErrorCode.NO_ERROR;
192 private String mErrorMessage;
193
194 /**
195 * Creates a call object with the local SIP profile.
196 * @param context the context for accessing system services such as
197 * ringtone, audio, WIFI etc
198 */
199 public SipAudioCall(Context context, SipProfile localProfile) {
200 mContext = context;
201 mLocalProfile = localProfile;
202 mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800203 }
204
205 /**
206 * Sets the listener to listen to the audio call events. The method calls
Scott Main02b1d682010-10-22 11:29:57 -0700207 * {@link #setListener setListener(listener, false)}.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800208 *
209 * @param listener to listen to the audio call events of this object
210 * @see #setListener(Listener, boolean)
211 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800212 public void setListener(SipAudioCall.Listener listener) {
213 setListener(listener, false);
214 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800215
216 /**
217 * Sets the listener to listen to the audio call events. A
218 * {@link SipAudioCall} can only hold one listener at a time. Subsequent
219 * calls to this method override the previous listener.
220 *
221 * @param listener to listen to the audio call events of this object
222 * @param callbackImmediately set to true if the caller wants to be called
223 * back immediately on the current state
224 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800225 public void setListener(SipAudioCall.Listener listener,
226 boolean callbackImmediately) {
227 mListener = listener;
228 try {
229 if ((listener == null) || !callbackImmediately) {
230 // do nothing
231 } else if (mErrorCode != SipErrorCode.NO_ERROR) {
232 listener.onError(this, mErrorCode, mErrorMessage);
233 } else if (mInCall) {
234 if (mHold) {
235 listener.onCallHeld(this);
236 } else {
237 listener.onCallEstablished(this);
238 }
239 } else {
240 int state = getState();
241 switch (state) {
242 case SipSession.State.READY_TO_CALL:
243 listener.onReadyToCall(this);
244 break;
245 case SipSession.State.INCOMING_CALL:
246 listener.onRinging(this, getPeerProfile());
247 break;
248 case SipSession.State.OUTGOING_CALL:
249 listener.onCalling(this);
250 break;
251 case SipSession.State.OUTGOING_CALL_RING_BACK:
252 listener.onRingingBack(this);
253 break;
254 }
255 }
256 } catch (Throwable t) {
257 Log.e(TAG, "setListener()", t);
258 }
259 }
260
261 /**
262 * Checks if the call is established.
263 *
264 * @return true if the call is established
265 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800266 public boolean isInCall() {
267 synchronized (this) {
268 return mInCall;
269 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800270 }
271
272 /**
273 * Checks if the call is on hold.
274 *
275 * @return true if the call is on hold
276 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800277 public boolean isOnHold() {
278 synchronized (this) {
279 return mHold;
280 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800281 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800282
283 /**
Hung-ying Tyanafa583e2010-09-17 15:40:31 +0800284 * Closes this object. This object is not usable after being closed.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800285 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800286 public void close() {
287 close(true);
288 }
289
290 private synchronized void close(boolean closeRtp) {
291 if (closeRtp) stopCall(RELEASE_SOCKET);
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800292
293 mInCall = false;
294 mHold = false;
295 mSessionId = System.currentTimeMillis();
296 mErrorCode = SipErrorCode.NO_ERROR;
297 mErrorMessage = null;
298
299 if (mSipSession != null) {
300 mSipSession.setListener(null);
301 mSipSession = null;
302 }
303 }
304
305 /**
306 * Gets the local SIP profile.
307 *
308 * @return the local SIP profile
309 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800310 public SipProfile getLocalProfile() {
311 synchronized (this) {
312 return mLocalProfile;
313 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800314 }
315
316 /**
317 * Gets the peer's SIP profile.
318 *
319 * @return the peer's SIP profile
320 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800321 public SipProfile getPeerProfile() {
322 synchronized (this) {
323 return (mSipSession == null) ? null : mSipSession.getPeerProfile();
324 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800325 }
326
327 /**
328 * Gets the state of the {@link SipSession} that carries this call.
329 * The value returned must be one of the states in {@link SipSession.State}.
330 *
331 * @return the session state
332 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800333 public int getState() {
334 synchronized (this) {
335 if (mSipSession == null) return SipSession.State.READY_TO_CALL;
336 return mSipSession.getState();
337 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800338 }
339
340
341 /**
342 * Gets the {@link SipSession} that carries this call.
343 *
344 * @return the session object that carries this call
345 * @hide
346 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800347 public SipSession getSipSession() {
348 synchronized (this) {
349 return mSipSession;
350 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800351 }
352
repo sync1aceda32011-06-23 19:40:36 +0800353 private synchronized void transferToNewSession() {
354 if (mTransferringSession == null) return;
355 SipSession origin = mSipSession;
356 mSipSession = mTransferringSession;
357 mTransferringSession = null;
358
359 // stop the replaced call.
360 if (mAudioStream != null) {
361 mAudioStream.join(null);
362 } else {
363 try {
364 mAudioStream = new AudioStream(InetAddress.getByName(
365 getLocalIp()));
366 } catch (Throwable t) {
367 Log.i(TAG, "transferToNewSession(): " + t);
368 }
369 }
370 if (origin != null) origin.endCall();
371 startAudio();
372 }
373
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800374 private SipSession.Listener createListener() {
375 return new SipSession.Listener() {
376 @Override
377 public void onCalling(SipSession session) {
378 Log.d(TAG, "calling... " + session);
379 Listener listener = mListener;
380 if (listener != null) {
381 try {
382 listener.onCalling(SipAudioCall.this);
383 } catch (Throwable t) {
384 Log.i(TAG, "onCalling(): " + t);
385 }
386 }
387 }
388
389 @Override
390 public void onRingingBack(SipSession session) {
391 Log.d(TAG, "sip call ringing back: " + session);
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800392 Listener listener = mListener;
393 if (listener != null) {
394 try {
395 listener.onRingingBack(SipAudioCall.this);
396 } catch (Throwable t) {
397 Log.i(TAG, "onRingingBack(): " + t);
398 }
399 }
400 }
401
402 @Override
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800403 public void onRinging(SipSession session,
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800404 SipProfile peerProfile, String sessionDescription) {
repo sync20935612011-06-28 15:25:44 +0800405 // this callback is triggered only for reinvite.
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800406 synchronized (SipAudioCall.this) {
407 if ((mSipSession == null) || !mInCall
408 || !session.getCallId().equals(
409 mSipSession.getCallId())) {
410 // should not happen
411 session.endCall();
412 return;
413 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800414
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800415 // session changing request
416 try {
417 String answer = createAnswer(sessionDescription).encode();
418 mSipSession.answerCall(answer, SESSION_TIMEOUT);
419 } catch (Throwable e) {
420 Log.e(TAG, "onRinging()", e);
421 session.endCall();
422 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800423 }
424 }
425
426 @Override
427 public void onCallEstablished(SipSession session,
428 String sessionDescription) {
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800429 mPeerSd = sessionDescription;
430 Log.v(TAG, "onCallEstablished()" + mPeerSd);
431
repo sync1aceda32011-06-23 19:40:36 +0800432 // TODO: how to notify the UI that the remote party is changed
433 if ((mTransferringSession != null)
434 && (session == mTransferringSession)) {
435 transferToNewSession();
436 return;
437 }
438
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800439 Listener listener = mListener;
440 if (listener != null) {
441 try {
442 if (mHold) {
443 listener.onCallHeld(SipAudioCall.this);
444 } else {
445 listener.onCallEstablished(SipAudioCall.this);
446 }
447 } catch (Throwable t) {
448 Log.i(TAG, "onCallEstablished(): " + t);
449 }
450 }
451 }
452
453 @Override
454 public void onCallEnded(SipSession session) {
repo sync1aceda32011-06-23 19:40:36 +0800455 Log.d(TAG, "sip call ended: " + session + " mSipSession:" + mSipSession);
456 // reset the trasnferring session if it is the one.
457 if (session == mTransferringSession) {
458 mTransferringSession = null;
459 return;
460 }
461 // or ignore the event if the original session is being
462 // transferred to the new one.
463 if ((mTransferringSession != null) ||
464 (session != mSipSession)) return;
465
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800466 Listener listener = mListener;
467 if (listener != null) {
468 try {
469 listener.onCallEnded(SipAudioCall.this);
470 } catch (Throwable t) {
471 Log.i(TAG, "onCallEnded(): " + t);
472 }
473 }
474 close();
475 }
476
477 @Override
478 public void onCallBusy(SipSession session) {
479 Log.d(TAG, "sip call busy: " + session);
480 Listener listener = mListener;
481 if (listener != null) {
482 try {
483 listener.onCallBusy(SipAudioCall.this);
484 } catch (Throwable t) {
485 Log.i(TAG, "onCallBusy(): " + t);
486 }
487 }
488 close(false);
489 }
490
491 @Override
492 public void onCallChangeFailed(SipSession session, int errorCode,
493 String message) {
494 Log.d(TAG, "sip call change failed: " + message);
495 mErrorCode = errorCode;
496 mErrorMessage = message;
497 Listener listener = mListener;
498 if (listener != null) {
499 try {
500 listener.onError(SipAudioCall.this, mErrorCode,
501 message);
502 } catch (Throwable t) {
503 Log.i(TAG, "onCallBusy(): " + t);
504 }
505 }
506 }
507
508 @Override
509 public void onError(SipSession session, int errorCode,
510 String message) {
511 SipAudioCall.this.onError(errorCode, message);
512 }
513
514 @Override
515 public void onRegistering(SipSession session) {
516 // irrelevant
517 }
518
519 @Override
520 public void onRegistrationTimeout(SipSession session) {
521 // irrelevant
522 }
523
524 @Override
525 public void onRegistrationFailed(SipSession session, int errorCode,
526 String message) {
527 // irrelevant
528 }
529
530 @Override
531 public void onRegistrationDone(SipSession session, int duration) {
532 // irrelevant
533 }
repo sync1aceda32011-06-23 19:40:36 +0800534
535 @Override
536 public void onCallTransferring(SipSession newSession,
537 String sessionDescription) {
538 Log.v(TAG, "onCallTransferring mSipSession:"
539 + mSipSession + " newSession:" + newSession);
540 mTransferringSession = newSession;
repo sync1aceda32011-06-23 19:40:36 +0800541 try {
repo sync307f15f2011-07-12 08:30:20 +0800542 if (sessionDescription == null) {
543 newSession.makeCall(newSession.getPeerProfile(),
544 createOffer().encode(), TRANSFER_TIMEOUT);
545 } else {
546 String answer = createAnswer(sessionDescription).encode();
547 newSession.answerCall(answer, SESSION_TIMEOUT);
548 }
repo sync1aceda32011-06-23 19:40:36 +0800549 } catch (Throwable e) {
550 Log.e(TAG, "onCallTransferring()", e);
551 newSession.endCall();
552 }
553 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800554 };
555 }
556
557 private void onError(int errorCode, String message) {
558 Log.d(TAG, "sip session error: "
559 + SipErrorCode.toString(errorCode) + ": " + message);
560 mErrorCode = errorCode;
561 mErrorMessage = message;
562 Listener listener = mListener;
563 if (listener != null) {
564 try {
565 listener.onError(this, errorCode, message);
566 } catch (Throwable t) {
567 Log.i(TAG, "onError(): " + t);
568 }
569 }
570 synchronized (this) {
571 if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST)
572 || !isInCall()) {
573 close(true);
574 }
575 }
576 }
577
578 /**
579 * Attaches an incoming call to this call object.
580 *
581 * @param session the session that receives the incoming call
582 * @param sessionDescription the session description of the incoming call
583 * @throws SipException if the SIP service fails to attach this object to
Hung-ying Tyan5bd37822010-12-20 19:08:24 +0800584 * the session or VOIP API is not supported by the device
585 * @see SipManager#isVoipSupported
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800586 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800587 public void attachCall(SipSession session, String sessionDescription)
588 throws SipException {
Hung-ying Tyan5bd37822010-12-20 19:08:24 +0800589 if (!SipManager.isVoipSupported(mContext)) {
590 throw new SipException("VOIP API is not supported");
591 }
592
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800593 synchronized (this) {
594 mSipSession = session;
595 mPeerSd = sessionDescription;
596 Log.v(TAG, "attachCall()" + mPeerSd);
597 try {
598 session.setListener(createListener());
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800599 } catch (Throwable e) {
600 Log.e(TAG, "attachCall()", e);
601 throwSipException(e);
602 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800603 }
604 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800605
606 /**
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800607 * Initiates an audio call to the specified profile. The attempt will be
608 * timed out if the call is not established within {@code timeout} seconds
Scott Main02b1d682010-10-22 11:29:57 -0700609 * and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800610 * will be called.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800611 *
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800612 * @param peerProfile the SIP profile to make the call to
Hung-ying Tyan3a4197e2010-09-24 23:27:40 +0800613 * @param sipSession the {@link SipSession} for carrying out the call
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800614 * @param timeout the timeout value in seconds. Default value (defined by
615 * SIP protocol) is used if {@code timeout} is zero or negative.
Scott Main02b1d682010-10-22 11:29:57 -0700616 * @see Listener#onError
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800617 * @throws SipException if the SIP service fails to create a session for the
Hung-ying Tyan5bd37822010-12-20 19:08:24 +0800618 * call or VOIP API is not supported by the device
619 * @see SipManager#isVoipSupported
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800620 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800621 public void makeCall(SipProfile peerProfile, SipSession sipSession,
622 int timeout) throws SipException {
Hung-ying Tyan5bd37822010-12-20 19:08:24 +0800623 if (!SipManager.isVoipSupported(mContext)) {
624 throw new SipException("VOIP API is not supported");
625 }
626
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800627 synchronized (this) {
628 mSipSession = sipSession;
629 try {
630 mAudioStream = new AudioStream(InetAddress.getByName(
631 getLocalIp()));
632 sipSession.setListener(createListener());
633 sipSession.makeCall(peerProfile, createOffer().encode(),
634 timeout);
635 } catch (IOException e) {
636 throw new SipException("makeCall()", e);
637 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800638 }
639 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800640
641 /**
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800642 * Ends a call.
643 * @throws SipException if the SIP service fails to end the call
Hung-ying Tyanafa583e2010-09-17 15:40:31 +0800644 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800645 public void endCall() throws SipException {
646 synchronized (this) {
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800647 stopCall(RELEASE_SOCKET);
648 mInCall = false;
Hung-ying Tyanafa583e2010-09-17 15:40:31 +0800649
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800650 // perform the above local ops first and then network op
651 if (mSipSession != null) mSipSession.endCall();
652 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800653 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800654
655 /**
Hung-ying Tyan286bb5a2010-09-14 20:43:54 +0800656 * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800657 * called. The attempt will be timed out if the call is not established
658 * within {@code timeout} seconds and
Scott Main02b1d682010-10-22 11:29:57 -0700659 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800660 * will be called.
661 *
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800662 * @param timeout the timeout value in seconds. Default value (defined by
663 * SIP protocol) is used if {@code timeout} is zero or negative.
Scott Main02b1d682010-10-22 11:29:57 -0700664 * @see Listener#onError
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800665 * @throws SipException if the SIP service fails to hold the call
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800666 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800667 public void holdCall(int timeout) throws SipException {
668 synchronized (this) {
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800669 if (mHold) return;
Hung-ying Tyan5bd37822010-12-20 19:08:24 +0800670 if (mSipSession == null) {
671 throw new SipException("Not in a call to hold call");
672 }
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800673 mSipSession.changeCall(createHoldOffer().encode(), timeout);
674 mHold = true;
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800675 setAudioGroupMode();
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800676 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800677 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800678
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800679 /**
680 * Answers a call. The attempt will be timed out if the call is not
681 * established within {@code timeout} seconds and
Scott Main02b1d682010-10-22 11:29:57 -0700682 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800683 * will be called.
684 *
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800685 * @param timeout the timeout value in seconds. Default value (defined by
686 * SIP protocol) is used if {@code timeout} is zero or negative.
Scott Main02b1d682010-10-22 11:29:57 -0700687 * @see Listener#onError
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800688 * @throws SipException if the SIP service fails to answer the call
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800689 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800690 public void answerCall(int timeout) throws SipException {
691 synchronized (this) {
Hung-ying Tyan5bd37822010-12-20 19:08:24 +0800692 if (mSipSession == null) {
693 throw new SipException("No call to answer");
694 }
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800695 try {
696 mAudioStream = new AudioStream(InetAddress.getByName(
697 getLocalIp()));
698 mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
699 } catch (IOException e) {
700 throw new SipException("answerCall()", e);
701 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800702 }
703 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800704
705 /**
706 * Continues a call that's on hold. When succeeds,
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800707 * {@link Listener#onCallEstablished} is called. The attempt will be timed
708 * out if the call is not established within {@code timeout} seconds and
Scott Main02b1d682010-10-22 11:29:57 -0700709 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
Hung-ying Tyan9352cf12010-09-16 20:14:18 +0800710 * will be called.
711 *
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800712 * @param timeout the timeout value in seconds. Default value (defined by
713 * SIP protocol) is used if {@code timeout} is zero or negative.
Scott Main02b1d682010-10-22 11:29:57 -0700714 * @see Listener#onError
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800715 * @throws SipException if the SIP service fails to unhold the call
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800716 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800717 public void continueCall(int timeout) throws SipException {
718 synchronized (this) {
719 if (!mHold) return;
720 mSipSession.changeCall(createContinueOffer().encode(), timeout);
721 mHold = false;
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800722 setAudioGroupMode();
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800723 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800724 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800725
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800726 private SimpleSessionDescription createOffer() {
727 SimpleSessionDescription offer =
728 new SimpleSessionDescription(mSessionId, getLocalIp());
729 AudioCodec[] codecs = AudioCodec.getCodecs();
730 Media media = offer.newMedia(
731 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
732 for (AudioCodec codec : AudioCodec.getCodecs()) {
733 media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
734 }
735 media.setRtpPayload(127, "telephone-event/8000", "0-15");
736 return offer;
737 }
738
739 private SimpleSessionDescription createAnswer(String offerSd) {
repo sync20935612011-06-28 15:25:44 +0800740 if (TextUtils.isEmpty(offerSd)) return createOffer();
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800741 SimpleSessionDescription offer =
742 new SimpleSessionDescription(offerSd);
743 SimpleSessionDescription answer =
744 new SimpleSessionDescription(mSessionId, getLocalIp());
745 AudioCodec codec = null;
746 for (Media media : offer.getMedia()) {
747 if ((codec == null) && (media.getPort() > 0)
748 && "audio".equals(media.getType())
749 && "RTP/AVP".equals(media.getProtocol())) {
750 // Find the first audio codec we supported.
751 for (int type : media.getRtpPayloadTypes()) {
752 codec = AudioCodec.getCodec(type, media.getRtpmap(type),
753 media.getFmtp(type));
754 if (codec != null) {
755 break;
756 }
757 }
758 if (codec != null) {
759 Media reply = answer.newMedia(
760 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
761 reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
762
763 // Check if DTMF is supported in the same media.
764 for (int type : media.getRtpPayloadTypes()) {
765 String rtpmap = media.getRtpmap(type);
766 if ((type != codec.type) && (rtpmap != null)
767 && rtpmap.startsWith("telephone-event")) {
768 reply.setRtpPayload(
769 type, rtpmap, media.getFmtp(type));
770 }
771 }
772
773 // Handle recvonly and sendonly.
774 if (media.getAttribute("recvonly") != null) {
775 answer.setAttribute("sendonly", "");
776 } else if(media.getAttribute("sendonly") != null) {
777 answer.setAttribute("recvonly", "");
778 } else if(offer.getAttribute("recvonly") != null) {
779 answer.setAttribute("sendonly", "");
780 } else if(offer.getAttribute("sendonly") != null) {
781 answer.setAttribute("recvonly", "");
782 }
783 continue;
784 }
785 }
786 // Reject the media.
787 Media reply = answer.newMedia(
788 media.getType(), 0, 1, media.getProtocol());
789 for (String format : media.getFormats()) {
790 reply.setFormat(format, null);
791 }
792 }
793 if (codec == null) {
794 throw new IllegalStateException("Reject SDP: no suitable codecs");
795 }
796 return answer;
797 }
798
799 private SimpleSessionDescription createHoldOffer() {
800 SimpleSessionDescription offer = createContinueOffer();
801 offer.setAttribute("sendonly", "");
802 return offer;
803 }
804
805 private SimpleSessionDescription createContinueOffer() {
806 SimpleSessionDescription offer =
807 new SimpleSessionDescription(mSessionId, getLocalIp());
808 Media media = offer.newMedia(
809 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
810 AudioCodec codec = mAudioStream.getCodec();
811 media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
812 int dtmfType = mAudioStream.getDtmfType();
813 if (dtmfType != -1) {
814 media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15");
815 }
816 return offer;
817 }
818
819 private void grabWifiHighPerfLock() {
820 if (mWifiHighPerfLock == null) {
821 Log.v(TAG, "acquire wifi high perf lock");
822 mWifiHighPerfLock = ((WifiManager)
823 mContext.getSystemService(Context.WIFI_SERVICE))
824 .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG);
825 mWifiHighPerfLock.acquire();
826 }
827 }
828
829 private void releaseWifiHighPerfLock() {
830 if (mWifiHighPerfLock != null) {
831 Log.v(TAG, "release wifi high perf lock");
832 mWifiHighPerfLock.release();
833 mWifiHighPerfLock = null;
834 }
835 }
836
837 private boolean isWifiOn() {
838 return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
839 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800840
841 /** Toggles mute. */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800842 public void toggleMute() {
843 synchronized (this) {
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800844 mMuted = !mMuted;
845 setAudioGroupMode();
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800846 }
847 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800848
849 /**
850 * Checks if the call is muted.
851 *
852 * @return true if the call is muted
853 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800854 public boolean isMuted() {
855 synchronized (this) {
856 return mMuted;
857 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800858 }
859
Hung-ying Tyane87b6442010-10-18 19:45:59 +0800860 /**
861 * Puts the device to speaker mode.
Scott Main02b1d682010-10-22 11:29:57 -0700862 * <p class="note"><strong>Note:</strong> Requires the
863 * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p>
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800864 *
865 * @param speakerMode set true to enable speaker mode; false to disable
Hung-ying Tyane87b6442010-10-18 19:45:59 +0800866 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800867 public void setSpeakerMode(boolean speakerMode) {
868 synchronized (this) {
869 ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
870 .setSpeakerphoneOn(speakerMode);
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800871 setAudioGroupMode();
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800872 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800873 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800874
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800875 private boolean isSpeakerOn() {
876 return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
877 .isSpeakerphoneOn();
878 }
879
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800880 /**
Scott Main02b1d682010-10-22 11:29:57 -0700881 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
882 * event 0--9 maps to decimal
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800883 * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
884 * flash to 16. Currently, event flash is not supported.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800885 *
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800886 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
887 * inputs.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800888 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800889 public void sendDtmf(int code) {
890 sendDtmf(code, null);
891 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800892
893 /**
Scott Main02b1d682010-10-22 11:29:57 -0700894 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
895 * event 0--9 maps to decimal
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800896 * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
897 * flash to 16. Currently, event flash is not supported.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800898 *
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800899 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
900 * inputs.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800901 * @param result the result message to send when done
902 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800903 public void sendDtmf(int code, Message result) {
904 synchronized (this) {
905 AudioGroup audioGroup = getAudioGroup();
906 if ((audioGroup != null) && (mSipSession != null)
907 && (SipSession.State.IN_CALL == getState())) {
908 Log.v(TAG, "send DTMF: " + code);
909 audioGroup.sendDtmf(code);
910 }
911 if (result != null) result.sendToTarget();
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800912 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800913 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800914
915 /**
916 * Gets the {@link AudioStream} object used in this call. The object
917 * represents the RTP stream that carries the audio data to and from the
918 * peer. The object may not be created before the call is established. And
919 * it is undefined after the call ends or the {@link #close} method is
920 * called.
921 *
922 * @return the {@link AudioStream} object or null if the RTP stream has not
923 * yet been set up
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800924 * @hide
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800925 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800926 public AudioStream getAudioStream() {
927 synchronized (this) {
928 return mAudioStream;
929 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800930 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800931
932 /**
933 * Gets the {@link AudioGroup} object which the {@link AudioStream} object
934 * joins. The group object may not exist before the call is established.
935 * Also, the {@code AudioStream} may change its group during a call (e.g.,
936 * after the call is held/un-held). Finally, the {@code AudioGroup} object
937 * returned by this method is undefined after the call ends or the
Hung-ying Tyan3294d442010-08-18 14:37:59 +0800938 * {@link #close} method is called. If a group object is set by
939 * {@link #setAudioGroup(AudioGroup)}, then this method returns that object.
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800940 *
941 * @return the {@link AudioGroup} object or null if the RTP stream has not
942 * yet been set up
943 * @see #getAudioStream
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800944 * @hide
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800945 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800946 public AudioGroup getAudioGroup() {
947 synchronized (this) {
948 if (mAudioGroup != null) return mAudioGroup;
949 return ((mAudioStream == null) ? null : mAudioStream.getGroup());
950 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800951 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800952
953 /**
Hung-ying Tyan3294d442010-08-18 14:37:59 +0800954 * Sets the {@link AudioGroup} object which the {@link AudioStream} object
955 * joins. If {@code audioGroup} is null, then the {@code AudioGroup} object
Hung-ying Tyanfa814632010-10-25 17:23:10 +0800956 * will be dynamically created when needed. Note that the mode of the
957 * {@code AudioGroup} is not changed according to the audio settings (i.e.,
958 * hold, mute, speaker phone) of this object. This is mainly used to merge
959 * multiple {@code SipAudioCall} objects to form a conference call. The
960 * settings of the first object (that merges others) override others'.
Hung-ying Tyan3294d442010-08-18 14:37:59 +0800961 *
962 * @see #getAudioStream
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800963 * @hide
Hung-ying Tyan3294d442010-08-18 14:37:59 +0800964 */
Hung-ying Tyan08faac32010-09-16 04:11:32 +0800965 public void setAudioGroup(AudioGroup group) {
966 synchronized (this) {
967 if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
968 mAudioStream.join(group);
969 }
970 mAudioGroup = group;
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800971 }
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800972 }
Hung-ying Tyan3294d442010-08-18 14:37:59 +0800973
974 /**
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800975 * Starts the audio for the established call. This method should be called
976 * after {@link Listener#onCallEstablished} is called.
Scott Main02b1d682010-10-22 11:29:57 -0700977 * <p class="note"><strong>Note:</strong> Requires the
Hung-ying Tyane87b6442010-10-18 19:45:59 +0800978 * {@link android.Manifest.permission#RECORD_AUDIO},
979 * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and
Scott Main02b1d682010-10-22 11:29:57 -0700980 * {@link android.Manifest.permission#WAKE_LOCK} permissions.</p>
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800981 */
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800982 public void startAudio() {
983 try {
984 startAudioInternal();
985 } catch (UnknownHostException e) {
986 onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage());
987 } catch (Throwable e) {
988 onError(SipErrorCode.CLIENT_ERROR, e.getMessage());
989 }
990 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800991
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800992 private synchronized void startAudioInternal() throws UnknownHostException {
993 if (mPeerSd == null) {
994 Log.v(TAG, "startAudioInternal() mPeerSd = null");
995 throw new IllegalStateException("mPeerSd = null");
996 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +0800997
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800998 stopCall(DONT_RELEASE_SOCKET);
999 mInCall = true;
Chung-yih Wang363c2ab2010-08-05 10:21:20 +08001000
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001001 // Run exact the same logic in createAnswer() to setup mAudioStream.
1002 SimpleSessionDescription offer =
1003 new SimpleSessionDescription(mPeerSd);
1004 AudioStream stream = mAudioStream;
1005 AudioCodec codec = null;
1006 for (Media media : offer.getMedia()) {
1007 if ((codec == null) && (media.getPort() > 0)
1008 && "audio".equals(media.getType())
1009 && "RTP/AVP".equals(media.getProtocol())) {
1010 // Find the first audio codec we supported.
1011 for (int type : media.getRtpPayloadTypes()) {
1012 codec = AudioCodec.getCodec(
1013 type, media.getRtpmap(type), media.getFmtp(type));
1014 if (codec != null) {
1015 break;
1016 }
1017 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +08001018
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001019 if (codec != null) {
1020 // Associate with the remote host.
1021 String address = media.getAddress();
1022 if (address == null) {
1023 address = offer.getAddress();
1024 }
1025 stream.associate(InetAddress.getByName(address),
1026 media.getPort());
1027
1028 stream.setDtmfType(-1);
1029 stream.setCodec(codec);
1030 // Check if DTMF is supported in the same media.
1031 for (int type : media.getRtpPayloadTypes()) {
1032 String rtpmap = media.getRtpmap(type);
1033 if ((type != codec.type) && (rtpmap != null)
1034 && rtpmap.startsWith("telephone-event")) {
1035 stream.setDtmfType(type);
1036 }
1037 }
1038
1039 // Handle recvonly and sendonly.
1040 if (mHold) {
1041 stream.setMode(RtpStream.MODE_NORMAL);
1042 } else if (media.getAttribute("recvonly") != null) {
1043 stream.setMode(RtpStream.MODE_SEND_ONLY);
1044 } else if(media.getAttribute("sendonly") != null) {
1045 stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
1046 } else if(offer.getAttribute("recvonly") != null) {
1047 stream.setMode(RtpStream.MODE_SEND_ONLY);
1048 } else if(offer.getAttribute("sendonly") != null) {
1049 stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
1050 } else {
1051 stream.setMode(RtpStream.MODE_NORMAL);
1052 }
1053 break;
1054 }
1055 }
1056 }
1057 if (codec == null) {
1058 throw new IllegalStateException("Reject SDP: no suitable codecs");
1059 }
1060
1061 if (isWifiOn()) grabWifiHighPerfLock();
1062
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001063 // AudioGroup logic:
1064 AudioGroup audioGroup = getAudioGroup();
1065 if (mHold) {
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001066 // don't create an AudioGroup here; doing so will fail if
1067 // there's another AudioGroup out there that's active
1068 } else {
1069 if (audioGroup == null) audioGroup = new AudioGroup();
1070 stream.join(audioGroup);
Hung-ying Tyanfa814632010-10-25 17:23:10 +08001071 }
1072 setAudioGroupMode();
1073 }
1074
1075 // set audio group mode based on current audio configuration
1076 private void setAudioGroupMode() {
1077 AudioGroup audioGroup = getAudioGroup();
1078 if (audioGroup != null) {
1079 if (mHold) {
1080 audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
1081 } else if (mMuted) {
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001082 audioGroup.setMode(AudioGroup.MODE_MUTED);
Hung-ying Tyanfa814632010-10-25 17:23:10 +08001083 } else if (isSpeakerOn()) {
1084 audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001085 } else {
1086 audioGroup.setMode(AudioGroup.MODE_NORMAL);
1087 }
1088 }
1089 }
1090
1091 private void stopCall(boolean releaseSocket) {
1092 Log.d(TAG, "stop audiocall");
1093 releaseWifiHighPerfLock();
1094 if (mAudioStream != null) {
1095 mAudioStream.join(null);
1096
1097 if (releaseSocket) {
1098 mAudioStream.release();
1099 mAudioStream = null;
1100 }
1101 }
1102 }
1103
1104 private String getLocalIp() {
1105 return mSipSession.getLocalIp();
1106 }
1107
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001108 private void throwSipException(Throwable throwable) throws SipException {
1109 if (throwable instanceof SipException) {
1110 throw (SipException) throwable;
1111 } else {
1112 throw new SipException("", throwable);
1113 }
1114 }
1115
1116 private SipProfile getPeerProfile(SipSession session) {
1117 return session.getPeerProfile();
1118 }
Chung-yih Wang363c2ab2010-08-05 10:21:20 +08001119}