SipAudioCall.java revision 84a357bb6a8005e1c5e924e96a8ecf310e77c47c
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 if (mWifiHighPerfLock == null) { 725 Log.v(TAG, "acquire wifi high perf lock"); 726 mWifiHighPerfLock = ((WifiManager) 727 mContext.getSystemService(Context.WIFI_SERVICE)) 728 .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG); 729 mWifiHighPerfLock.acquire(); 730 } 731 } 732 733 private void releaseWifiHighPerfLock() { 734 if (mWifiHighPerfLock != null) { 735 Log.v(TAG, "release wifi high perf lock"); 736 mWifiHighPerfLock.release(); 737 mWifiHighPerfLock = null; 738 } 739 } 740 741 private boolean isWifiOn() { 742 return (mWm.getConnectionInfo().getBSSID() == null) ? false : true; 743 } 744 745 /** Toggles mute. */ 746 public synchronized void toggleMute() { 747 AudioGroup audioGroup = getAudioGroup(); 748 if (audioGroup != null) { 749 audioGroup.setMode( 750 mMuted ? AudioGroup.MODE_NORMAL : AudioGroup.MODE_MUTED); 751 mMuted = !mMuted; 752 } 753 } 754 755 /** 756 * Checks if the call is muted. 757 * 758 * @return true if the call is muted 759 */ 760 public synchronized boolean isMuted() { 761 return mMuted; 762 } 763 764 /** Puts the device to speaker mode. */ 765 public synchronized void setSpeakerMode(boolean speakerMode) { 766 ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) 767 .setSpeakerphoneOn(speakerMode); 768 } 769 770 /** 771 * Sends a DTMF code. According to RFC2833, event 0--9 maps to decimal 772 * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event 773 * flash to 16. Currently, event flash is not supported. 774 * 775 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid 776 * inputs. 777 * @see http://tools.ietf.org/html/rfc2833 778 */ 779 public void sendDtmf(int code) { 780 sendDtmf(code, null); 781 } 782 783 /** 784 * Sends a DTMF code. According to RFC2833, event 0--9 maps to decimal 785 * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event 786 * flash to 16. Currently, event flash is not supported. 787 * 788 * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid 789 * inputs. 790 * @param result the result message to send when done 791 */ 792 public synchronized void sendDtmf(int code, Message result) { 793 AudioGroup audioGroup = getAudioGroup(); 794 if ((audioGroup != null) && (mSipSession != null) 795 && (SipSession.State.IN_CALL == getState())) { 796 Log.v(TAG, "send DTMF: " + code); 797 audioGroup.sendDtmf(code); 798 } 799 if (result != null) result.sendToTarget(); 800 } 801 802 /** 803 * Gets the {@link AudioStream} object used in this call. The object 804 * represents the RTP stream that carries the audio data to and from the 805 * peer. The object may not be created before the call is established. And 806 * it is undefined after the call ends or the {@link #close} method is 807 * called. 808 * 809 * @return the {@link AudioStream} object or null if the RTP stream has not 810 * yet been set up 811 * @hide 812 */ 813 public synchronized AudioStream getAudioStream() { 814 return mAudioStream; 815 } 816 817 /** 818 * Gets the {@link AudioGroup} object which the {@link AudioStream} object 819 * joins. The group object may not exist before the call is established. 820 * Also, the {@code AudioStream} may change its group during a call (e.g., 821 * after the call is held/un-held). Finally, the {@code AudioGroup} object 822 * returned by this method is undefined after the call ends or the 823 * {@link #close} method is called. If a group object is set by 824 * {@link #setAudioGroup(AudioGroup)}, then this method returns that object. 825 * 826 * @return the {@link AudioGroup} object or null if the RTP stream has not 827 * yet been set up 828 * @see #getAudioStream 829 * @hide 830 */ 831 public synchronized AudioGroup getAudioGroup() { 832 if (mAudioGroup != null) return mAudioGroup; 833 return ((mAudioStream == null) ? null : mAudioStream.getGroup()); 834 } 835 836 /** 837 * Sets the {@link AudioGroup} object which the {@link AudioStream} object 838 * joins. If {@code audioGroup} is null, then the {@code AudioGroup} object 839 * will be dynamically created when needed. 840 * 841 * @see #getAudioStream 842 * @hide 843 */ 844 public synchronized void setAudioGroup(AudioGroup group) { 845 if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) { 846 mAudioStream.join(group); 847 } 848 mAudioGroup = group; 849 } 850 851 /** 852 * Starts the audio for the established call. This method should be called 853 * after {@link Listener#onCallEstablished} is called. 854 */ 855 public void startAudio() { 856 try { 857 startAudioInternal(); 858 } catch (UnknownHostException e) { 859 onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage()); 860 } catch (Throwable e) { 861 onError(SipErrorCode.CLIENT_ERROR, e.getMessage()); 862 } 863 } 864 865 private synchronized void startAudioInternal() throws UnknownHostException { 866 if (mPeerSd == null) { 867 Log.v(TAG, "startAudioInternal() mPeerSd = null"); 868 throw new IllegalStateException("mPeerSd = null"); 869 } 870 871 stopCall(DONT_RELEASE_SOCKET); 872 mInCall = true; 873 874 // Run exact the same logic in createAnswer() to setup mAudioStream. 875 SimpleSessionDescription offer = 876 new SimpleSessionDescription(mPeerSd); 877 AudioStream stream = mAudioStream; 878 AudioCodec codec = null; 879 for (Media media : offer.getMedia()) { 880 if ((codec == null) && (media.getPort() > 0) 881 && "audio".equals(media.getType()) 882 && "RTP/AVP".equals(media.getProtocol())) { 883 // Find the first audio codec we supported. 884 for (int type : media.getRtpPayloadTypes()) { 885 codec = AudioCodec.getCodec( 886 type, media.getRtpmap(type), media.getFmtp(type)); 887 if (codec != null) { 888 break; 889 } 890 } 891 892 if (codec != null) { 893 // Associate with the remote host. 894 String address = media.getAddress(); 895 if (address == null) { 896 address = offer.getAddress(); 897 } 898 stream.associate(InetAddress.getByName(address), 899 media.getPort()); 900 901 stream.setDtmfType(-1); 902 stream.setCodec(codec); 903 // Check if DTMF is supported in the same media. 904 for (int type : media.getRtpPayloadTypes()) { 905 String rtpmap = media.getRtpmap(type); 906 if ((type != codec.type) && (rtpmap != null) 907 && rtpmap.startsWith("telephone-event")) { 908 stream.setDtmfType(type); 909 } 910 } 911 912 // Handle recvonly and sendonly. 913 if (mHold) { 914 stream.setMode(RtpStream.MODE_NORMAL); 915 } else if (media.getAttribute("recvonly") != null) { 916 stream.setMode(RtpStream.MODE_SEND_ONLY); 917 } else if(media.getAttribute("sendonly") != null) { 918 stream.setMode(RtpStream.MODE_RECEIVE_ONLY); 919 } else if(offer.getAttribute("recvonly") != null) { 920 stream.setMode(RtpStream.MODE_SEND_ONLY); 921 } else if(offer.getAttribute("sendonly") != null) { 922 stream.setMode(RtpStream.MODE_RECEIVE_ONLY); 923 } else { 924 stream.setMode(RtpStream.MODE_NORMAL); 925 } 926 break; 927 } 928 } 929 } 930 if (codec == null) { 931 throw new IllegalStateException("Reject SDP: no suitable codecs"); 932 } 933 934 if (isWifiOn()) grabWifiHighPerfLock(); 935 936 if (!mHold) { 937 /* The recorder volume will be very low if the device is in 938 * IN_CALL mode. Therefore, we have to set the mode to NORMAL 939 * in order to have the normal microphone level. 940 */ 941 ((AudioManager) mContext.getSystemService 942 (Context.AUDIO_SERVICE)) 943 .setMode(AudioManager.MODE_NORMAL); 944 } 945 946 // AudioGroup logic: 947 AudioGroup audioGroup = getAudioGroup(); 948 if (mHold) { 949 if (audioGroup != null) { 950 audioGroup.setMode(AudioGroup.MODE_ON_HOLD); 951 } 952 // don't create an AudioGroup here; doing so will fail if 953 // there's another AudioGroup out there that's active 954 } else { 955 if (audioGroup == null) audioGroup = new AudioGroup(); 956 stream.join(audioGroup); 957 if (mMuted) { 958 audioGroup.setMode(AudioGroup.MODE_MUTED); 959 } else { 960 audioGroup.setMode(AudioGroup.MODE_NORMAL); 961 } 962 } 963 } 964 965 private void stopCall(boolean releaseSocket) { 966 Log.d(TAG, "stop audiocall"); 967 releaseWifiHighPerfLock(); 968 if (mAudioStream != null) { 969 mAudioStream.join(null); 970 971 if (releaseSocket) { 972 mAudioStream.release(); 973 mAudioStream = null; 974 } 975 } 976 } 977 978 private String getLocalIp() { 979 return mSipSession.getLocalIp(); 980 } 981 982 983 /** 984 * Enables/disables the ring-back tone. 985 * 986 * @param enabled true to enable; false to disable 987 */ 988 public synchronized void setRingbackToneEnabled(boolean enabled) { 989 mRingbackToneEnabled = enabled; 990 } 991 992 /** 993 * Enables/disables the ring tone. 994 * 995 * @param enabled true to enable; false to disable 996 */ 997 public synchronized void setRingtoneEnabled(boolean enabled) { 998 mRingtoneEnabled = enabled; 999 } 1000 1001 private void startRingbackTone() { 1002 if (!mRingbackToneEnabled) return; 1003 if (mRingbackTone == null) { 1004 // The volume relative to other sounds in the stream 1005 int toneVolume = 80; 1006 mRingbackTone = new ToneGenerator( 1007 AudioManager.STREAM_VOICE_CALL, toneVolume); 1008 } 1009 mRingbackTone.startTone(ToneGenerator.TONE_CDMA_LOW_PBX_L); 1010 } 1011 1012 private void stopRingbackTone() { 1013 if (mRingbackTone != null) { 1014 mRingbackTone.stopTone(); 1015 mRingbackTone.release(); 1016 mRingbackTone = null; 1017 } 1018 } 1019 1020 private void startRinging() { 1021 if (!mRingtoneEnabled) return; 1022 ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE)) 1023 .vibrate(new long[] {0, 1000, 1000}, 1); 1024 AudioManager am = (AudioManager) 1025 mContext.getSystemService(Context.AUDIO_SERVICE); 1026 if (am.getStreamVolume(AudioManager.STREAM_RING) > 0) { 1027 String ringtoneUri = 1028 Settings.System.DEFAULT_RINGTONE_URI.toString(); 1029 mRingtone = RingtoneManager.getRingtone(mContext, 1030 Uri.parse(ringtoneUri)); 1031 mRingtone.play(); 1032 } 1033 } 1034 1035 private void stopRinging() { 1036 ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE)) 1037 .cancel(); 1038 if (mRingtone != null) mRingtone.stop(); 1039 } 1040 1041 private void throwSipException(Throwable throwable) throws SipException { 1042 if (throwable instanceof SipException) { 1043 throw (SipException) throwable; 1044 } else { 1045 throw new SipException("", throwable); 1046 } 1047 } 1048 1049 private SipProfile getPeerProfile(SipSession session) { 1050 return session.getPeerProfile(); 1051 } 1052} 1053