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