1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.sip; 18 19 import android.content.Context; 20 import android.media.AudioManager; 21 import android.net.rtp.AudioCodec; 22 import android.net.rtp.AudioGroup; 23 import android.net.rtp.AudioStream; 24 import android.net.rtp.RtpStream; 25 import android.net.sip.SimpleSessionDescription.Media; 26 import android.net.wifi.WifiManager; 27 import android.os.Message; 28 import android.os.RemoteException; 29 import android.text.TextUtils; 30 import android.util.Log; 31 32 import java.io.IOException; 33 import java.net.InetAddress; 34 import java.net.UnknownHostException; 35 import java.util.ArrayList; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 40 /** 41 * 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()}. 44 * 45 * <p class="note"><strong>Note:</strong> Using this class require the 46 * {@link android.Manifest.permission#INTERNET} and 47 * {@link android.Manifest.permission#USE_SIP} permissions. In addition, {@link 48 * #startAudio} requires the 49 * {@link android.Manifest.permission#RECORD_AUDIO}, 50 * {@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> 54 * 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> 61 */ 62 public class SipAudioCall { 63 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 67 private static final int TRANSFER_TIMEOUT = 15; // in seconds 68 69 /** 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 */ 73 public static class Listener { 74 /** 75 * Called when the call object is ready to make another call. 76 * The default implementation calls {@link #onChanged}. 77 * 78 * @param call the call object that is ready to make another call 79 */ onReadyToCall(SipAudioCall call)80 public void onReadyToCall(SipAudioCall call) { 81 onChanged(call); 82 } 83 84 /** 85 * Called when a request is sent out to initiate a new call. 86 * The default implementation calls {@link #onChanged}. 87 * 88 * @param call the call object that carries out the audio call 89 */ onCalling(SipAudioCall call)90 public void onCalling(SipAudioCall call) { 91 onChanged(call); 92 } 93 94 /** 95 * Called when a new call comes in. 96 * The default implementation calls {@link #onChanged}. 97 * 98 * @param call the call object that carries out the audio call 99 * @param caller the SIP profile of the caller 100 */ onRinging(SipAudioCall call, SipProfile caller)101 public void onRinging(SipAudioCall call, SipProfile caller) { 102 onChanged(call); 103 } 104 105 /** 106 * Called when a RINGING response is received for the INVITE request 107 * sent. The default implementation calls {@link #onChanged}. 108 * 109 * @param call the call object that carries out the audio call 110 */ onRingingBack(SipAudioCall call)111 public void onRingingBack(SipAudioCall call) { 112 onChanged(call); 113 } 114 115 /** 116 * Called when the session is established. 117 * The default implementation calls {@link #onChanged}. 118 * 119 * @param call the call object that carries out the audio call 120 */ onCallEstablished(SipAudioCall call)121 public void onCallEstablished(SipAudioCall call) { 122 onChanged(call); 123 } 124 125 /** 126 * Called when the session is terminated. 127 * The default implementation calls {@link #onChanged}. 128 * 129 * @param call the call object that carries out the audio call 130 */ onCallEnded(SipAudioCall call)131 public void onCallEnded(SipAudioCall call) { 132 onChanged(call); 133 } 134 135 /** 136 * Called when the peer is busy during session initialization. 137 * The default implementation calls {@link #onChanged}. 138 * 139 * @param call the call object that carries out the audio call 140 */ onCallBusy(SipAudioCall call)141 public void onCallBusy(SipAudioCall call) { 142 onChanged(call); 143 } 144 145 /** 146 * Called when the call is on hold. 147 * The default implementation calls {@link #onChanged}. 148 * 149 * @param call the call object that carries out the audio call 150 */ onCallHeld(SipAudioCall call)151 public void onCallHeld(SipAudioCall call) { 152 onChanged(call); 153 } 154 155 /** 156 * Called when an error occurs. The default implementation is no op. 157 * 158 * @param call the call object that carries out the audio call 159 * @param errorCode error code of this error 160 * @param errorMessage error message 161 * @see SipErrorCode 162 */ onError(SipAudioCall call, int errorCode, String errorMessage)163 public void onError(SipAudioCall call, int errorCode, 164 String errorMessage) { 165 // no-op 166 } 167 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 */ onChanged(SipAudioCall call)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; 182 private SipSession mTransferringSession; 183 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 194 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 */ SipAudioCall(Context context, SipProfile localProfile)206 public SipAudioCall(Context context, SipProfile localProfile) { 207 mContext = context; 208 mLocalProfile = localProfile; 209 mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 210 } 211 212 /** 213 * Sets the listener to listen to the audio call events. The method calls 214 * {@link #setListener setListener(listener, false)}. 215 * 216 * @param listener to listen to the audio call events of this object 217 * @see #setListener(Listener, boolean) 218 */ setListener(SipAudioCall.Listener listener)219 public void setListener(SipAudioCall.Listener listener) { 220 setListener(listener, false); 221 } 222 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 */ setListener(SipAudioCall.Listener listener, boolean callbackImmediately)232 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 */ isInCall()273 public boolean isInCall() { 274 synchronized (this) { 275 return mInCall; 276 } 277 } 278 279 /** 280 * Checks if the call is on hold. 281 * 282 * @return true if the call is on hold 283 */ isOnHold()284 public boolean isOnHold() { 285 synchronized (this) { 286 return mHold; 287 } 288 } 289 290 /** 291 * Closes this object. This object is not usable after being closed. 292 */ close()293 public void close() { 294 close(true); 295 } 296 close(boolean closeRtp)297 private synchronized void close(boolean closeRtp) { 298 if (closeRtp) stopCall(RELEASE_SOCKET); 299 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 */ getLocalProfile()317 public SipProfile getLocalProfile() { 318 synchronized (this) { 319 return mLocalProfile; 320 } 321 } 322 323 /** 324 * Gets the peer's SIP profile. 325 * 326 * @return the peer's SIP profile 327 */ getPeerProfile()328 public SipProfile getPeerProfile() { 329 synchronized (this) { 330 return (mSipSession == null) ? null : mSipSession.getPeerProfile(); 331 } 332 } 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 */ getState()340 public int getState() { 341 synchronized (this) { 342 if (mSipSession == null) return SipSession.State.READY_TO_CALL; 343 return mSipSession.getState(); 344 } 345 } 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 */ getSipSession()354 public SipSession getSipSession() { 355 synchronized (this) { 356 return mSipSession; 357 } 358 } 359 transferToNewSession()360 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 createListener()381 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); 399 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 410 public void onRinging(SipSession session, 411 SipProfile peerProfile, String sessionDescription) { 412 // this callback is triggered only for reinvite. 413 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 } 421 422 // 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 } 430 } 431 } 432 433 @Override 434 public void onCallEstablished(SipSession session, 435 String sessionDescription) { 436 mPeerSd = sessionDescription; 437 Log.v(TAG, "onCallEstablished()" + mPeerSd); 438 439 // 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 446 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) { 462 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 473 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 } 541 542 @Override 543 public void onCallTransferring(SipSession newSession, 544 String sessionDescription) { 545 Log.v(TAG, "onCallTransferring mSipSession:" 546 + mSipSession + " newSession:" + newSession); 547 mTransferringSession = newSession; 548 try { 549 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 } 556 } catch (Throwable e) { 557 Log.e(TAG, "onCallTransferring()", e); 558 newSession.endCall(); 559 } 560 } 561 }; 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 591 * the session or VOIP API is not supported by the device 592 * @see SipManager#isVoipSupported 593 */ 594 public void attachCall(SipSession session, String sessionDescription) 595 throws SipException { 596 if (!SipManager.isVoipSupported(mContext)) { 597 throw new SipException("VOIP API is not supported"); 598 } 599 600 synchronized (this) { 601 mSipSession = session; 602 mPeerSd = sessionDescription; 603 Log.v(TAG, "attachCall()" + mPeerSd); 604 try { 605 session.setListener(createListener()); 606 } catch (Throwable e) { 607 Log.e(TAG, "attachCall()", e); 608 throwSipException(e); 609 } 610 } 611 } 612 613 /** 614 * 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 616 * and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 617 * will be called. 618 * 619 * @param peerProfile the SIP profile to make the call to 620 * @param sipSession the {@link SipSession} for carrying out the call 621 * @param timeout the timeout value in seconds. Default value (defined by 622 * SIP protocol) is used if {@code timeout} is zero or negative. 623 * @see Listener#onError 624 * @throws SipException if the SIP service fails to create a session for the 625 * call or VOIP API is not supported by the device 626 * @see SipManager#isVoipSupported 627 */ 628 public void makeCall(SipProfile peerProfile, SipSession sipSession, 629 int timeout) throws SipException { 630 if (!SipManager.isVoipSupported(mContext)) { 631 throw new SipException("VOIP API is not supported"); 632 } 633 634 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 } 645 } 646 } 647 648 /** 649 * Ends a call. 650 * @throws SipException if the SIP service fails to end the call 651 */ 652 public void endCall() throws SipException { 653 synchronized (this) { 654 stopCall(RELEASE_SOCKET); 655 mInCall = false; 656 657 // perform the above local ops first and then network op 658 if (mSipSession != null) mSipSession.endCall(); 659 } 660 } 661 662 /** 663 * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is 664 * called. The attempt will be timed out if the call is not established 665 * within {@code timeout} seconds and 666 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 667 * will be called. 668 * 669 * @param timeout the timeout value in seconds. Default value (defined by 670 * SIP protocol) is used if {@code timeout} is zero or negative. 671 * @see Listener#onError 672 * @throws SipException if the SIP service fails to hold the call 673 */ 674 public void holdCall(int timeout) throws SipException { 675 synchronized (this) { 676 if (mHold) return; 677 if (mSipSession == null) { 678 throw new SipException("Not in a call to hold call"); 679 } 680 mSipSession.changeCall(createHoldOffer().encode(), timeout); 681 mHold = true; 682 setAudioGroupMode(); 683 } 684 } 685 686 /** 687 * Answers a call. The attempt will be timed out if the call is not 688 * established within {@code timeout} seconds and 689 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 690 * will be called. 691 * 692 * @param timeout the timeout value in seconds. Default value (defined by 693 * SIP protocol) is used if {@code timeout} is zero or negative. 694 * @see Listener#onError 695 * @throws SipException if the SIP service fails to answer the call 696 */ 697 public void answerCall(int timeout) throws SipException { 698 synchronized (this) { 699 if (mSipSession == null) { 700 throw new SipException("No call to answer"); 701 } 702 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 } 709 } 710 } 711 712 /** 713 * Continues a call that's on hold. When succeeds, 714 * {@link Listener#onCallEstablished} is called. The attempt will be timed 715 * out if the call is not established within {@code timeout} seconds and 716 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 717 * will be called. 718 * 719 * @param timeout the timeout value in seconds. Default value (defined by 720 * SIP protocol) is used if {@code timeout} is zero or negative. 721 * @see Listener#onError 722 * @throws SipException if the SIP service fails to unhold the call 723 */ 724 public void continueCall(int timeout) throws SipException { 725 synchronized (this) { 726 if (!mHold) return; 727 mSipSession.changeCall(createContinueOffer().encode(), timeout); 728 mHold = false; 729 setAudioGroupMode(); 730 } 731 } 732 733 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) { 747 if (TextUtils.isEmpty(offerSd)) return createOffer(); 748 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 } 847 848 /** Toggles mute. */ 849 public void toggleMute() { 850 synchronized (this) { 851 mMuted = !mMuted; 852 setAudioGroupMode(); 853 } 854 } 855 856 /** 857 * Checks if the call is muted. 858 * 859 * @return true if the call is muted 860 */ 861 public boolean isMuted() { 862 synchronized (this) { 863 return mMuted; 864 } 865 } 866 867 /** 868 * Puts the device to speaker mode. 869 * <p class="note"><strong>Note:</strong> Requires the 870 * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p> 871 * 872 * @param speakerMode set true to enable speaker mode; false to disable 873 */ 874 public void setSpeakerMode(boolean speakerMode) { 875 synchronized (this) { 876 ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) 877 .setSpeakerphoneOn(speakerMode); 878 setAudioGroupMode(); 879 } 880 } 881 882 private boolean isSpeakerOn() { 883 return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) 884 .isSpeakerphoneOn(); 885 } 886 887 /** 888 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>, 889 * event 0--9 maps to decimal 890 * 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. 892 * 893 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid 894 * inputs. 895 */ 896 public void sendDtmf(int code) { 897 sendDtmf(code, null); 898 } 899 900 /** 901 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>, 902 * event 0--9 maps to decimal 903 * 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. 905 * 906 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid 907 * inputs. 908 * @param result the result message to send when done 909 */ 910 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(); 919 } 920 } 921 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 931 * @hide 932 */ 933 public AudioStream getAudioStream() { 934 synchronized (this) { 935 return mAudioStream; 936 } 937 } 938 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 945 * {@link #close} method is called. If a group object is set by 946 * {@link #setAudioGroup(AudioGroup)}, then this method returns that object. 947 * 948 * @return the {@link AudioGroup} object or null if the RTP stream has not 949 * yet been set up 950 * @see #getAudioStream 951 * @hide 952 */ 953 public AudioGroup getAudioGroup() { 954 synchronized (this) { 955 if (mAudioGroup != null) return mAudioGroup; 956 return ((mAudioStream == null) ? null : mAudioStream.getGroup()); 957 } 958 } 959 960 /** 961 * Sets the {@link AudioGroup} object which the {@link AudioStream} object 962 * joins. If {@code audioGroup} is null, then the {@code AudioGroup} object 963 * 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'. 968 * 969 * @see #getAudioStream 970 * @hide 971 */ 972 public void setAudioGroup(AudioGroup group) { 973 synchronized (this) { 974 if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) { 975 mAudioStream.join(group); 976 } 977 mAudioGroup = group; 978 } 979 } 980 981 /** 982 * Starts the audio for the established call. This method should be called 983 * after {@link Listener#onCallEstablished} is called. 984 * <p class="note"><strong>Note:</strong> Requires the 985 * {@link android.Manifest.permission#RECORD_AUDIO}, 986 * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and 987 * {@link android.Manifest.permission#WAKE_LOCK} permissions.</p> 988 */ 989 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 } 998 999 private synchronized void startAudioInternal() throws UnknownHostException { 1000 if (mPeerSd == null) { 1001 Log.v(TAG, "startAudioInternal() mPeerSd = null"); 1002 throw new IllegalStateException("mPeerSd = null"); 1003 } 1004 1005 stopCall(DONT_RELEASE_SOCKET); 1006 mInCall = true; 1007 1008 // 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 } 1025 1026 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 1070 // AudioGroup logic: 1071 AudioGroup audioGroup = getAudioGroup(); 1072 if (mHold) { 1073 // 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); 1078 } 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) { 1089 audioGroup.setMode(AudioGroup.MODE_MUTED); 1090 } else if (isSpeakerOn()) { 1091 audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION); 1092 } 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 1115 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 } 1126 } 1127