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