SipAudioCall.java revision f498e98d2e2910e866ec0728cebbe676dd475d9e
198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang/*
298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * Copyright (C) 2010 The Android Open Source Project
398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang *
498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * Licensed under the Apache License, Version 2.0 (the "License");
598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * you may not use this file except in compliance with the License.
698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * You may obtain a copy of the License at
798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang *
898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang *      http://www.apache.org/licenses/LICENSE-2.0
998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang *
1098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * Unless required by applicable law or agreed to in writing, software
1198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * distributed under the License is distributed on an "AS IS" BASIS,
1298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * See the License for the specific language governing permissions and
1498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * limitations under the License.
1598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang */
1698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
1798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangpackage android.net.sip;
1898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
1998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport gov.nist.javax.sdp.fields.SDPKeywords;
2098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
2198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.content.Context;
2298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.media.AudioManager;
2398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.media.Ringtone;
2498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.media.RingtoneManager;
2598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.media.ToneGenerator;
2698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.net.Uri;
2798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.net.rtp.AudioCodec;
2898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.net.rtp.AudioGroup;
2998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.net.rtp.AudioStream;
3098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.net.rtp.RtpStream;
319248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wangimport android.net.wifi.WifiManager;
3298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.os.Message;
3398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.os.RemoteException;
3498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.os.Vibrator;
3598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.provider.Settings;
3698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport android.util.Log;
3798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
3898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport java.io.IOException;
3998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport java.net.InetAddress;
4098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport java.text.ParseException;
4198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport java.util.ArrayList;
4298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport java.util.HashMap;
4398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport java.util.List;
4498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport java.util.Map;
4598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport javax.sdp.SdpException;
4698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
4798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang/**
4898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * Class that handles an audio call over SIP.
4998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang */
5098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang/** @hide */
5198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangpublic class SipAudioCallImpl extends SipSessionAdapter
5298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        implements SipAudioCall {
5398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private static final String TAG = SipAudioCallImpl.class.getSimpleName();
5498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private static final boolean RELEASE_SOCKET = true;
5598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private static final boolean DONT_RELEASE_SOCKET = false;
5698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private static final String AUDIO = "audio";
5798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private static final int DTMF = 101;
58f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan    private static final int SESSION_TIMEOUT = 5; // in seconds
5998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
6098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private Context mContext;
6198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private SipProfile mLocalProfile;
6298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private SipAudioCall.Listener mListener;
6398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private ISipSession mSipSession;
6498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private SdpSessionDescription mPeerSd;
6598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
660ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan    private AudioStream mAudioStream;
670ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan    private AudioGroup mAudioGroup;
6898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private SdpSessionDescription.AudioCodec mCodec;
6998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private long mSessionId = -1L; // SDP session ID
7098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private boolean mInCall = false;
7198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private boolean mMuted = false;
7298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private boolean mHold = false;
7398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
7498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private boolean mRingbackToneEnabled = true;
7598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private boolean mRingtoneEnabled = true;
7698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private Ringtone mRingtone;
7798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private ToneGenerator mRingbackTone;
7898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
7998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private SipProfile mPendingCallRequest;
809248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang    private WifiManager mWm;
819248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang    private WifiManager.WifiLock mWifiHighPerfLock;
8298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
83188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan    private SipErrorCode mErrorCode;
84188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan    private String mErrorMessage;
85188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan
8698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public SipAudioCallImpl(Context context, SipProfile localProfile) {
8798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        mContext = context;
8898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        mLocalProfile = localProfile;
899248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang        mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
9098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
9198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
9298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public void setListener(SipAudioCall.Listener listener) {
9398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        setListener(listener, false);
9498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
9598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
9698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public void setListener(SipAudioCall.Listener listener,
9798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            boolean callbackImmediately) {
9898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        mListener = listener;
9998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
100188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan            if ((listener == null) || !callbackImmediately) {
101188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                // do nothing
102188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan            } else if (mErrorCode != null) {
103188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                listener.onError(this, mErrorCode, mErrorMessage);
104188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan            } else if (mInCall) {
105188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                if (mHold) {
106188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                    listener.onCallHeld(this);
107188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                } else {
108188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                    listener.onCallEstablished(this);
109188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                }
110188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan            } else {
111188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                SipSessionState state = getState();
112188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                switch (state) {
113188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                    case READY_TO_CALL:
114188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                        listener.onReadyToCall(this);
115188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                        break;
116188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                    case INCOMING_CALL:
117188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                        listener.onRinging(this, getPeerProfile(mSipSession));
118188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                        break;
119188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                    case OUTGOING_CALL:
120188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                        listener.onCalling(this);
121188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                        break;
122188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                    case OUTGOING_CALL_RING_BACK:
123188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                        listener.onRingingBack(this);
124188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                        break;
125188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                }
12698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
12798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (Throwable t) {
12898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            Log.e(TAG, "setListener()", t);
12998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
13098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
13198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
13298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized boolean isInCall() {
13398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return mInCall;
13498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
13598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
13698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized boolean isOnHold() {
13798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return mHold;
13898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
13998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
14098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public void close() {
14198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        close(true);
14298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
14398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
14498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private synchronized void close(boolean closeRtp) {
14598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (closeRtp) stopCall(RELEASE_SOCKET);
14698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        stopRingbackTone();
14798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        stopRinging();
148f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan
14998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        mInCall = false;
15098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        mHold = false;
15198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        mSessionId = -1L;
152188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan        mErrorCode = null;
153188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan        mErrorMessage = null;
154f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan
155f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan        if (mSipSession != null) {
156f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan            try {
157f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan                mSipSession.setListener(null);
158f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan            } catch (RemoteException e) {
159f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan                // don't care
160f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan            }
161f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan            mSipSession = null;
162f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan        }
16398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
16498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
16598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized SipProfile getLocalProfile() {
16698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return mLocalProfile;
16798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
16898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
16998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized SipProfile getPeerProfile() {
17098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
17198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            return (mSipSession == null) ? null : mSipSession.getPeerProfile();
17298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (RemoteException e) {
17398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            return null;
17498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
17598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
17698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
17798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized SipSessionState getState() {
17898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (mSipSession == null) return SipSessionState.READY_TO_CALL;
17998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
18098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            return Enum.valueOf(SipSessionState.class, mSipSession.getState());
18198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (RemoteException e) {
18298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            return SipSessionState.REMOTE_ERROR;
18398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
18498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
18598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
18698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
18798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized ISipSession getSipSession() {
18898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return mSipSession;
18998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
19098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
19198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    @Override
19298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public void onCalling(ISipSession session) {
19398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Log.d(TAG, "calling... " + session);
19498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Listener listener = mListener;
19598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (listener != null) {
19698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            try {
19772e725a66e388eca197a1e3ee118bc6481a82fe4Hung-ying Tyan                listener.onCalling(this);
19898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            } catch (Throwable t) {
19998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                Log.e(TAG, "onCalling()", t);
20098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
20198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
20298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
20398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
20498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    @Override
20598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public void onRingingBack(ISipSession session) {
20698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Log.d(TAG, "sip call ringing back: " + session);
20798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (!mInCall) startRingbackTone();
20898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Listener listener = mListener;
20998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (listener != null) {
21098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            try {
21172e725a66e388eca197a1e3ee118bc6481a82fe4Hung-ying Tyan                listener.onRingingBack(this);
21298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            } catch (Throwable t) {
21398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                Log.e(TAG, "onRingingBack()", t);
21498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
21598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
21698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
21798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
21898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    @Override
21998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized void onRinging(ISipSession session,
2206d0ef774a2492b0996ded3a43c300c7f72a94897Chia-chi Yeh            SipProfile peerProfile, String sessionDescription) {
22198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
22298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            if ((mSipSession == null) || !mInCall
22398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    || !session.getCallId().equals(mSipSession.getCallId())) {
22498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                // should not happen
22598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                session.endCall();
22698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                return;
22798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
22898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
22998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            // session changing request
23098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            try {
23198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                mPeerSd = new SdpSessionDescription(sessionDescription);
232f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan                answerCall(SESSION_TIMEOUT);
23398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            } catch (Throwable e) {
23498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                Log.e(TAG, "onRinging()", e);
23598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                session.endCall();
23698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
23798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (RemoteException e) {
23898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            Log.e(TAG, "onRinging()", e);
23998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
24098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
24198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
2426d0ef774a2492b0996ded3a43c300c7f72a94897Chia-chi Yeh    private synchronized void establishCall(String sessionDescription) {
24398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        stopRingbackTone();
24498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        stopRinging();
24598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
24698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            SdpSessionDescription sd =
24798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    new SdpSessionDescription(sessionDescription);
24898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            Log.d(TAG, "sip call established: " + sd);
24998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            startCall(sd);
25098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            mInCall = true;
25198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (SdpException e) {
25298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            Log.e(TAG, "createSessionDescription()", e);
25398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
25498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
25598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
25698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    @Override
25798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public void onCallEstablished(ISipSession session,
2586d0ef774a2492b0996ded3a43c300c7f72a94897Chia-chi Yeh            String sessionDescription) {
25998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        establishCall(sessionDescription);
26098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Listener listener = mListener;
26198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (listener != null) {
26298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            try {
26398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                if (mHold) {
26472e725a66e388eca197a1e3ee118bc6481a82fe4Hung-ying Tyan                    listener.onCallHeld(this);
26598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                } else {
26672e725a66e388eca197a1e3ee118bc6481a82fe4Hung-ying Tyan                    listener.onCallEstablished(this);
26798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                }
26898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            } catch (Throwable t) {
26998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                Log.e(TAG, "onCallEstablished()", t);
27098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
27198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
27298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
27398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
27498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    @Override
27598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public void onCallEnded(ISipSession session) {
27698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Log.d(TAG, "sip call ended: " + session);
27798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        close();
27898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Listener listener = mListener;
27998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (listener != null) {
28098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            try {
28172e725a66e388eca197a1e3ee118bc6481a82fe4Hung-ying Tyan                listener.onCallEnded(this);
28298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            } catch (Throwable t) {
28398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                Log.e(TAG, "onCallEnded()", t);
28498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
28598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
28698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
28798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
28898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    @Override
28998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public void onCallBusy(ISipSession session) {
29098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Log.d(TAG, "sip call busy: " + session);
29198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        close(false);
29298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Listener listener = mListener;
29398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (listener != null) {
29498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            try {
29572e725a66e388eca197a1e3ee118bc6481a82fe4Hung-ying Tyan                listener.onCallBusy(this);
29698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            } catch (Throwable t) {
29798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                Log.e(TAG, "onCallBusy()", t);
29898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
29998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
30098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
30198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
302188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan    private SipErrorCode getErrorCode(String errorCode) {
303188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan        return Enum.valueOf(SipErrorCode.class, errorCode);
304188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan    }
305188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan
30698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    @Override
3072bd51a23644fa0d5a460a4a939e95d5d4e85b891Hung-ying Tyan    public void onCallChangeFailed(ISipSession session, String errorCode,
3082bd51a23644fa0d5a460a4a939e95d5d4e85b891Hung-ying Tyan            String message) {
30998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Log.d(TAG, "sip call change failed: " + message);
310188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan        mErrorCode = getErrorCode(errorCode);
311188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan        mErrorMessage = message;
31298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Listener listener = mListener;
31398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (listener != null) {
31498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            try {
31572e725a66e388eca197a1e3ee118bc6481a82fe4Hung-ying Tyan                listener.onError(this, mErrorCode, message);
31698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            } catch (Throwable t) {
31798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                Log.e(TAG, "onCallBusy()", t);
31898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
31998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
32098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
32198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
32298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    @Override
32372e725a66e388eca197a1e3ee118bc6481a82fe4Hung-ying Tyan    public void onError(ISipSession session, String errorCodeString,
32472e725a66e388eca197a1e3ee118bc6481a82fe4Hung-ying Tyan            String message) {
32572e725a66e388eca197a1e3ee118bc6481a82fe4Hung-ying Tyan        Log.d(TAG, "sip session error: " + errorCodeString + ": " + message);
32672e725a66e388eca197a1e3ee118bc6481a82fe4Hung-ying Tyan        SipErrorCode errorCode = mErrorCode = getErrorCode(errorCodeString);
327188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan        mErrorMessage = message;
32898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        synchronized (this) {
329188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan            if ((mErrorCode == SipErrorCode.DATA_CONNECTION_LOST)
330188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                    || !isInCall()) {
331188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan                close(true);
332188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan            }
33398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
33498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Listener listener = mListener;
33598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (listener != null) {
33698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            try {
33772e725a66e388eca197a1e3ee118bc6481a82fe4Hung-ying Tyan                listener.onError(this, errorCode, message);
33898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            } catch (Throwable t) {
33998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                Log.e(TAG, "onError()", t);
34098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
34198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
34298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
34398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
34498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized void attachCall(ISipSession session,
3456d0ef774a2492b0996ded3a43c300c7f72a94897Chia-chi Yeh            String sessionDescription) throws SipException {
34698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        mSipSession = session;
34798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
3486d0ef774a2492b0996ded3a43c300c7f72a94897Chia-chi Yeh            mPeerSd = new SdpSessionDescription(sessionDescription);
34998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            session.setListener(this);
350188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan
351188f70fdaf7d8cd20e9386051d06ca5a3ba0b683Hung-ying Tyan            if (getState() == SipSessionState.INCOMING_CALL) startRinging();
35298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (Throwable e) {
35398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            Log.e(TAG, "attachCall()", e);
35498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            throwSipException(e);
35598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
35698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
35798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
35898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized void makeCall(SipProfile peerProfile,
359f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan            SipManager sipManager, int timeout) throws SipException {
36098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
36198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            mSipSession = sipManager.createSipSession(mLocalProfile, this);
36298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            if (mSipSession == null) {
36398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                throw new SipException(
36498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                        "Failed to create SipSession; network available?");
36598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
366f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan            mSipSession.makeCall(peerProfile, createOfferSessionDescription(),
367f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan                    timeout);
36898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (Throwable e) {
36998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            if (e instanceof SipException) {
37098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                throw (SipException) e;
37198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            } else {
37298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                throwSipException(e);
37398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
37498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
37598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
37698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
37798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized void endCall() throws SipException {
37898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
37998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            stopRinging();
38098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            stopCall(true);
38192a48830c7e1468155db24aa604efbbbbef8afc7Hung-ying Tyan            mInCall = false;
38292a48830c7e1468155db24aa604efbbbbef8afc7Hung-ying Tyan
38392a48830c7e1468155db24aa604efbbbbef8afc7Hung-ying Tyan            // perform the above local ops first and then network op
38492a48830c7e1468155db24aa604efbbbbef8afc7Hung-ying Tyan            if (mSipSession != null) mSipSession.endCall();
38598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (Throwable e) {
38698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            throwSipException(e);
38798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
38898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
38998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
390f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan    public synchronized void holdCall(int timeout) throws SipException {
39198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (mHold) return;
39298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
393f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan            mSipSession.changeCall(createHoldSessionDescription(), timeout);
39498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            mHold = true;
39598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (Throwable e) {
39698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            throwSipException(e);
39798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
39898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
39998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        AudioGroup audioGroup = getAudioGroup();
40098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
40198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
40298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
403f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan    public synchronized void answerCall(int timeout) throws SipException {
40498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
40598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            stopRinging();
406f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan            mSipSession.answerCall(createAnswerSessionDescription(), timeout);
40798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (Throwable e) {
40898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            Log.e(TAG, "answerCall()", e);
40998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            throwSipException(e);
41098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
41198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
41298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
413f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan    public synchronized void continueCall(int timeout) throws SipException {
41498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (!mHold) return;
41598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
41698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            mHold = false;
417f498e98d2e2910e866ec0728cebbe676dd475d9eHung-ying Tyan            mSipSession.changeCall(createContinueSessionDescription(), timeout);
41898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (Throwable e) {
41998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            throwSipException(e);
42098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
42198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
42298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        AudioGroup audioGroup = getAudioGroup();
42398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL);
42498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
42598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
4266d0ef774a2492b0996ded3a43c300c7f72a94897Chia-chi Yeh    private String createOfferSessionDescription() {
42798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        AudioCodec[] codecs = AudioCodec.getSystemSupportedCodecs();
42898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return createSdpBuilder(true, convert(codecs)).build();
42998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
43098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
4316d0ef774a2492b0996ded3a43c300c7f72a94897Chia-chi Yeh    private String createAnswerSessionDescription() {
43298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
43398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            // choose an acceptable media from mPeerSd to answer
43498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            SdpSessionDescription.AudioCodec codec = getCodec(mPeerSd);
43598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            SdpSessionDescription.Builder sdpBuilder =
43698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    createSdpBuilder(false, codec);
43798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            if (mPeerSd.isSendOnly(AUDIO)) {
43898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                sdpBuilder.addMediaAttribute(AUDIO, "recvonly", (String) null);
43998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            } else if (mPeerSd.isReceiveOnly(AUDIO)) {
44098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                sdpBuilder.addMediaAttribute(AUDIO, "sendonly", (String) null);
44198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
44298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            return sdpBuilder.build();
44398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (SdpException e) {
44498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            throw new RuntimeException(e);
44598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
44698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
44798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
4486d0ef774a2492b0996ded3a43c300c7f72a94897Chia-chi Yeh    private String createHoldSessionDescription() {
44998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
45098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            return createSdpBuilder(false, mCodec)
45198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    .addMediaAttribute(AUDIO, "sendonly", (String) null)
45298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    .build();
45398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (SdpException e) {
45498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            throw new RuntimeException(e);
45598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
45698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
45798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
4589248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang    private void grabWifiHighPerfLock() {
4599248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang        if (mWifiHighPerfLock == null) {
4609248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang            Log.v(TAG, "acquire wifi high perf lock");
4619248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang            mWifiHighPerfLock = ((WifiManager)
4629248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang                    mContext.getSystemService(Context.WIFI_SERVICE))
4639248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang                    .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG);
4649248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang            mWifiHighPerfLock.acquire();
4659248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang        }
4669248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang    }
4679248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang
4689248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang    private void releaseWifiHighPerfLock() {
4699248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang        if (mWifiHighPerfLock != null) {
4709248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang            Log.v(TAG, "release wifi high perf lock");
4719248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang            mWifiHighPerfLock.release();
4729248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang            mWifiHighPerfLock = null;
4739248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang        }
4749248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang    }
4759248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang
4769248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang    private boolean isWifiOn() {
4779248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang        return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
4789248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang    }
4799248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang
4806d0ef774a2492b0996ded3a43c300c7f72a94897Chia-chi Yeh    private String createContinueSessionDescription() {
48198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return createSdpBuilder(true, mCodec).build();
48298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
48398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
48498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private String getMediaDescription(SdpSessionDescription.AudioCodec codec) {
48598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return String.format("%d %s/%d", codec.payloadType, codec.name,
48698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                codec.sampleRate);
48798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
48898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
48998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private long getSessionId() {
49098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (mSessionId < 0) {
49198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            mSessionId = System.currentTimeMillis();
49298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
49398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return mSessionId;
49498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
49598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
49698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private SdpSessionDescription.Builder createSdpBuilder(
49798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            boolean addTelephoneEvent,
49898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            SdpSessionDescription.AudioCodec... codecs) {
49998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        String localIp = getLocalIp();
50098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        SdpSessionDescription.Builder sdpBuilder;
50198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
50298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            long sessionVersion = System.currentTimeMillis();
50398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            sdpBuilder = new SdpSessionDescription.Builder("SIP Call")
50498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    .setOrigin(mLocalProfile, getSessionId(), sessionVersion,
50598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                            SDPKeywords.IN, SDPKeywords.IPV4, localIp)
50698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    .setConnectionInfo(SDPKeywords.IN, SDPKeywords.IPV4,
50798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                            localIp);
50898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            List<Integer> codecIds = new ArrayList<Integer>();
50998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            for (SdpSessionDescription.AudioCodec codec : codecs) {
51098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                codecIds.add(codec.payloadType);
51198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
51298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            if (addTelephoneEvent) codecIds.add(DTMF);
51398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            sdpBuilder.addMedia(AUDIO, getLocalMediaPort(), 1, "RTP/AVP",
51498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    codecIds.toArray(new Integer[codecIds.size()]));
51598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            for (SdpSessionDescription.AudioCodec codec : codecs) {
51698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                sdpBuilder.addMediaAttribute(AUDIO, "rtpmap",
51798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                        getMediaDescription(codec));
51898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
51998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            if (addTelephoneEvent) {
52098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                sdpBuilder.addMediaAttribute(AUDIO, "rtpmap",
52198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                        DTMF + " telephone-event/8000");
52298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
52398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            // FIXME: deal with vbr codec
52498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            sdpBuilder.addMediaAttribute(AUDIO, "ptime", "20");
52598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (SdpException e) {
52698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            throw new RuntimeException(e);
52798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
52898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return sdpBuilder;
52998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
53098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
53198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized void toggleMute() {
53298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        AudioGroup audioGroup = getAudioGroup();
53398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (audioGroup != null) {
53498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            audioGroup.setMode(
53598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    mMuted ? AudioGroup.MODE_NORMAL : AudioGroup.MODE_MUTED);
53698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            mMuted = !mMuted;
53798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
53898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
53998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
54098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized boolean isMuted() {
54198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return mMuted;
54298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
54398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
544b8298544a5e3f292fed1fef8cbb578789f3577ccChung-yih Wang    public synchronized void setSpeakerMode(boolean speakerMode) {
54598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
546b8298544a5e3f292fed1fef8cbb578789f3577ccChung-yih Wang                .setSpeakerphoneOn(speakerMode);
54798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
54898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
54998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public void sendDtmf(int code) {
55098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        sendDtmf(code, null);
55198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
55298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
55398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized void sendDtmf(int code, Message result) {
55498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        AudioGroup audioGroup = getAudioGroup();
55598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if ((audioGroup != null) && (mSipSession != null)
55698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                && (SipSessionState.IN_CALL == getState())) {
55798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            Log.v(TAG, "send DTMF: " + code);
55898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            audioGroup.sendDtmf(code);
55998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
56098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (result != null) result.sendToTarget();
56198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
56298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
56398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized AudioStream getAudioStream() {
5640ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan        return mAudioStream;
56598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
56698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
56798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized AudioGroup getAudioGroup() {
5680ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan        if (mAudioGroup != null) return mAudioGroup;
5690ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan        return ((mAudioStream == null) ? null : mAudioStream.getAudioGroup());
5700ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan    }
5710ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan
5720ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan    public synchronized void setAudioGroup(AudioGroup group) {
5730ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan        if ((mAudioStream != null) && (mAudioStream.getAudioGroup() != null)) {
5740ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan            mAudioStream.join(group);
5750ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan        }
5760ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan        mAudioGroup = group;
57798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
57898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
57998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private SdpSessionDescription.AudioCodec getCodec(SdpSessionDescription sd) {
58098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        HashMap<String, AudioCodec> acceptableCodecs =
58198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                new HashMap<String, AudioCodec>();
58298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        for (AudioCodec codec : AudioCodec.getSystemSupportedCodecs()) {
58398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            acceptableCodecs.put(codec.name, codec);
58498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
58598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        for (SdpSessionDescription.AudioCodec codec : sd.getAudioCodecs()) {
58698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            AudioCodec matchedCodec = acceptableCodecs.get(codec.name);
58798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            if (matchedCodec != null) return codec;
58898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
58998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Log.w(TAG, "no common codec is found, use PCM/0");
59098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return convert(AudioCodec.ULAW);
59198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
59298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
59398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private AudioCodec convert(SdpSessionDescription.AudioCodec codec) {
59498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        AudioCodec c = AudioCodec.getSystemSupportedCodec(codec.name);
59598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return ((c == null) ? AudioCodec.ULAW : c);
59698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
59798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
59898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private SdpSessionDescription.AudioCodec convert(AudioCodec codec) {
59998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return new SdpSessionDescription.AudioCodec(codec.defaultType,
60098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                codec.name, codec.sampleRate, codec.sampleCount);
60198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
60298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
60398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private SdpSessionDescription.AudioCodec[] convert(AudioCodec[] codecs) {
60498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        SdpSessionDescription.AudioCodec[] copies =
60598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                new SdpSessionDescription.AudioCodec[codecs.length];
60698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        for (int i = 0, len = codecs.length; i < len; i++) {
60798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            copies[i] = convert(codecs[i]);
60898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
60998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        return copies;
61098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
61198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
61298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private void startCall(SdpSessionDescription peerSd) {
61398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        stopCall(DONT_RELEASE_SOCKET);
6149248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang        if (isWifiOn()) grabWifiHighPerfLock();
61598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        mPeerSd = peerSd;
61698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        String peerMediaAddress = peerSd.getPeerMediaAddress(AUDIO);
61798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        // TODO: handle multiple media fields
61898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        int peerMediaPort = peerSd.getPeerMediaPort(AUDIO);
61998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Log.i(TAG, "start audiocall " + peerMediaAddress + ":" + peerMediaPort);
62098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
62198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        int localPort = getLocalMediaPort();
62298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        int sampleRate = 8000;
62398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        int frameSize = sampleRate / 50; // 160
62498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
62598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            // TODO: get sample rate from sdp
62698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            mCodec = getCodec(peerSd);
62798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
6280ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan            AudioStream audioStream = mAudioStream;
62998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            audioStream.associate(InetAddress.getByName(peerMediaAddress),
63098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    peerMediaPort);
63198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            audioStream.setCodec(convert(mCodec), mCodec.payloadType);
63298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            audioStream.setDtmfType(DTMF);
63398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            Log.d(TAG, "start media: localPort=" + localPort + ", peer="
63498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    + peerMediaAddress + ":" + peerMediaPort);
63598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
63698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            audioStream.setMode(RtpStream.MODE_NORMAL);
63798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            if (!mHold) {
63898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                // FIXME: won't work if peer is not sending nor receiving
63998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                if (!peerSd.isSending(AUDIO)) {
64098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    Log.d(TAG, "   not receiving");
64198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    audioStream.setMode(RtpStream.MODE_SEND_ONLY);
64298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                }
64398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                if (!peerSd.isReceiving(AUDIO)) {
64498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    Log.d(TAG, "   not sending");
64598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY);
64698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                }
6470ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan
648b8298544a5e3f292fed1fef8cbb578789f3577ccChung-yih Wang                /* The recorder volume will be very low if the device is in
649b8298544a5e3f292fed1fef8cbb578789f3577ccChung-yih Wang                 * IN_CALL mode. Therefore, we have to set the mode to NORMAL
650b8298544a5e3f292fed1fef8cbb578789f3577ccChung-yih Wang                 * in order to have the normal microphone level.
651b8298544a5e3f292fed1fef8cbb578789f3577ccChung-yih Wang                 */
652b8298544a5e3f292fed1fef8cbb578789f3577ccChung-yih Wang                ((AudioManager) mContext.getSystemService
653b8298544a5e3f292fed1fef8cbb578789f3577ccChung-yih Wang                        (Context.AUDIO_SERVICE))
654b8298544a5e3f292fed1fef8cbb578789f3577ccChung-yih Wang                        .setMode(AudioManager.MODE_NORMAL);
65598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
65698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
6570ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan            // AudioGroup logic:
6580ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan            AudioGroup audioGroup = getAudioGroup();
65998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            if (mHold) {
6600ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                if (audioGroup != null) {
6610ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                    audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
6620ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                }
6630ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                // don't create an AudioGroup here; doing so will fail if
6640ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                // there's another AudioGroup out there that's active
66598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            } else {
6660ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                if (audioGroup == null) audioGroup = new AudioGroup();
6670ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                audioStream.join(audioGroup);
6680ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                if (mMuted) {
6690ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                    audioGroup.setMode(AudioGroup.MODE_MUTED);
6700ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                } else {
6710ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                    audioGroup.setMode(AudioGroup.MODE_NORMAL);
6720ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                }
67398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
67498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (Exception e) {
67598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            Log.e(TAG, "call()", e);
67698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
67798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
67898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
67998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private void stopCall(boolean releaseSocket) {
68098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        Log.d(TAG, "stop audiocall");
6819248b2a99ffa82713a8a6d71d0b68667b8f187e7Chung-yih Wang        releaseWifiHighPerfLock();
6820ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan        if (mAudioStream != null) {
6830ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan            mAudioStream.join(null);
68498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
68598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            if (releaseSocket) {
6860ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                mAudioStream.release();
6870ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan                mAudioStream = null;
68898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            }
68998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
69098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
69198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
69298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private int getLocalMediaPort() {
6930ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan        if (mAudioStream != null) return mAudioStream.getLocalPort();
69498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
6950ca131a2c0474ace43678a130eeee0d491a5fac4Hung-ying Tyan            AudioStream s = mAudioStream =
69698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    new AudioStream(InetAddress.getByName(getLocalIp()));
69798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            return s.getLocalPort();
69898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (IOException e) {
69998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            Log.w(TAG, "getLocalMediaPort(): " + e);
70098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            throw new RuntimeException(e);
70198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
70298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
70398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
70498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private String getLocalIp() {
70598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
70698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            return mSipSession.getLocalIp();
70798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (RemoteException e) {
70898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            // FIXME
70998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            return "127.0.0.1";
71098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
71198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
71298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
71398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized void setRingbackToneEnabled(boolean enabled) {
71498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        mRingbackToneEnabled = enabled;
71598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
71698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
71798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    public synchronized void setRingtoneEnabled(boolean enabled) {
71898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        mRingtoneEnabled = enabled;
71998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
72098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
72198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private void startRingbackTone() {
72298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (!mRingbackToneEnabled) return;
72398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (mRingbackTone == null) {
72498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            // The volume relative to other sounds in the stream
72598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            int toneVolume = 80;
72698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            mRingbackTone = new ToneGenerator(
72798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    AudioManager.STREAM_VOICE_CALL, toneVolume);
72898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
72998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        mRingbackTone.startTone(ToneGenerator.TONE_CDMA_LOW_PBX_L);
73098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
73198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
73298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private void stopRingbackTone() {
73398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (mRingbackTone != null) {
73498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            mRingbackTone.stopTone();
73598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            mRingbackTone.release();
73698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            mRingbackTone = null;
73798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
73898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
73998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
74098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private void startRinging() {
74198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (!mRingtoneEnabled) return;
74298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
74398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                .vibrate(new long[] {0, 1000, 1000}, 1);
74498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        AudioManager am = (AudioManager)
74598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                mContext.getSystemService(Context.AUDIO_SERVICE);
74698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (am.getStreamVolume(AudioManager.STREAM_RING) > 0) {
74798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            String ringtoneUri =
74898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    Settings.System.DEFAULT_RINGTONE_URI.toString();
74998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            mRingtone = RingtoneManager.getRingtone(mContext,
75098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                    Uri.parse(ringtoneUri));
75198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            mRingtone.play();
75298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
75398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
75498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
75598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private void stopRinging() {
75698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
75798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang                .cancel();
75898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (mRingtone != null) mRingtone.stop();
75998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
76098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
76198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private void throwSipException(Throwable throwable) throws SipException {
76298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        if (throwable instanceof SipException) {
76398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            throw (SipException) throwable;
76498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } else {
76598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            throw new SipException("", throwable);
76698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
76798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
76898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang
76998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    private SipProfile getPeerProfile(ISipSession session) {
77098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        try {
77198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            return session.getPeerProfile();
77298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        } catch (RemoteException e) {
77398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang            return null;
77498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang        }
77598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang    }
77698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang}
777