SipAudioCall.java revision 89bc1fe73efb73b43758d41c9ff9f2f4902dd019
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.net.rtp.AudioCodec; 22import android.net.rtp.AudioGroup; 23import android.net.rtp.AudioStream; 24import android.net.rtp.RtpStream; 25import android.net.sip.SimpleSessionDescription.Media; 26import android.net.wifi.WifiManager; 27import android.os.Message; 28import android.os.RemoteException; 29import android.util.Log; 30 31import java.io.IOException; 32import java.net.InetAddress; 33import java.net.UnknownHostException; 34import java.util.ArrayList; 35import java.util.HashMap; 36import java.util.List; 37import 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 */ 54public 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 or VOIP API is not supported by the device 523 * @see SipManager#isVoipSupported 524 */ 525 public void attachCall(SipSession session, String sessionDescription) 526 throws SipException { 527 if (!SipManager.isVoipSupported(mContext)) { 528 throw new SipException("VOIP API is not supported"); 529 } 530 531 synchronized (this) { 532 mSipSession = session; 533 mPeerSd = sessionDescription; 534 Log.v(TAG, "attachCall()" + mPeerSd); 535 try { 536 session.setListener(createListener()); 537 } catch (Throwable e) { 538 Log.e(TAG, "attachCall()", e); 539 throwSipException(e); 540 } 541 } 542 } 543 544 /** 545 * Initiates an audio call to the specified profile. The attempt will be 546 * timed out if the call is not established within {@code timeout} seconds 547 * and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 548 * will be called. 549 * 550 * @param peerProfile the SIP profile to make the call to 551 * @param sipSession the {@link SipSession} for carrying out the call 552 * @param timeout the timeout value in seconds. Default value (defined by 553 * SIP protocol) is used if {@code timeout} is zero or negative. 554 * @see Listener#onError 555 * @throws SipException if the SIP service fails to create a session for the 556 * call or VOIP API is not supported by the device 557 * @see SipManager#isVoipSupported 558 */ 559 public void makeCall(SipProfile peerProfile, SipSession sipSession, 560 int timeout) throws SipException { 561 if (!SipManager.isVoipSupported(mContext)) { 562 throw new SipException("VOIP API is not supported"); 563 } 564 565 synchronized (this) { 566 mSipSession = sipSession; 567 try { 568 mAudioStream = new AudioStream(InetAddress.getByName( 569 getLocalIp())); 570 sipSession.setListener(createListener()); 571 sipSession.makeCall(peerProfile, createOffer().encode(), 572 timeout); 573 } catch (IOException e) { 574 throw new SipException("makeCall()", e); 575 } 576 } 577 } 578 579 /** 580 * Ends a call. 581 * @throws SipException if the SIP service fails to end the call 582 */ 583 public void endCall() throws SipException { 584 synchronized (this) { 585 stopCall(RELEASE_SOCKET); 586 mInCall = false; 587 588 // perform the above local ops first and then network op 589 if (mSipSession != null) mSipSession.endCall(); 590 } 591 } 592 593 /** 594 * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is 595 * called. The attempt will be timed out if the call is not established 596 * within {@code timeout} seconds and 597 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 598 * will be called. 599 * 600 * @param timeout the timeout value in seconds. Default value (defined by 601 * SIP protocol) is used if {@code timeout} is zero or negative. 602 * @see Listener#onError 603 * @throws SipException if the SIP service fails to hold the call 604 */ 605 public void holdCall(int timeout) throws SipException { 606 synchronized (this) { 607 if (mHold) return; 608 if (mSipSession == null) { 609 throw new SipException("Not in a call to hold call"); 610 } 611 mSipSession.changeCall(createHoldOffer().encode(), timeout); 612 mHold = true; 613 setAudioGroupMode(); 614 } 615 } 616 617 /** 618 * Answers a call. The attempt will be timed out if the call is not 619 * established within {@code timeout} seconds and 620 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 621 * will be called. 622 * 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 answer the call 627 */ 628 public void answerCall(int timeout) throws SipException { 629 synchronized (this) { 630 if (mSipSession == null) { 631 throw new SipException("No call to answer"); 632 } 633 try { 634 mAudioStream = new AudioStream(InetAddress.getByName( 635 getLocalIp())); 636 mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout); 637 } catch (IOException e) { 638 throw new SipException("answerCall()", e); 639 } 640 } 641 } 642 643 /** 644 * Continues a call that's on hold. When succeeds, 645 * {@link Listener#onCallEstablished} is called. The attempt will be timed 646 * out if the call is not established within {@code timeout} seconds and 647 * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} 648 * will be called. 649 * 650 * @param timeout the timeout value in seconds. Default value (defined by 651 * SIP protocol) is used if {@code timeout} is zero or negative. 652 * @see Listener#onError 653 * @throws SipException if the SIP service fails to unhold the call 654 */ 655 public void continueCall(int timeout) throws SipException { 656 synchronized (this) { 657 if (!mHold) return; 658 mSipSession.changeCall(createContinueOffer().encode(), timeout); 659 mHold = false; 660 setAudioGroupMode(); 661 } 662 } 663 664 private SimpleSessionDescription createOffer() { 665 SimpleSessionDescription offer = 666 new SimpleSessionDescription(mSessionId, getLocalIp()); 667 AudioCodec[] codecs = AudioCodec.getCodecs(); 668 Media media = offer.newMedia( 669 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP"); 670 for (AudioCodec codec : AudioCodec.getCodecs()) { 671 media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp); 672 } 673 media.setRtpPayload(127, "telephone-event/8000", "0-15"); 674 return offer; 675 } 676 677 private SimpleSessionDescription createAnswer(String offerSd) { 678 SimpleSessionDescription offer = 679 new SimpleSessionDescription(offerSd); 680 SimpleSessionDescription answer = 681 new SimpleSessionDescription(mSessionId, getLocalIp()); 682 AudioCodec codec = null; 683 for (Media media : offer.getMedia()) { 684 if ((codec == null) && (media.getPort() > 0) 685 && "audio".equals(media.getType()) 686 && "RTP/AVP".equals(media.getProtocol())) { 687 // Find the first audio codec we supported. 688 for (int type : media.getRtpPayloadTypes()) { 689 codec = AudioCodec.getCodec(type, media.getRtpmap(type), 690 media.getFmtp(type)); 691 if (codec != null) { 692 break; 693 } 694 } 695 if (codec != null) { 696 Media reply = answer.newMedia( 697 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP"); 698 reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp); 699 700 // Check if DTMF is supported in the same media. 701 for (int type : media.getRtpPayloadTypes()) { 702 String rtpmap = media.getRtpmap(type); 703 if ((type != codec.type) && (rtpmap != null) 704 && rtpmap.startsWith("telephone-event")) { 705 reply.setRtpPayload( 706 type, rtpmap, media.getFmtp(type)); 707 } 708 } 709 710 // Handle recvonly and sendonly. 711 if (media.getAttribute("recvonly") != null) { 712 answer.setAttribute("sendonly", ""); 713 } else if(media.getAttribute("sendonly") != null) { 714 answer.setAttribute("recvonly", ""); 715 } else if(offer.getAttribute("recvonly") != null) { 716 answer.setAttribute("sendonly", ""); 717 } else if(offer.getAttribute("sendonly") != null) { 718 answer.setAttribute("recvonly", ""); 719 } 720 continue; 721 } 722 } 723 // Reject the media. 724 Media reply = answer.newMedia( 725 media.getType(), 0, 1, media.getProtocol()); 726 for (String format : media.getFormats()) { 727 reply.setFormat(format, null); 728 } 729 } 730 if (codec == null) { 731 throw new IllegalStateException("Reject SDP: no suitable codecs"); 732 } 733 return answer; 734 } 735 736 private SimpleSessionDescription createHoldOffer() { 737 SimpleSessionDescription offer = createContinueOffer(); 738 offer.setAttribute("sendonly", ""); 739 return offer; 740 } 741 742 private SimpleSessionDescription createContinueOffer() { 743 SimpleSessionDescription offer = 744 new SimpleSessionDescription(mSessionId, getLocalIp()); 745 Media media = offer.newMedia( 746 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP"); 747 AudioCodec codec = mAudioStream.getCodec(); 748 media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp); 749 int dtmfType = mAudioStream.getDtmfType(); 750 if (dtmfType != -1) { 751 media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15"); 752 } 753 return offer; 754 } 755 756 private void grabWifiHighPerfLock() { 757 if (mWifiHighPerfLock == null) { 758 Log.v(TAG, "acquire wifi high perf lock"); 759 mWifiHighPerfLock = ((WifiManager) 760 mContext.getSystemService(Context.WIFI_SERVICE)) 761 .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG); 762 mWifiHighPerfLock.acquire(); 763 } 764 } 765 766 private void releaseWifiHighPerfLock() { 767 if (mWifiHighPerfLock != null) { 768 Log.v(TAG, "release wifi high perf lock"); 769 mWifiHighPerfLock.release(); 770 mWifiHighPerfLock = null; 771 } 772 } 773 774 private boolean isWifiOn() { 775 return (mWm.getConnectionInfo().getBSSID() == null) ? false : true; 776 } 777 778 /** Toggles mute. */ 779 public void toggleMute() { 780 synchronized (this) { 781 mMuted = !mMuted; 782 setAudioGroupMode(); 783 } 784 } 785 786 /** 787 * Checks if the call is muted. 788 * 789 * @return true if the call is muted 790 */ 791 public boolean isMuted() { 792 synchronized (this) { 793 return mMuted; 794 } 795 } 796 797 /** 798 * Puts the device to speaker mode. 799 * <p class="note"><strong>Note:</strong> Requires the 800 * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p> 801 * 802 * @param speakerMode set true to enable speaker mode; false to disable 803 */ 804 public void setSpeakerMode(boolean speakerMode) { 805 synchronized (this) { 806 ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) 807 .setSpeakerphoneOn(speakerMode); 808 setAudioGroupMode(); 809 } 810 } 811 812 private boolean isSpeakerOn() { 813 return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) 814 .isSpeakerphoneOn(); 815 } 816 817 /** 818 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>, 819 * event 0--9 maps to decimal 820 * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event 821 * flash to 16. Currently, event flash is not supported. 822 * 823 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid 824 * inputs. 825 */ 826 public void sendDtmf(int code) { 827 sendDtmf(code, null); 828 } 829 830 /** 831 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>, 832 * event 0--9 maps to decimal 833 * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event 834 * flash to 16. Currently, event flash is not supported. 835 * 836 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid 837 * inputs. 838 * @param result the result message to send when done 839 */ 840 public void sendDtmf(int code, Message result) { 841 synchronized (this) { 842 AudioGroup audioGroup = getAudioGroup(); 843 if ((audioGroup != null) && (mSipSession != null) 844 && (SipSession.State.IN_CALL == getState())) { 845 Log.v(TAG, "send DTMF: " + code); 846 audioGroup.sendDtmf(code); 847 } 848 if (result != null) result.sendToTarget(); 849 } 850 } 851 852 /** 853 * Gets the {@link AudioStream} object used in this call. The object 854 * represents the RTP stream that carries the audio data to and from the 855 * peer. The object may not be created before the call is established. And 856 * it is undefined after the call ends or the {@link #close} method is 857 * called. 858 * 859 * @return the {@link AudioStream} object or null if the RTP stream has not 860 * yet been set up 861 * @hide 862 */ 863 public AudioStream getAudioStream() { 864 synchronized (this) { 865 return mAudioStream; 866 } 867 } 868 869 /** 870 * Gets the {@link AudioGroup} object which the {@link AudioStream} object 871 * joins. The group object may not exist before the call is established. 872 * Also, the {@code AudioStream} may change its group during a call (e.g., 873 * after the call is held/un-held). Finally, the {@code AudioGroup} object 874 * returned by this method is undefined after the call ends or the 875 * {@link #close} method is called. If a group object is set by 876 * {@link #setAudioGroup(AudioGroup)}, then this method returns that object. 877 * 878 * @return the {@link AudioGroup} object or null if the RTP stream has not 879 * yet been set up 880 * @see #getAudioStream 881 * @hide 882 */ 883 public AudioGroup getAudioGroup() { 884 synchronized (this) { 885 if (mAudioGroup != null) return mAudioGroup; 886 return ((mAudioStream == null) ? null : mAudioStream.getGroup()); 887 } 888 } 889 890 /** 891 * Sets the {@link AudioGroup} object which the {@link AudioStream} object 892 * joins. If {@code audioGroup} is null, then the {@code AudioGroup} object 893 * will be dynamically created when needed. Note that the mode of the 894 * {@code AudioGroup} is not changed according to the audio settings (i.e., 895 * hold, mute, speaker phone) of this object. This is mainly used to merge 896 * multiple {@code SipAudioCall} objects to form a conference call. The 897 * settings of the first object (that merges others) override others'. 898 * 899 * @see #getAudioStream 900 * @hide 901 */ 902 public void setAudioGroup(AudioGroup group) { 903 synchronized (this) { 904 if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) { 905 mAudioStream.join(group); 906 } 907 mAudioGroup = group; 908 } 909 } 910 911 /** 912 * Starts the audio for the established call. This method should be called 913 * after {@link Listener#onCallEstablished} is called. 914 * <p class="note"><strong>Note:</strong> Requires the 915 * {@link android.Manifest.permission#RECORD_AUDIO}, 916 * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and 917 * {@link android.Manifest.permission#WAKE_LOCK} permissions.</p> 918 */ 919 public void startAudio() { 920 try { 921 startAudioInternal(); 922 } catch (UnknownHostException e) { 923 onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage()); 924 } catch (Throwable e) { 925 onError(SipErrorCode.CLIENT_ERROR, e.getMessage()); 926 } 927 } 928 929 private synchronized void startAudioInternal() throws UnknownHostException { 930 if (mPeerSd == null) { 931 Log.v(TAG, "startAudioInternal() mPeerSd = null"); 932 throw new IllegalStateException("mPeerSd = null"); 933 } 934 935 stopCall(DONT_RELEASE_SOCKET); 936 mInCall = true; 937 938 // Run exact the same logic in createAnswer() to setup mAudioStream. 939 SimpleSessionDescription offer = 940 new SimpleSessionDescription(mPeerSd); 941 AudioStream stream = mAudioStream; 942 AudioCodec codec = null; 943 for (Media media : offer.getMedia()) { 944 if ((codec == null) && (media.getPort() > 0) 945 && "audio".equals(media.getType()) 946 && "RTP/AVP".equals(media.getProtocol())) { 947 // Find the first audio codec we supported. 948 for (int type : media.getRtpPayloadTypes()) { 949 codec = AudioCodec.getCodec( 950 type, media.getRtpmap(type), media.getFmtp(type)); 951 if (codec != null) { 952 break; 953 } 954 } 955 956 if (codec != null) { 957 // Associate with the remote host. 958 String address = media.getAddress(); 959 if (address == null) { 960 address = offer.getAddress(); 961 } 962 stream.associate(InetAddress.getByName(address), 963 media.getPort()); 964 965 stream.setDtmfType(-1); 966 stream.setCodec(codec); 967 // Check if DTMF is supported in the same media. 968 for (int type : media.getRtpPayloadTypes()) { 969 String rtpmap = media.getRtpmap(type); 970 if ((type != codec.type) && (rtpmap != null) 971 && rtpmap.startsWith("telephone-event")) { 972 stream.setDtmfType(type); 973 } 974 } 975 976 // Handle recvonly and sendonly. 977 if (mHold) { 978 stream.setMode(RtpStream.MODE_NORMAL); 979 } else if (media.getAttribute("recvonly") != null) { 980 stream.setMode(RtpStream.MODE_SEND_ONLY); 981 } else if(media.getAttribute("sendonly") != null) { 982 stream.setMode(RtpStream.MODE_RECEIVE_ONLY); 983 } else if(offer.getAttribute("recvonly") != null) { 984 stream.setMode(RtpStream.MODE_SEND_ONLY); 985 } else if(offer.getAttribute("sendonly") != null) { 986 stream.setMode(RtpStream.MODE_RECEIVE_ONLY); 987 } else { 988 stream.setMode(RtpStream.MODE_NORMAL); 989 } 990 break; 991 } 992 } 993 } 994 if (codec == null) { 995 throw new IllegalStateException("Reject SDP: no suitable codecs"); 996 } 997 998 if (isWifiOn()) grabWifiHighPerfLock(); 999 1000 // AudioGroup logic: 1001 AudioGroup audioGroup = getAudioGroup(); 1002 if (mHold) { 1003 // don't create an AudioGroup here; doing so will fail if 1004 // there's another AudioGroup out there that's active 1005 } else { 1006 if (audioGroup == null) audioGroup = new AudioGroup(); 1007 stream.join(audioGroup); 1008 } 1009 setAudioGroupMode(); 1010 } 1011 1012 // set audio group mode based on current audio configuration 1013 private void setAudioGroupMode() { 1014 AudioGroup audioGroup = getAudioGroup(); 1015 if (audioGroup != null) { 1016 if (mHold) { 1017 audioGroup.setMode(AudioGroup.MODE_ON_HOLD); 1018 } else if (mMuted) { 1019 audioGroup.setMode(AudioGroup.MODE_MUTED); 1020 } else if (isSpeakerOn()) { 1021 audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION); 1022 } else { 1023 audioGroup.setMode(AudioGroup.MODE_NORMAL); 1024 } 1025 } 1026 } 1027 1028 private void stopCall(boolean releaseSocket) { 1029 Log.d(TAG, "stop audiocall"); 1030 releaseWifiHighPerfLock(); 1031 if (mAudioStream != null) { 1032 mAudioStream.join(null); 1033 1034 if (releaseSocket) { 1035 mAudioStream.release(); 1036 mAudioStream = null; 1037 } 1038 } 1039 } 1040 1041 private String getLocalIp() { 1042 return mSipSession.getLocalIp(); 1043 } 1044 1045 private void throwSipException(Throwable throwable) throws SipException { 1046 if (throwable instanceof SipException) { 1047 throw (SipException) throwable; 1048 } else { 1049 throw new SipException("", throwable); 1050 } 1051 } 1052 1053 private SipProfile getPeerProfile(SipSession session) { 1054 return session.getPeerProfile(); 1055 } 1056} 1057