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