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