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.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.content.Context; 23 import android.media.AudioManager; 24 import android.net.rtp.AudioCodec; 25 import android.net.rtp.AudioGroup; 26 import android.net.rtp.AudioStream; 27 import android.net.rtp.RtpStream; 28 import android.net.sip.SimpleSessionDescription.Media; 29 import android.net.wifi.WifiManager; 30 import android.os.Message; 31 import android.telephony.Rlog; 32 import android.text.TextUtils; 33 34 import java.io.IOException; 35 import java.net.InetAddress; 36 import java.net.UnknownHostException; 37 38 /** 39 * Handles an Internet audio call over SIP. You can instantiate this class with {@link SipManager}, 40 * using {@link SipManager#makeAudioCall makeAudioCall()} and {@link SipManager#takeAudioCall 41 * takeAudioCall()}. 42 * 43 * <p class="note"><strong>Note:</strong> Using this class require the 44 * {@link android.Manifest.permission#INTERNET} and 45 * {@link android.Manifest.permission#USE_SIP} permissions. In addition, {@link 46 * #startAudio} requires the 47 * {@link android.Manifest.permission#RECORD_AUDIO}, 48 * {@link android.Manifest.permission#ACCESS_WIFI_STATE}, and 49 * {@link android.Manifest.permission#WAKE_LOCK} permissions; and {@link #setSpeakerMode 50 * setSpeakerMode()} requires the 51 * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p> 52 * 53 * <div class="special reference"> 54 * <h3>Developer Guides</h3> 55 * <p>For more information about using SIP, read the 56 * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a> 57 * developer guide.</p> 58 * </div> 59 * @deprecated {@link android.net.sip.SipManager} and associated classes are no longer supported and 60 * should not be used as the basis of future VOIP apps. 61 */ 62 public class SipAudioCall { 63 private static final String LOG_TAG = SipAudioCall.class.getSimpleName(); 64 private static final boolean DBG = false; 65 private static final boolean RELEASE_SOCKET = true; 66 private static final boolean DONT_RELEASE_SOCKET = false; 67 private static final int SESSION_TIMEOUT = 5; // in seconds 68 private static final int TRANSFER_TIMEOUT = 15; // in seconds 69 70 /** Listener for events relating to a SIP call, such as when a call is being 71 * received ("on ringing") or a call is outgoing ("on calling"). 72 * <p>Many of these events are also received by {@link SipSession.Listener}.</p> 73 */ 74 public static class Listener { 75 /** 76 * Called when the call object is ready to make another call. 77 * The default implementation calls {@link #onChanged}. 78 * 79 * @param call the call object that is ready to make another call 80 */ onReadyToCall(SipAudioCall call)81 public void onReadyToCall(SipAudioCall call) { 82 onChanged(call); 83 } 84 85 /** 86 * Called when a request is sent out to initiate a new call. 87 * The default implementation calls {@link #onChanged}. 88 * 89 * @param call the call object that carries out the audio call 90 */ onCalling(SipAudioCall call)91 public void onCalling(SipAudioCall call) { 92 onChanged(call); 93 } 94 95 /** 96 * Called when a new call comes in. 97 * The default implementation calls {@link #onChanged}. 98 * 99 * @param call the call object that carries out the audio call 100 * @param caller the SIP profile of the caller 101 */ onRinging(SipAudioCall call, SipProfile caller)102 public void onRinging(SipAudioCall call, SipProfile caller) { 103 onChanged(call); 104 } 105 106 /** 107 * Called when a RINGING response is received for the INVITE request 108 * sent. The default implementation calls {@link #onChanged}. 109 * 110 * @param call the call object that carries out the audio call 111 */ onRingingBack(SipAudioCall call)112 public void onRingingBack(SipAudioCall call) { 113 onChanged(call); 114 } 115 116 /** 117 * Called when the session is established. 118 * The default implementation calls {@link #onChanged}. 119 * 120 * @param call the call object that carries out the audio call 121 */ onCallEstablished(SipAudioCall call)122 public void onCallEstablished(SipAudioCall call) { 123 onChanged(call); 124 } 125 126 /** 127 * Called when the session is terminated. 128 * The default implementation calls {@link #onChanged}. 129 * 130 * @param call the call object that carries out the audio call 131 */ onCallEnded(SipAudioCall call)132 public void onCallEnded(SipAudioCall call) { 133 onChanged(call); 134 } 135 136 /** 137 * Called when the peer is busy during session initialization. 138 * The default implementation calls {@link #onChanged}. 139 * 140 * @param call the call object that carries out the audio call 141 */ onCallBusy(SipAudioCall call)142 public void onCallBusy(SipAudioCall call) { 143 onChanged(call); 144 } 145 146 /** 147 * Called when the call is on hold. 148 * The default implementation calls {@link #onChanged}. 149 * 150 * @param call the call object that carries out the audio call 151 */ onCallHeld(SipAudioCall call)152 public void onCallHeld(SipAudioCall call) { 153 onChanged(call); 154 } 155 156 /** 157 * Called when an error occurs. The default implementation is no op. 158 * 159 * @param call the call object that carries out the audio call 160 * @param errorCode error code of this error 161 * @param errorMessage error message 162 * @see SipErrorCode 163 */ onError(SipAudioCall call, int errorCode, String errorMessage)164 public void onError(SipAudioCall call, int errorCode, 165 String errorMessage) { 166 // no-op 167 } 168 169 /** 170 * Called when an event occurs and the corresponding callback is not 171 * overridden. The default implementation is no op. Error events are 172 * not re-directed to this callback and are handled in {@link #onError}. 173 */ onChanged(SipAudioCall call)174 public void onChanged(SipAudioCall call) { 175 // no-op 176 } 177 } 178 179 private Context mContext; 180 private SipProfile mLocalProfile; 181 private SipAudioCall.Listener mListener; 182 private SipSession mSipSession; 183 private SipSession mTransferringSession; 184 185 private long mSessionId = System.currentTimeMillis(); 186 private String mPeerSd; 187 188 private AudioStream mAudioStream; 189 private AudioGroup mAudioGroup; 190 191 private boolean mInCall = false; 192 private boolean mMuted = false; 193 private boolean mHold = false; 194 195 private WifiManager mWm; 196 private WifiManager.WifiLock mWifiHighPerfLock; 197 198 private int mErrorCode = SipErrorCode.NO_ERROR; 199 private String mErrorMessage; 200 private final Object mLock; 201 202 /** 203 * Creates a call object with the local SIP profile. 204 * @param context the context for accessing system services such as 205 * ringtone, audio, WIFI etc 206 */ SipAudioCall(Context context, SipProfile localProfile)207 public SipAudioCall(Context context, SipProfile localProfile) { 208 mContext = context; 209 mLocalProfile = localProfile; 210 mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 211 mLock = new Object(); 212 } 213 214 /** 215 * Sets the listener to listen to the audio call events. The method calls 216 * {@link #setListener setListener(listener, false)}. 217 * 218 * @param listener to listen to the audio call events of this object 219 * @see #setListener(Listener, boolean) 220 */ setListener(SipAudioCall.Listener listener)221 public void setListener(SipAudioCall.Listener listener) { 222 setListener(listener, false); 223 } 224 225 /** 226 * Sets the listener to listen to the audio call events. A 227 * {@link SipAudioCall} can only hold one listener at a time. Subsequent 228 * calls to this method override the previous listener. 229 * 230 * @param listener to listen to the audio call events of this object 231 * @param callbackImmediately set to true if the caller wants to be called 232 * back immediately on the current state 233 */ setListener(SipAudioCall.Listener listener, boolean callbackImmediately)234 public void setListener(SipAudioCall.Listener listener, 235 boolean callbackImmediately) { 236 mListener = listener; 237 try { 238 if ((listener == null) || !callbackImmediately) { 239 // do nothing 240 } else if (mErrorCode != SipErrorCode.NO_ERROR) { 241 listener.onError(this, mErrorCode, mErrorMessage); 242 } else if (mInCall) { 243 if (mHold) { 244 listener.onCallHeld(this); 245 } else { 246 listener.onCallEstablished(this); 247 } 248 } else { 249 int state = getState(); 250 switch (state) { 251 case SipSession.State.READY_TO_CALL: 252 listener.onReadyToCall(this); 253 break; 254 case SipSession.State.INCOMING_CALL: 255 listener.onRinging(this, getPeerProfile()); 256 break; 257 case SipSession.State.OUTGOING_CALL: 258 listener.onCalling(this); 259 break; 260 case SipSession.State.OUTGOING_CALL_RING_BACK: 261 listener.onRingingBack(this); 262 break; 263 } 264 } 265 } catch (Throwable t) { 266 loge("setListener()", t); 267 } 268 } 269 270 /** 271 * Checks if the call is established. 272 * 273 * @return true if the call is established 274 */ isInCall()275 public boolean isInCall() { 276 synchronized (mLock) { 277 return mInCall; 278 } 279 } 280 281 /** 282 * Checks if the call is on hold. 283 * 284 * @return true if the call is on hold 285 */ isOnHold()286 public boolean isOnHold() { 287 synchronized (mLock) { 288 return mHold; 289 } 290 } 291 292 /** 293 * Closes this object. This object is not usable after being closed. 294 */ close()295 public void close() { 296 close(true); 297 } 298 close(boolean closeRtp)299 private synchronized void close(boolean closeRtp) { 300 if (closeRtp) stopCall(RELEASE_SOCKET); 301 302 mInCall = false; 303 mHold = false; 304 mSessionId = System.currentTimeMillis(); 305 mErrorCode = SipErrorCode.NO_ERROR; 306 mErrorMessage = null; 307 308 if (mSipSession != null) { 309 mSipSession.setListener(null); 310 mSipSession = null; 311 } 312 } 313 314 /** 315 * Gets the local SIP profile. 316 * 317 * @return the local SIP profile 318 */ getLocalProfile()319 public SipProfile getLocalProfile() { 320 synchronized (mLock) { 321 return mLocalProfile; 322 } 323 } 324 325 /** 326 * Gets the peer's SIP profile. 327 * 328 * @return the peer's SIP profile 329 */ getPeerProfile()330 public SipProfile getPeerProfile() { 331 synchronized (mLock) { 332 return (mSipSession == null) ? null : mSipSession.getPeerProfile(); 333 } 334 } 335 336 /** 337 * Gets the state of the {@link SipSession} that carries this call. 338 * The value returned must be one of the states in {@link SipSession.State}. 339 * 340 * @return the session state 341 */ getState()342 public int getState() { 343 synchronized (mLock) { 344 if (mSipSession == null) return SipSession.State.READY_TO_CALL; 345 return mSipSession.getState(); 346 } 347 } 348 349 350 /** 351 * Gets the {@link SipSession} that carries this call. 352 * 353 * @return the session object that carries this call 354 * @hide 355 */ getSipSession()356 public SipSession getSipSession() { 357 synchronized (mLock) { 358 return mSipSession; 359 } 360 } 361 transferToNewSession()362 private synchronized void transferToNewSession() { 363 if (mTransferringSession == null) return; 364 SipSession origin = mSipSession; 365 mSipSession = mTransferringSession; 366 mTransferringSession = null; 367 368 // stop the replaced call. 369 if (mAudioStream != null) { 370 mAudioStream.join(null); 371 } else { 372 try { 373 mAudioStream = new AudioStream(InetAddress.getByName( 374 getLocalIp())); 375 } catch (Throwable t) { 376 loge("transferToNewSession():", t); 377 } 378 } 379 if (origin != null) origin.endCall(); 380 startAudio(); 381 } 382 createListener()383 private SipSession.Listener createListener() { 384 return new SipSession.Listener() { 385 @Override 386 public void onCalling(SipSession session) { 387 if (DBG) log("onCalling: session=" + session); 388 Listener listener = mListener; 389 if (listener != null) { 390 try { 391 listener.onCalling(SipAudioCall.this); 392 } catch (Throwable t) { 393 loge("onCalling():", t); 394 } 395 } 396 } 397 398 @Override 399 public void onRingingBack(SipSession session) { 400 if (DBG) log("onRingingBackk: " + session); 401 Listener listener = mListener; 402 if (listener != null) { 403 try { 404 listener.onRingingBack(SipAudioCall.this); 405 } catch (Throwable t) { 406 loge("onRingingBack():", t); 407 } 408 } 409 } 410 411 @Override 412 public void onRinging(SipSession session, 413 SipProfile peerProfile, String sessionDescription) { 414 // this callback is triggered only for reinvite. 415 synchronized (mLock) { 416 if ((mSipSession == null) || !mInCall 417 || !session.getCallId().equals( 418 mSipSession.getCallId())) { 419 // should not happen 420 session.endCall(); 421 return; 422 } 423 424 // session changing request 425 try { 426 String answer = createAnswer(sessionDescription).encode(); 427 mSipSession.answerCall(answer, SESSION_TIMEOUT); 428 } catch (Throwable e) { 429 loge("onRinging():", e); 430 session.endCall(); 431 } 432 } 433 } 434 435 @Override 436 public void onCallEstablished(SipSession session, 437 String sessionDescription) { 438 mPeerSd = sessionDescription; 439 if (DBG) log("onCallEstablished(): " + mPeerSd); 440 441 // TODO: how to notify the UI that the remote party is changed 442 if ((mTransferringSession != null) 443 && (session == mTransferringSession)) { 444 transferToNewSession(); 445 return; 446 } 447 448 Listener listener = mListener; 449 if (listener != null) { 450 try { 451 if (mHold) { 452 listener.onCallHeld(SipAudioCall.this); 453 } else { 454 listener.onCallEstablished(SipAudioCall.this); 455 } 456 } catch (Throwable t) { 457 loge("onCallEstablished(): ", t); 458 } 459 } 460 } 461 462 @Override 463 public void onCallEnded(SipSession session) { 464 if (DBG) log("onCallEnded: " + session + " mSipSession:" + mSipSession); 465 // reset the trasnferring session if it is the one. 466 if (session == mTransferringSession) { 467 mTransferringSession = null; 468 return; 469 } 470 // or ignore the event if the original session is being 471 // transferred to the new one. 472 if ((mTransferringSession != null) || 473 (session != mSipSession)) return; 474 475 Listener listener = mListener; 476 if (listener != null) { 477 try { 478 listener.onCallEnded(SipAudioCall.this); 479 } catch (Throwable t) { 480 loge("onCallEnded(): ", t); 481 } 482 } 483 close(); 484 } 485 486 @Override 487 public void onCallBusy(SipSession session) { 488 if (DBG) log("onCallBusy: " + session); 489 Listener listener = mListener; 490 if (listener != null) { 491 try { 492 listener.onCallBusy(SipAudioCall.this); 493 } catch (Throwable t) { 494 loge("onCallBusy(): ", t); 495 } 496 } 497 close(false); 498 } 499 500 @Override 501 public void onCallChangeFailed(SipSession session, int errorCode, 502 String message) { 503 if (DBG) log("onCallChangedFailed: " + message); 504 mErrorCode = errorCode; 505 mErrorMessage = message; 506 Listener listener = mListener; 507 if (listener != null) { 508 try { 509 listener.onError(SipAudioCall.this, mErrorCode, 510 message); 511 } catch (Throwable t) { 512 loge("onCallBusy():", t); 513 } 514 } 515 } 516 517 @Override 518 public void onError(SipSession session, int errorCode, 519 String message) { 520 SipAudioCall.this.onError(errorCode, message); 521 } 522 523 @Override 524 public void onRegistering(SipSession session) { 525 // irrelevant 526 } 527 528 @Override 529 public void onRegistrationTimeout(SipSession session) { 530 // irrelevant 531 } 532 533 @Override 534 public void onRegistrationFailed(SipSession session, int errorCode, 535 String message) { 536 // irrelevant 537 } 538 539 @Override 540 public void onRegistrationDone(SipSession session, int duration) { 541 // irrelevant 542 } 543 544 @Override 545 public void onCallTransferring(SipSession newSession, 546 String sessionDescription) { 547 if (DBG) log("onCallTransferring: mSipSession=" 548 + mSipSession + " newSession=" + newSession); 549 mTransferringSession = newSession; 550 try { 551 if (sessionDescription == null) { 552 newSession.makeCall(newSession.getPeerProfile(), 553 createOffer().encode(), TRANSFER_TIMEOUT); 554 } else { 555 String answer = createAnswer(sessionDescription).encode(); 556 newSession.answerCall(answer, SESSION_TIMEOUT); 557 } 558 } catch (Throwable e) { 559 loge("onCallTransferring()", e); 560 newSession.endCall(); 561 } 562 } 563 }; 564 } 565 566 private void onError(int errorCode, String message) { 567 if (DBG) log("onError: " 568 + SipErrorCode.toString(errorCode) + ": " + message); 569 mErrorCode = errorCode; 570 mErrorMessage = message; 571 Listener listener = mListener; 572 if (listener != null) { 573 try { 574 listener.onError(this, errorCode, message); 575 } catch (Throwable t) { 576 loge("onError():", t); 577 } 578 } 579 synchronized (mLock) { 580 if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST) 581 || !isInCall()) { 582 close(true); 583 } 584 } 585 } 586 587 /** 588 * Attaches an incoming call to this call object. 589 * 590 * @param session the session that receives the incoming call 591 * @param sessionDescription the session description of the incoming call 592 * @throws SipException if the SIP service fails to attach this object to 593 * the session or VOIP API is not supported by the device 594 * @see SipManager#isVoipSupported 595 */ 596 public void attachCall(SipSession session, String sessionDescription) 597 throws SipException { 598 if (!SipManager.isVoipSupported(mContext)) { 599 throw new SipException("VOIP API is not supported"); 600 } 601 602 synchronized (mLock) { 603 mSipSession = session; 604 mPeerSd = sessionDescription; 605 if (DBG) log("attachCall(): " + mPeerSd); 606 try { 607 session.setListener(createListener()); 608 } catch (Throwable e) { 609 loge("attachCall()", e); 610 throwSipException(e); 611 } 612 } 613 } 614 615 /** 616 * Initiates an audio call to the specified profile. The attempt will be 617 * timed out if the call is not established within {@code timeout} seconds 618 * and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 619 * will be called. 620 * 621 * @param peerProfile the SIP profile to make the call to 622 * @param sipSession the {@link SipSession} for carrying out the call 623 * @param timeout the timeout value in seconds. Default value (defined by 624 * SIP protocol) is used if {@code timeout} is zero or negative. 625 * @see Listener#onError 626 * @throws SipException if the SIP service fails to create a session for the 627 * call or VOIP API is not supported by the device 628 * @see SipManager#isVoipSupported 629 */ 630 public void makeCall(SipProfile peerProfile, SipSession sipSession, 631 int timeout) throws SipException { 632 if (DBG) log("makeCall: " + peerProfile + " session=" + sipSession + " timeout=" + timeout); 633 if (!SipManager.isVoipSupported(mContext)) { 634 throw new SipException("VOIP API is not supported"); 635 } 636 637 synchronized (mLock) { 638 mSipSession = sipSession; 639 try { 640 mAudioStream = new AudioStream(InetAddress.getByName( 641 getLocalIp())); 642 sipSession.setListener(createListener()); 643 sipSession.makeCall(peerProfile, createOffer().encode(), 644 timeout); 645 } catch (IOException e) { 646 loge("makeCall:", e); 647 throw new SipException("makeCall()", e); 648 } 649 } 650 } 651 652 /** 653 * Ends a call. 654 * @throws SipException if the SIP service fails to end the call 655 */ 656 public void endCall() throws SipException { 657 if (DBG) log("endCall: mSipSession" + mSipSession); 658 synchronized (mLock) { 659 stopCall(RELEASE_SOCKET); 660 mInCall = false; 661 662 // perform the above local ops first and then network op 663 if (mSipSession != null) mSipSession.endCall(); 664 } 665 } 666 667 /** 668 * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is 669 * called. The attempt will be timed out if the call is not established 670 * within {@code timeout} seconds and 671 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 672 * will be called. 673 * 674 * @param timeout the timeout value in seconds. Default value (defined by 675 * SIP protocol) is used if {@code timeout} is zero or negative. 676 * @see Listener#onError 677 * @throws SipException if the SIP service fails to hold the call 678 */ 679 public void holdCall(int timeout) throws SipException { 680 if (DBG) log("holdCall: mSipSession" + mSipSession + " timeout=" + timeout); 681 synchronized (mLock) { 682 if (mHold) return; 683 if (mSipSession == null) { 684 loge("holdCall:"); 685 throw new SipException("Not in a call to hold call"); 686 } 687 mSipSession.changeCall(createHoldOffer().encode(), timeout); 688 mHold = true; 689 setAudioGroupMode(); 690 } 691 } 692 693 /** 694 * Answers a call. The attempt will be timed out if the call is not 695 * established within {@code timeout} seconds and 696 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 697 * will be called. 698 * 699 * @param timeout the timeout value in seconds. Default value (defined by 700 * SIP protocol) is used if {@code timeout} is zero or negative. 701 * @see Listener#onError 702 * @throws SipException if the SIP service fails to answer the call 703 */ 704 public void answerCall(int timeout) throws SipException { 705 if (DBG) log("answerCall: mSipSession" + mSipSession + " timeout=" + timeout); 706 synchronized (mLock) { 707 if (mSipSession == null) { 708 throw new SipException("No call to answer"); 709 } 710 try { 711 mAudioStream = new AudioStream(InetAddress.getByName( 712 getLocalIp())); 713 mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout); 714 } catch (IOException e) { 715 loge("answerCall:", e); 716 throw new SipException("answerCall()", e); 717 } 718 } 719 } 720 721 /** 722 * Continues a call that's on hold. When succeeds, 723 * {@link Listener#onCallEstablished} is called. The attempt will be timed 724 * out if the call is not established within {@code timeout} seconds and 725 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 726 * will be called. 727 * 728 * @param timeout the timeout value in seconds. Default value (defined by 729 * SIP protocol) is used if {@code timeout} is zero or negative. 730 * @see Listener#onError 731 * @throws SipException if the SIP service fails to unhold the call 732 */ 733 public void continueCall(int timeout) throws SipException { 734 if (DBG) log("continueCall: mSipSession" + mSipSession + " timeout=" + timeout); 735 synchronized (mLock) { 736 if (!mHold) return; 737 mSipSession.changeCall(createContinueOffer().encode(), timeout); 738 mHold = false; 739 setAudioGroupMode(); 740 } 741 } 742 743 private SimpleSessionDescription createOffer() { 744 SimpleSessionDescription offer = 745 new SimpleSessionDescription(mSessionId, getLocalIp()); 746 AudioCodec[] codecs = AudioCodec.getCodecs(); 747 Media media = offer.newMedia( 748 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP"); 749 for (AudioCodec codec : AudioCodec.getCodecs()) { 750 media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp); 751 } 752 media.setRtpPayload(127, "telephone-event/8000", "0-15"); 753 if (DBG) log("createOffer: offer=" + offer); 754 return offer; 755 } 756 757 private SimpleSessionDescription createAnswer(String offerSd) { 758 if (TextUtils.isEmpty(offerSd)) return createOffer(); 759 SimpleSessionDescription offer = 760 new SimpleSessionDescription(offerSd); 761 SimpleSessionDescription answer = 762 new SimpleSessionDescription(mSessionId, getLocalIp()); 763 AudioCodec codec = null; 764 for (Media media : offer.getMedia()) { 765 if ((codec == null) && (media.getPort() > 0) 766 && "audio".equals(media.getType()) 767 && "RTP/AVP".equals(media.getProtocol())) { 768 // Find the first audio codec we supported. 769 for (int type : media.getRtpPayloadTypes()) { 770 codec = AudioCodec.getCodec(type, media.getRtpmap(type), 771 media.getFmtp(type)); 772 if (codec != null) { 773 break; 774 } 775 } 776 if (codec != null) { 777 Media reply = answer.newMedia( 778 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP"); 779 reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp); 780 781 // Check if DTMF is supported in the same media. 782 for (int type : media.getRtpPayloadTypes()) { 783 String rtpmap = media.getRtpmap(type); 784 if ((type != codec.type) && (rtpmap != null) 785 && rtpmap.startsWith("telephone-event")) { 786 reply.setRtpPayload( 787 type, rtpmap, media.getFmtp(type)); 788 } 789 } 790 791 // Handle recvonly and sendonly. 792 if (media.getAttribute("recvonly") != null) { 793 answer.setAttribute("sendonly", ""); 794 } else if(media.getAttribute("sendonly") != null) { 795 answer.setAttribute("recvonly", ""); 796 } else if(offer.getAttribute("recvonly") != null) { 797 answer.setAttribute("sendonly", ""); 798 } else if(offer.getAttribute("sendonly") != null) { 799 answer.setAttribute("recvonly", ""); 800 } 801 continue; 802 } 803 } 804 // Reject the media. 805 Media reply = answer.newMedia( 806 media.getType(), 0, 1, media.getProtocol()); 807 for (String format : media.getFormats()) { 808 reply.setFormat(format, null); 809 } 810 } 811 if (codec == null) { 812 loge("createAnswer: no suitable codes"); 813 throw new IllegalStateException("Reject SDP: no suitable codecs"); 814 } 815 if (DBG) log("createAnswer: answer=" + answer); 816 return answer; 817 } 818 819 private SimpleSessionDescription createHoldOffer() { 820 SimpleSessionDescription offer = createContinueOffer(); 821 offer.setAttribute("sendonly", ""); 822 if (DBG) log("createHoldOffer: offer=" + offer); 823 return offer; 824 } 825 826 private SimpleSessionDescription createContinueOffer() { 827 if (DBG) log("createContinueOffer"); 828 SimpleSessionDescription offer = 829 new SimpleSessionDescription(mSessionId, getLocalIp()); 830 Media media = offer.newMedia( 831 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP"); 832 AudioCodec codec = mAudioStream.getCodec(); 833 media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp); 834 int dtmfType = mAudioStream.getDtmfType(); 835 if (dtmfType != -1) { 836 media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15"); 837 } 838 return offer; 839 } 840 841 private void grabWifiHighPerfLock() { 842 if (mWifiHighPerfLock == null) { 843 if (DBG) log("grabWifiHighPerfLock:"); 844 mWifiHighPerfLock = ((WifiManager) 845 mContext.getSystemService(Context.WIFI_SERVICE)) 846 .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, LOG_TAG); 847 mWifiHighPerfLock.acquire(); 848 } 849 } 850 851 private void releaseWifiHighPerfLock() { 852 if (mWifiHighPerfLock != null) { 853 if (DBG) log("releaseWifiHighPerfLock:"); 854 mWifiHighPerfLock.release(); 855 mWifiHighPerfLock = null; 856 } 857 } 858 859 private boolean isWifiOn() { 860 return (mWm.getConnectionInfo().getBSSID() == null) ? false : true; 861 } 862 863 /** Toggles mute. */ 864 public void toggleMute() { 865 synchronized (mLock) { 866 mMuted = !mMuted; 867 setAudioGroupMode(); 868 } 869 } 870 871 /** 872 * Checks if the call is muted. 873 * 874 * @return true if the call is muted 875 */ 876 public boolean isMuted() { 877 synchronized (mLock) { 878 return mMuted; 879 } 880 } 881 882 /** 883 * Puts the device to speaker mode. 884 * <p class="note"><strong>Note:</strong> Requires the 885 * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p> 886 * 887 * @param speakerMode set true to enable speaker mode; false to disable 888 */ 889 public void setSpeakerMode(boolean speakerMode) { 890 synchronized (mLock) { 891 ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) 892 .setSpeakerphoneOn(speakerMode); 893 setAudioGroupMode(); 894 } 895 } 896 897 private boolean isSpeakerOn() { 898 return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) 899 .isSpeakerphoneOn(); 900 } 901 902 /** 903 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>, 904 * event 0--9 maps to decimal 905 * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event 906 * flash to 16. Currently, event flash is not supported. 907 * 908 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid 909 * inputs. 910 */ 911 public void sendDtmf(int code) { 912 sendDtmf(code, null); 913 } 914 915 /** 916 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>, 917 * event 0--9 maps to decimal 918 * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event 919 * flash to 16. Currently, event flash is not supported. 920 * 921 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid 922 * inputs. 923 * @param result the result message to send when done 924 */ 925 public void sendDtmf(int code, Message result) { 926 synchronized (mLock) { 927 AudioGroup audioGroup = getAudioGroup(); 928 if ((audioGroup != null) && (mSipSession != null) 929 && (SipSession.State.IN_CALL == getState())) { 930 if (DBG) log("sendDtmf: code=" + code + " result=" + result); 931 audioGroup.sendDtmf(code); 932 } 933 if (result != null) result.sendToTarget(); 934 } 935 } 936 937 /** 938 * Gets the {@link AudioStream} object used in this call. The object 939 * represents the RTP stream that carries the audio data to and from the 940 * peer. The object may not be created before the call is established. And 941 * it is undefined after the call ends or the {@link #close} method is 942 * called. 943 * 944 * @return the {@link AudioStream} object or null if the RTP stream has not 945 * yet been set up 946 * @hide 947 */ 948 public AudioStream getAudioStream() { 949 synchronized (mLock) { 950 return mAudioStream; 951 } 952 } 953 954 /** 955 * Gets the {@link AudioGroup} object which the {@link AudioStream} object 956 * joins. The group object may not exist before the call is established. 957 * Also, the {@code AudioStream} may change its group during a call (e.g., 958 * after the call is held/un-held). Finally, the {@code AudioGroup} object 959 * returned by this method is undefined after the call ends or the 960 * {@link #close} method is called. If a group object is set by 961 * {@link #setAudioGroup(AudioGroup)}, then this method returns that object. 962 * 963 * @return the {@link AudioGroup} object or null if the RTP stream has not 964 * yet been set up 965 * @see #getAudioStream 966 * @hide 967 */ 968 @SystemApi 969 public @Nullable AudioGroup getAudioGroup() { 970 synchronized (mLock) { 971 if (mAudioGroup != null) return mAudioGroup; 972 return ((mAudioStream == null) ? null : mAudioStream.getGroup()); 973 } 974 } 975 976 /** 977 * Sets the {@link AudioGroup} object which the {@link AudioStream} object 978 * joins. If {@code audioGroup} is null, then the {@code AudioGroup} object 979 * will be dynamically created when needed. Note that the mode of the 980 * {@code AudioGroup} is not changed according to the audio settings (i.e., 981 * hold, mute, speaker phone) of this object. This is mainly used to merge 982 * multiple {@code SipAudioCall} objects to form a conference call. The 983 * settings of the first object (that merges others) override others'. 984 * 985 * @see #getAudioStream 986 * @hide 987 */ 988 @SystemApi 989 public void setAudioGroup(@NonNull AudioGroup group) { 990 synchronized (mLock) { 991 if (DBG) log("setAudioGroup: group=" + group); 992 if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) { 993 mAudioStream.join(group); 994 } 995 mAudioGroup = group; 996 } 997 } 998 999 /** 1000 * Starts the audio for the established call. This method should be called 1001 * after {@link Listener#onCallEstablished} is called. 1002 * <p class="note"><strong>Note:</strong> Requires the 1003 * {@link android.Manifest.permission#RECORD_AUDIO}, 1004 * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and 1005 * {@link android.Manifest.permission#WAKE_LOCK} permissions.</p> 1006 */ 1007 public void startAudio() { 1008 try { 1009 startAudioInternal(); 1010 } catch (UnknownHostException e) { 1011 onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage()); 1012 } catch (Throwable e) { 1013 onError(SipErrorCode.CLIENT_ERROR, e.getMessage()); 1014 } 1015 } 1016 1017 private synchronized void startAudioInternal() throws UnknownHostException { 1018 if (DBG) loge("startAudioInternal: mPeerSd=" + mPeerSd); 1019 if (mPeerSd == null) { 1020 throw new IllegalStateException("mPeerSd = null"); 1021 } 1022 1023 stopCall(DONT_RELEASE_SOCKET); 1024 mInCall = true; 1025 1026 // Run exact the same logic in createAnswer() to setup mAudioStream. 1027 SimpleSessionDescription offer = 1028 new SimpleSessionDescription(mPeerSd); 1029 AudioStream stream = mAudioStream; 1030 AudioCodec codec = null; 1031 for (Media media : offer.getMedia()) { 1032 if ((codec == null) && (media.getPort() > 0) 1033 && "audio".equals(media.getType()) 1034 && "RTP/AVP".equals(media.getProtocol())) { 1035 // Find the first audio codec we supported. 1036 for (int type : media.getRtpPayloadTypes()) { 1037 codec = AudioCodec.getCodec( 1038 type, media.getRtpmap(type), media.getFmtp(type)); 1039 if (codec != null) { 1040 break; 1041 } 1042 } 1043 1044 if (codec != null) { 1045 // Associate with the remote host. 1046 String address = media.getAddress(); 1047 if (address == null) { 1048 address = offer.getAddress(); 1049 } 1050 stream.associate(InetAddress.getByName(address), 1051 media.getPort()); 1052 1053 stream.setDtmfType(-1); 1054 stream.setCodec(codec); 1055 // Check if DTMF is supported in the same media. 1056 for (int type : media.getRtpPayloadTypes()) { 1057 String rtpmap = media.getRtpmap(type); 1058 if ((type != codec.type) && (rtpmap != null) 1059 && rtpmap.startsWith("telephone-event")) { 1060 stream.setDtmfType(type); 1061 } 1062 } 1063 1064 // Handle recvonly and sendonly. 1065 if (mHold) { 1066 stream.setMode(RtpStream.MODE_NORMAL); 1067 } else if (media.getAttribute("recvonly") != null) { 1068 stream.setMode(RtpStream.MODE_SEND_ONLY); 1069 } else if(media.getAttribute("sendonly") != null) { 1070 stream.setMode(RtpStream.MODE_RECEIVE_ONLY); 1071 } else if(offer.getAttribute("recvonly") != null) { 1072 stream.setMode(RtpStream.MODE_SEND_ONLY); 1073 } else if(offer.getAttribute("sendonly") != null) { 1074 stream.setMode(RtpStream.MODE_RECEIVE_ONLY); 1075 } else { 1076 stream.setMode(RtpStream.MODE_NORMAL); 1077 } 1078 break; 1079 } 1080 } 1081 } 1082 if (codec == null) { 1083 throw new IllegalStateException("Reject SDP: no suitable codecs"); 1084 } 1085 1086 if (isWifiOn()) grabWifiHighPerfLock(); 1087 1088 // AudioGroup logic: 1089 AudioGroup audioGroup = getAudioGroup(); 1090 if (mHold) { 1091 // don't create an AudioGroup here; doing so will fail if 1092 // there's another AudioGroup out there that's active 1093 } else { 1094 if (audioGroup == null) audioGroup = new AudioGroup(mContext); 1095 stream.join(audioGroup); 1096 } 1097 setAudioGroupMode(); 1098 } 1099 1100 // set audio group mode based on current audio configuration 1101 private void setAudioGroupMode() { 1102 AudioGroup audioGroup = getAudioGroup(); 1103 if (DBG) log("setAudioGroupMode: audioGroup=" + audioGroup); 1104 if (audioGroup != null) { 1105 if (mHold) { 1106 audioGroup.setMode(AudioGroup.MODE_ON_HOLD); 1107 } else if (mMuted) { 1108 audioGroup.setMode(AudioGroup.MODE_MUTED); 1109 } else if (isSpeakerOn()) { 1110 audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION); 1111 } else { 1112 audioGroup.setMode(AudioGroup.MODE_NORMAL); 1113 } 1114 } 1115 } 1116 1117 private void stopCall(boolean releaseSocket) { 1118 if (DBG) log("stopCall: releaseSocket=" + releaseSocket); 1119 releaseWifiHighPerfLock(); 1120 if (mAudioStream != null) { 1121 mAudioStream.join(null); 1122 1123 if (releaseSocket) { 1124 mAudioStream.release(); 1125 mAudioStream = null; 1126 } 1127 } 1128 } 1129 1130 private String getLocalIp() { 1131 return mSipSession.getLocalIp(); 1132 } 1133 1134 private void throwSipException(Throwable throwable) throws SipException { 1135 if (throwable instanceof SipException) { 1136 throw (SipException) throwable; 1137 } else { 1138 throw new SipException("", throwable); 1139 } 1140 } 1141 1142 private void log(String s) { 1143 Rlog.d(LOG_TAG, s); 1144 } 1145 1146 private void loge(String s) { 1147 Rlog.e(LOG_TAG, s); 1148 } 1149 1150 private void loge(String s, Throwable t) { 1151 Rlog.e(LOG_TAG, s, t); 1152 } 1153 } 1154