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.<br/><br/>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 public class SipAudioCall { 56 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 60 private static final int TRANSFER_TIMEOUT = 15; // in seconds 61 62 /** 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 */ 66 public static class Listener { 67 /** 68 * Called when the call object is ready to make another call. 69 * The default implementation calls {@link #onChanged}. 70 * 71 * @param call the call object that is ready to make another call 72 */ onReadyToCall(SipAudioCall call)73 public void onReadyToCall(SipAudioCall call) { 74 onChanged(call); 75 } 76 77 /** 78 * Called when a request is sent out to initiate a new call. 79 * The default implementation calls {@link #onChanged}. 80 * 81 * @param call the call object that carries out the audio call 82 */ onCalling(SipAudioCall call)83 public void onCalling(SipAudioCall call) { 84 onChanged(call); 85 } 86 87 /** 88 * Called when a new call comes in. 89 * The default implementation calls {@link #onChanged}. 90 * 91 * @param call the call object that carries out the audio call 92 * @param caller the SIP profile of the caller 93 */ onRinging(SipAudioCall call, SipProfile caller)94 public void onRinging(SipAudioCall call, SipProfile caller) { 95 onChanged(call); 96 } 97 98 /** 99 * Called when a RINGING response is received for the INVITE request 100 * sent. The default implementation calls {@link #onChanged}. 101 * 102 * @param call the call object that carries out the audio call 103 */ onRingingBack(SipAudioCall call)104 public void onRingingBack(SipAudioCall call) { 105 onChanged(call); 106 } 107 108 /** 109 * Called when the session is established. 110 * The default implementation calls {@link #onChanged}. 111 * 112 * @param call the call object that carries out the audio call 113 */ onCallEstablished(SipAudioCall call)114 public void onCallEstablished(SipAudioCall call) { 115 onChanged(call); 116 } 117 118 /** 119 * Called when the session is terminated. 120 * The default implementation calls {@link #onChanged}. 121 * 122 * @param call the call object that carries out the audio call 123 */ onCallEnded(SipAudioCall call)124 public void onCallEnded(SipAudioCall call) { 125 onChanged(call); 126 } 127 128 /** 129 * Called when the peer is busy during session initialization. 130 * The default implementation calls {@link #onChanged}. 131 * 132 * @param call the call object that carries out the audio call 133 */ onCallBusy(SipAudioCall call)134 public void onCallBusy(SipAudioCall call) { 135 onChanged(call); 136 } 137 138 /** 139 * Called when the call is on hold. 140 * The default implementation calls {@link #onChanged}. 141 * 142 * @param call the call object that carries out the audio call 143 */ onCallHeld(SipAudioCall call)144 public void onCallHeld(SipAudioCall call) { 145 onChanged(call); 146 } 147 148 /** 149 * Called when an error occurs. The default implementation is no op. 150 * 151 * @param call the call object that carries out the audio call 152 * @param errorCode error code of this error 153 * @param errorMessage error message 154 * @see SipErrorCode 155 */ onError(SipAudioCall call, int errorCode, String errorMessage)156 public void onError(SipAudioCall call, int errorCode, 157 String errorMessage) { 158 // no-op 159 } 160 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 */ onChanged(SipAudioCall call)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; 175 private SipSession mTransferringSession; 176 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 187 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 */ SipAudioCall(Context context, SipProfile localProfile)199 public SipAudioCall(Context context, SipProfile localProfile) { 200 mContext = context; 201 mLocalProfile = localProfile; 202 mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 203 } 204 205 /** 206 * Sets the listener to listen to the audio call events. The method calls 207 * {@link #setListener setListener(listener, false)}. 208 * 209 * @param listener to listen to the audio call events of this object 210 * @see #setListener(Listener, boolean) 211 */ setListener(SipAudioCall.Listener listener)212 public void setListener(SipAudioCall.Listener listener) { 213 setListener(listener, false); 214 } 215 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 */ setListener(SipAudioCall.Listener listener, boolean callbackImmediately)225 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 */ isInCall()266 public boolean isInCall() { 267 synchronized (this) { 268 return mInCall; 269 } 270 } 271 272 /** 273 * Checks if the call is on hold. 274 * 275 * @return true if the call is on hold 276 */ isOnHold()277 public boolean isOnHold() { 278 synchronized (this) { 279 return mHold; 280 } 281 } 282 283 /** 284 * Closes this object. This object is not usable after being closed. 285 */ close()286 public void close() { 287 close(true); 288 } 289 close(boolean closeRtp)290 private synchronized void close(boolean closeRtp) { 291 if (closeRtp) stopCall(RELEASE_SOCKET); 292 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 */ getLocalProfile()310 public SipProfile getLocalProfile() { 311 synchronized (this) { 312 return mLocalProfile; 313 } 314 } 315 316 /** 317 * Gets the peer's SIP profile. 318 * 319 * @return the peer's SIP profile 320 */ getPeerProfile()321 public SipProfile getPeerProfile() { 322 synchronized (this) { 323 return (mSipSession == null) ? null : mSipSession.getPeerProfile(); 324 } 325 } 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 */ getState()333 public int getState() { 334 synchronized (this) { 335 if (mSipSession == null) return SipSession.State.READY_TO_CALL; 336 return mSipSession.getState(); 337 } 338 } 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 */ getSipSession()347 public SipSession getSipSession() { 348 synchronized (this) { 349 return mSipSession; 350 } 351 } 352 transferToNewSession()353 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 createListener()374 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); 392 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 403 public void onRinging(SipSession session, 404 SipProfile peerProfile, String sessionDescription) { 405 // this callback is triggered only for reinvite. 406 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 } 414 415 // 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 } 423 } 424 } 425 426 @Override 427 public void onCallEstablished(SipSession session, 428 String sessionDescription) { 429 mPeerSd = sessionDescription; 430 Log.v(TAG, "onCallEstablished()" + mPeerSd); 431 432 // 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 439 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) { 455 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 466 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 } 534 535 @Override 536 public void onCallTransferring(SipSession newSession, 537 String sessionDescription) { 538 Log.v(TAG, "onCallTransferring mSipSession:" 539 + mSipSession + " newSession:" + newSession); 540 mTransferringSession = newSession; 541 try { 542 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 } 549 } catch (Throwable e) { 550 Log.e(TAG, "onCallTransferring()", e); 551 newSession.endCall(); 552 } 553 } 554 }; 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 584 * the session or VOIP API is not supported by the device 585 * @see SipManager#isVoipSupported 586 */ 587 public void attachCall(SipSession session, String sessionDescription) 588 throws SipException { 589 if (!SipManager.isVoipSupported(mContext)) { 590 throw new SipException("VOIP API is not supported"); 591 } 592 593 synchronized (this) { 594 mSipSession = session; 595 mPeerSd = sessionDescription; 596 Log.v(TAG, "attachCall()" + mPeerSd); 597 try { 598 session.setListener(createListener()); 599 } catch (Throwable e) { 600 Log.e(TAG, "attachCall()", e); 601 throwSipException(e); 602 } 603 } 604 } 605 606 /** 607 * 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 609 * and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 610 * will be called. 611 * 612 * @param peerProfile the SIP profile to make the call to 613 * @param sipSession the {@link SipSession} for carrying out the call 614 * @param timeout the timeout value in seconds. Default value (defined by 615 * SIP protocol) is used if {@code timeout} is zero or negative. 616 * @see Listener#onError 617 * @throws SipException if the SIP service fails to create a session for the 618 * call or VOIP API is not supported by the device 619 * @see SipManager#isVoipSupported 620 */ 621 public void makeCall(SipProfile peerProfile, SipSession sipSession, 622 int timeout) throws SipException { 623 if (!SipManager.isVoipSupported(mContext)) { 624 throw new SipException("VOIP API is not supported"); 625 } 626 627 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 } 638 } 639 } 640 641 /** 642 * Ends a call. 643 * @throws SipException if the SIP service fails to end the call 644 */ 645 public void endCall() throws SipException { 646 synchronized (this) { 647 stopCall(RELEASE_SOCKET); 648 mInCall = false; 649 650 // perform the above local ops first and then network op 651 if (mSipSession != null) mSipSession.endCall(); 652 } 653 } 654 655 /** 656 * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is 657 * called. The attempt will be timed out if the call is not established 658 * within {@code timeout} seconds and 659 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 660 * will be called. 661 * 662 * @param timeout the timeout value in seconds. Default value (defined by 663 * SIP protocol) is used if {@code timeout} is zero or negative. 664 * @see Listener#onError 665 * @throws SipException if the SIP service fails to hold the call 666 */ 667 public void holdCall(int timeout) throws SipException { 668 synchronized (this) { 669 if (mHold) return; 670 if (mSipSession == null) { 671 throw new SipException("Not in a call to hold call"); 672 } 673 mSipSession.changeCall(createHoldOffer().encode(), timeout); 674 mHold = true; 675 setAudioGroupMode(); 676 } 677 } 678 679 /** 680 * Answers a call. The attempt will be timed out if the call is not 681 * established within {@code timeout} seconds and 682 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 683 * will be called. 684 * 685 * @param timeout the timeout value in seconds. Default value (defined by 686 * SIP protocol) is used if {@code timeout} is zero or negative. 687 * @see Listener#onError 688 * @throws SipException if the SIP service fails to answer the call 689 */ 690 public void answerCall(int timeout) throws SipException { 691 synchronized (this) { 692 if (mSipSession == null) { 693 throw new SipException("No call to answer"); 694 } 695 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 } 702 } 703 } 704 705 /** 706 * Continues a call that's on hold. When succeeds, 707 * {@link Listener#onCallEstablished} is called. The attempt will be timed 708 * out if the call is not established within {@code timeout} seconds and 709 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 710 * will be called. 711 * 712 * @param timeout the timeout value in seconds. Default value (defined by 713 * SIP protocol) is used if {@code timeout} is zero or negative. 714 * @see Listener#onError 715 * @throws SipException if the SIP service fails to unhold the call 716 */ 717 public void continueCall(int timeout) throws SipException { 718 synchronized (this) { 719 if (!mHold) return; 720 mSipSession.changeCall(createContinueOffer().encode(), timeout); 721 mHold = false; 722 setAudioGroupMode(); 723 } 724 } 725 726 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) { 740 if (TextUtils.isEmpty(offerSd)) return createOffer(); 741 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 } 840 841 /** Toggles mute. */ 842 public void toggleMute() { 843 synchronized (this) { 844 mMuted = !mMuted; 845 setAudioGroupMode(); 846 } 847 } 848 849 /** 850 * Checks if the call is muted. 851 * 852 * @return true if the call is muted 853 */ 854 public boolean isMuted() { 855 synchronized (this) { 856 return mMuted; 857 } 858 } 859 860 /** 861 * Puts the device to speaker mode. 862 * <p class="note"><strong>Note:</strong> Requires the 863 * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p> 864 * 865 * @param speakerMode set true to enable speaker mode; false to disable 866 */ 867 public void setSpeakerMode(boolean speakerMode) { 868 synchronized (this) { 869 ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) 870 .setSpeakerphoneOn(speakerMode); 871 setAudioGroupMode(); 872 } 873 } 874 875 private boolean isSpeakerOn() { 876 return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) 877 .isSpeakerphoneOn(); 878 } 879 880 /** 881 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>, 882 * event 0--9 maps to decimal 883 * 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. 885 * 886 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid 887 * inputs. 888 */ 889 public void sendDtmf(int code) { 890 sendDtmf(code, null); 891 } 892 893 /** 894 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>, 895 * event 0--9 maps to decimal 896 * 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. 898 * 899 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid 900 * inputs. 901 * @param result the result message to send when done 902 */ 903 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(); 912 } 913 } 914 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 924 * @hide 925 */ 926 public AudioStream getAudioStream() { 927 synchronized (this) { 928 return mAudioStream; 929 } 930 } 931 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 938 * {@link #close} method is called. If a group object is set by 939 * {@link #setAudioGroup(AudioGroup)}, then this method returns that object. 940 * 941 * @return the {@link AudioGroup} object or null if the RTP stream has not 942 * yet been set up 943 * @see #getAudioStream 944 * @hide 945 */ 946 public AudioGroup getAudioGroup() { 947 synchronized (this) { 948 if (mAudioGroup != null) return mAudioGroup; 949 return ((mAudioStream == null) ? null : mAudioStream.getGroup()); 950 } 951 } 952 953 /** 954 * Sets the {@link AudioGroup} object which the {@link AudioStream} object 955 * joins. If {@code audioGroup} is null, then the {@code AudioGroup} object 956 * 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'. 961 * 962 * @see #getAudioStream 963 * @hide 964 */ 965 public void setAudioGroup(AudioGroup group) { 966 synchronized (this) { 967 if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) { 968 mAudioStream.join(group); 969 } 970 mAudioGroup = group; 971 } 972 } 973 974 /** 975 * Starts the audio for the established call. This method should be called 976 * after {@link Listener#onCallEstablished} is called. 977 * <p class="note"><strong>Note:</strong> Requires the 978 * {@link android.Manifest.permission#RECORD_AUDIO}, 979 * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and 980 * {@link android.Manifest.permission#WAKE_LOCK} permissions.</p> 981 */ 982 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 } 991 992 private synchronized void startAudioInternal() throws UnknownHostException { 993 if (mPeerSd == null) { 994 Log.v(TAG, "startAudioInternal() mPeerSd = null"); 995 throw new IllegalStateException("mPeerSd = null"); 996 } 997 998 stopCall(DONT_RELEASE_SOCKET); 999 mInCall = true; 1000 1001 // 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 } 1018 1019 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 1063 // AudioGroup logic: 1064 AudioGroup audioGroup = getAudioGroup(); 1065 if (mHold) { 1066 // 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); 1071 } 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) { 1082 audioGroup.setMode(AudioGroup.MODE_MUTED); 1083 } else if (isSpeakerOn()) { 1084 audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION); 1085 } 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 1108 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 } 1119 } 1120