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