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