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