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