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