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