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.app.PendingIntent;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.pm.PackageManager;
     23 import android.os.IBinder;
     24 import android.os.RemoteException;
     25 import android.os.ServiceManager;
     26 import android.telephony.Rlog;
     27 
     28 import java.text.ParseException;
     29 
     30 /**
     31  * Provides APIs for SIP tasks, such as initiating SIP connections, and provides access to related
     32  * SIP services. This class is the starting point for any SIP actions. You can acquire an instance
     33  * of it with {@link #newInstance newInstance()}.</p>
     34  * <p>The APIs in this class allows you to:</p>
     35  * <ul>
     36  * <li>Create a {@link SipSession} to get ready for making calls or listen for incoming calls. See
     37  * {@link #createSipSession createSipSession()} and {@link #getSessionFor getSessionFor()}.</li>
     38  * <li>Initiate and receive generic SIP calls or audio-only SIP calls. Generic SIP calls may
     39  * be video, audio, or other, and are initiated with {@link #open open()}. Audio-only SIP calls
     40  * should be handled with a {@link SipAudioCall}, which you can acquire with {@link
     41  * #makeAudioCall makeAudioCall()} and {@link #takeAudioCall takeAudioCall()}.</li>
     42  * <li>Register and unregister with a SIP service provider, with
     43  *      {@link #register register()} and {@link #unregister unregister()}.</li>
     44  * <li>Verify session connectivity, with {@link #isOpened isOpened()} and
     45  *      {@link #isRegistered isRegistered()}.</li>
     46  * </ul>
     47  * <p class="note"><strong>Note:</strong> Not all Android-powered devices support VOIP calls using
     48  * SIP. You should always call {@link android.net.sip.SipManager#isVoipSupported
     49  * isVoipSupported()} to verify that the device supports VOIP calling and {@link
     50  * android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports
     51  * the SIP APIs. Your application must also request the {@link
     52  * android.Manifest.permission#INTERNET} and {@link android.Manifest.permission#USE_SIP}
     53  * permissions.</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 SipManager {
     63     /**
     64      * The result code to be sent back with the incoming call
     65      * {@link PendingIntent}.
     66      * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
     67      */
     68     public static final int INCOMING_CALL_RESULT_CODE = 101;
     69 
     70     /**
     71      * Key to retrieve the call ID from an incoming call intent.
     72      * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
     73      */
     74     public static final String EXTRA_CALL_ID = "android:sipCallID";
     75 
     76     /**
     77      * Key to retrieve the offered session description from an incoming call
     78      * intent.
     79      * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
     80      */
     81     public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
     82 
     83     /**
     84      * Action to broadcast when SipService is up.
     85      * Internal use only.
     86      * @hide
     87      */
     88     public static final String ACTION_SIP_SERVICE_UP =
     89             "android.net.sip.SIP_SERVICE_UP";
     90     /**
     91      * Action string for the incoming call intent for the Phone app.
     92      * Internal use only.
     93      * @hide
     94      */
     95     public static final String ACTION_SIP_INCOMING_CALL =
     96             "com.android.phone.SIP_INCOMING_CALL";
     97     /**
     98      * Action string for the add-phone intent.
     99      * Internal use only.
    100      * @hide
    101      */
    102     public static final String ACTION_SIP_ADD_PHONE =
    103             "com.android.phone.SIP_ADD_PHONE";
    104     /**
    105      * Action string for the remove-phone intent.
    106      * Internal use only.
    107      * @hide
    108      */
    109     public static final String ACTION_SIP_REMOVE_PHONE =
    110             "com.android.phone.SIP_REMOVE_PHONE";
    111     /**
    112      * Part of the ACTION_SIP_ADD_PHONE and ACTION_SIP_REMOVE_PHONE intents.
    113      * Internal use only.
    114      * @hide
    115      */
    116     public static final String EXTRA_LOCAL_URI = "android:localSipUri";
    117 
    118     private static final String TAG = "SipManager";
    119 
    120     private ISipService mSipService;
    121     private Context mContext;
    122 
    123     /**
    124      * Creates a manager instance. Returns null if SIP API is not supported.
    125      *
    126      * @param context application context for creating the manager object
    127      * @return the manager instance or null if SIP API is not supported
    128      */
    129     public static SipManager newInstance(Context context) {
    130         return (isApiSupported(context) ? new SipManager(context) : null);
    131     }
    132 
    133     /**
    134      * Returns true if the SIP API is supported by the system.
    135      */
    136     public static boolean isApiSupported(Context context) {
    137         return context.getPackageManager().hasSystemFeature(
    138                 PackageManager.FEATURE_SIP);
    139     }
    140 
    141     /**
    142      * Returns true if the system supports SIP-based VOIP API.
    143      */
    144     public static boolean isVoipSupported(Context context) {
    145         return context.getPackageManager().hasSystemFeature(
    146                 PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context);
    147     }
    148 
    149     /**
    150      * Returns true if SIP is only available on WIFI.
    151      */
    152     public static boolean isSipWifiOnly(Context context) {
    153         return context.getResources().getBoolean(
    154                 com.android.internal.R.bool.config_sip_wifi_only);
    155     }
    156 
    157     private SipManager(Context context) {
    158         mContext = context;
    159         createSipService();
    160     }
    161 
    162     private void createSipService() {
    163         IBinder b = ServiceManager.getService(Context.SIP_SERVICE);
    164         mSipService = ISipService.Stub.asInterface(b);
    165     }
    166 
    167     /**
    168      * Opens the profile for making generic SIP calls. The caller may make subsequent calls
    169      * through {@link #makeAudioCall}. If one also wants to receive calls on the
    170      * profile, use
    171      * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}
    172      * instead.
    173      *
    174      * @param localProfile the SIP profile to make calls from
    175      * @throws SipException if the profile contains incorrect settings or
    176      *      calling the SIP service results in an error
    177      */
    178     public void open(SipProfile localProfile) throws SipException {
    179         try {
    180             mSipService.open(localProfile);
    181         } catch (RemoteException e) {
    182             throw new SipException("open()", e);
    183         }
    184     }
    185 
    186     /**
    187      * Opens the profile for making calls and/or receiving generic SIP calls. The caller may
    188      * make subsequent calls through {@link #makeAudioCall}. If the
    189      * auto-registration option is enabled in the profile, the SIP service
    190      * will register the profile to the corresponding SIP provider periodically
    191      * in order to receive calls from the provider. When the SIP service
    192      * receives a new call, it will send out an intent with the provided action
    193      * string. The intent contains a call ID extra and an offer session
    194      * description string extra. Use {@link #getCallId} and
    195      * {@link #getOfferSessionDescription} to retrieve those extras.
    196      *
    197      * @param localProfile the SIP profile to receive incoming calls for
    198      * @param incomingCallPendingIntent When an incoming call is received, the
    199      *      SIP service will call
    200      *      {@link PendingIntent#send(Context, int, Intent)} to send back the
    201      *      intent to the caller with {@link #INCOMING_CALL_RESULT_CODE} as the
    202      *      result code and the intent to fill in the call ID and session
    203      *      description information. It cannot be null.
    204      * @param listener to listen to registration events; can be null
    205      * @see #getCallId
    206      * @see #getOfferSessionDescription
    207      * @see #takeAudioCall
    208      * @throws NullPointerException if {@code incomingCallPendingIntent} is null
    209      * @throws SipException if the profile contains incorrect settings or
    210      *      calling the SIP service results in an error
    211      * @see #isIncomingCallIntent
    212      * @see #getCallId
    213      * @see #getOfferSessionDescription
    214      */
    215     public void open(SipProfile localProfile,
    216             PendingIntent incomingCallPendingIntent,
    217             SipRegistrationListener listener) throws SipException {
    218         if (incomingCallPendingIntent == null) {
    219             throw new NullPointerException(
    220                     "incomingCallPendingIntent cannot be null");
    221         }
    222         try {
    223             mSipService.open3(localProfile, incomingCallPendingIntent,
    224                     createRelay(listener, localProfile.getUriString()));
    225         } catch (RemoteException e) {
    226             throw new SipException("open()", e);
    227         }
    228     }
    229 
    230     /**
    231      * Sets the listener to listen to registration events. No effect if the
    232      * profile has not been opened to receive calls (see
    233      * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}).
    234      *
    235      * @param localProfileUri the URI of the profile
    236      * @param listener to listen to registration events; can be null
    237      * @throws SipException if calling the SIP service results in an error
    238      */
    239     public void setRegistrationListener(String localProfileUri,
    240             SipRegistrationListener listener) throws SipException {
    241         try {
    242             mSipService.setRegistrationListener(
    243                     localProfileUri, createRelay(listener, localProfileUri));
    244         } catch (RemoteException e) {
    245             throw new SipException("setRegistrationListener()", e);
    246         }
    247     }
    248 
    249     /**
    250      * Closes the specified profile to not make/receive calls. All the resources
    251      * that were allocated to the profile are also released.
    252      *
    253      * @param localProfileUri the URI of the profile to close
    254      * @throws SipException if calling the SIP service results in an error
    255      */
    256     public void close(String localProfileUri) throws SipException {
    257         try {
    258             mSipService.close(localProfileUri);
    259         } catch (RemoteException e) {
    260             throw new SipException("close()", e);
    261         }
    262     }
    263 
    264     /**
    265      * Checks if the specified profile is opened in the SIP service for
    266      * making and/or receiving calls.
    267      *
    268      * @param localProfileUri the URI of the profile in question
    269      * @return true if the profile is enabled to receive calls
    270      * @throws SipException if calling the SIP service results in an error
    271      */
    272     public boolean isOpened(String localProfileUri) throws SipException {
    273         try {
    274             return mSipService.isOpened(localProfileUri);
    275         } catch (RemoteException e) {
    276             throw new SipException("isOpened()", e);
    277         }
    278     }
    279 
    280     /**
    281      * Checks if the SIP service has successfully registered the profile to the
    282      * SIP provider (specified in the profile) for receiving calls. Returning
    283      * true from this method also implies the profile is opened
    284      * ({@link #isOpened}).
    285      *
    286      * @param localProfileUri the URI of the profile in question
    287      * @return true if the profile is registered to the SIP provider; false if
    288      *        the profile has not been opened in the SIP service or the SIP
    289      *        service has not yet successfully registered the profile to the SIP
    290      *        provider
    291      * @throws SipException if calling the SIP service results in an error
    292      */
    293     public boolean isRegistered(String localProfileUri) throws SipException {
    294         try {
    295             return mSipService.isRegistered(localProfileUri);
    296         } catch (RemoteException e) {
    297             throw new SipException("isRegistered()", e);
    298         }
    299     }
    300 
    301     /**
    302      * Creates a {@link SipAudioCall} to make a call. The attempt will be timed
    303      * out if the call is not established within {@code timeout} seconds and
    304      * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
    305      * will be called.
    306      *
    307      * @param localProfile the SIP profile to make the call from
    308      * @param peerProfile the SIP profile to make the call to
    309      * @param listener to listen to the call events from {@link SipAudioCall};
    310      *      can be null
    311      * @param timeout the timeout value in seconds. Default value (defined by
    312      *        SIP protocol) is used if {@code timeout} is zero or negative.
    313      * @return a {@link SipAudioCall} object
    314      * @throws SipException if calling the SIP service results in an error or
    315      *      VOIP API is not supported by the device
    316      * @see SipAudioCall.Listener#onError
    317      * @see #isVoipSupported
    318      */
    319     public SipAudioCall makeAudioCall(SipProfile localProfile,
    320             SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
    321             throws SipException {
    322         if (!isVoipSupported(mContext)) {
    323             throw new SipException("VOIP API is not supported");
    324         }
    325         SipAudioCall call = new SipAudioCall(mContext, localProfile);
    326         call.setListener(listener);
    327         SipSession s = createSipSession(localProfile, null);
    328         call.makeCall(peerProfile, s, timeout);
    329         return call;
    330     }
    331 
    332     /**
    333      * Creates a {@link SipAudioCall} to make an audio call. The attempt will be
    334      * timed out if the call is not established within {@code timeout} seconds
    335      * and
    336      * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
    337      * will be called.
    338      *
    339      * @param localProfileUri URI of the SIP profile to make the call from
    340      * @param peerProfileUri URI of the SIP profile to make the call to
    341      * @param listener to listen to the call events from {@link SipAudioCall};
    342      *      can be null
    343      * @param timeout the timeout value in seconds. Default value (defined by
    344      *        SIP protocol) is used if {@code timeout} is zero or negative.
    345      * @return a {@link SipAudioCall} object
    346      * @throws SipException if calling the SIP service results in an error or
    347      *      VOIP API is not supported by the device
    348      * @see SipAudioCall.Listener#onError
    349      * @see #isVoipSupported
    350      */
    351     public SipAudioCall makeAudioCall(String localProfileUri,
    352             String peerProfileUri, SipAudioCall.Listener listener, int timeout)
    353             throws SipException {
    354         if (!isVoipSupported(mContext)) {
    355             throw new SipException("VOIP API is not supported");
    356         }
    357         try {
    358             return makeAudioCall(
    359                     new SipProfile.Builder(localProfileUri).build(),
    360                     new SipProfile.Builder(peerProfileUri).build(), listener,
    361                     timeout);
    362         } catch (ParseException e) {
    363             throw new SipException("build SipProfile", e);
    364         }
    365     }
    366 
    367     /**
    368      * Creates a {@link SipAudioCall} to take an incoming call. Before the call
    369      * is returned, the listener will receive a
    370      * {@link SipAudioCall.Listener#onRinging}
    371      * callback.
    372      *
    373      * @param incomingCallIntent the incoming call broadcast intent
    374      * @param listener to listen to the call events from {@link SipAudioCall};
    375      *      can be null
    376      * @return a {@link SipAudioCall} object
    377      * @throws SipException if calling the SIP service results in an error
    378      */
    379     public SipAudioCall takeAudioCall(Intent incomingCallIntent,
    380             SipAudioCall.Listener listener) throws SipException {
    381         if (incomingCallIntent == null) {
    382             throw new SipException("Cannot retrieve session with null intent");
    383         }
    384 
    385         String callId = getCallId(incomingCallIntent);
    386         if (callId == null) {
    387             throw new SipException("Call ID missing in incoming call intent");
    388         }
    389 
    390         String offerSd = getOfferSessionDescription(incomingCallIntent);
    391         if (offerSd == null) {
    392             throw new SipException("Session description missing in incoming "
    393                     + "call intent");
    394         }
    395 
    396         try {
    397             ISipSession session = mSipService.getPendingSession(callId);
    398             if (session == null) {
    399                 throw new SipException("No pending session for the call");
    400             }
    401             SipAudioCall call = new SipAudioCall(
    402                     mContext, session.getLocalProfile());
    403             call.attachCall(new SipSession(session), offerSd);
    404             call.setListener(listener);
    405             return call;
    406         } catch (Throwable t) {
    407             throw new SipException("takeAudioCall()", t);
    408         }
    409     }
    410 
    411     /**
    412      * Checks if the intent is an incoming call broadcast intent.
    413      *
    414      * @param intent the intent in question
    415      * @return true if the intent is an incoming call broadcast intent
    416      */
    417     public static boolean isIncomingCallIntent(Intent intent) {
    418         if (intent == null) return false;
    419         String callId = getCallId(intent);
    420         String offerSd = getOfferSessionDescription(intent);
    421         return ((callId != null) && (offerSd != null));
    422     }
    423 
    424     /**
    425      * Gets the call ID from the specified incoming call broadcast intent.
    426      *
    427      * @param incomingCallIntent the incoming call broadcast intent
    428      * @return the call ID or null if the intent does not contain it
    429      */
    430     public static String getCallId(Intent incomingCallIntent) {
    431         return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
    432     }
    433 
    434     /**
    435      * Gets the offer session description from the specified incoming call
    436      * broadcast intent.
    437      *
    438      * @param incomingCallIntent the incoming call broadcast intent
    439      * @return the offer session description or null if the intent does not
    440      *      have it
    441      */
    442     public static String getOfferSessionDescription(Intent incomingCallIntent) {
    443         return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD);
    444     }
    445 
    446     /**
    447      * Creates an incoming call broadcast intent.
    448      *
    449      * @param callId the call ID of the incoming call
    450      * @param sessionDescription the session description of the incoming call
    451      * @return the incoming call intent
    452      * @hide
    453      */
    454     public static Intent createIncomingCallBroadcast(String callId,
    455             String sessionDescription) {
    456         Intent intent = new Intent();
    457         intent.putExtra(EXTRA_CALL_ID, callId);
    458         intent.putExtra(EXTRA_OFFER_SD, sessionDescription);
    459         return intent;
    460     }
    461 
    462     /**
    463      * Manually registers the profile to the corresponding SIP provider for
    464      * receiving calls.
    465      * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is
    466      * still needed to be called at least once in order for the SIP service to
    467      * notify the caller with the {@link android.app.PendingIntent} when an incoming call is
    468      * received.
    469      *
    470      * @param localProfile the SIP profile to register with
    471      * @param expiryTime registration expiration time (in seconds)
    472      * @param listener to listen to the registration events
    473      * @throws SipException if calling the SIP service results in an error
    474      */
    475     public void register(SipProfile localProfile, int expiryTime,
    476             SipRegistrationListener listener) throws SipException {
    477         try {
    478             ISipSession session = mSipService.createSession(localProfile,
    479                     createRelay(listener, localProfile.getUriString()));
    480             if (session == null) {
    481                 throw new SipException(
    482                         "SipService.createSession() returns null");
    483             }
    484             session.register(expiryTime);
    485         } catch (RemoteException e) {
    486             throw new SipException("register()", e);
    487         }
    488     }
    489 
    490     /**
    491      * Manually unregisters the profile from the corresponding SIP provider for
    492      * stop receiving further calls. This may interference with the auto
    493      * registration process in the SIP service if the auto-registration option
    494      * in the profile is enabled.
    495      *
    496      * @param localProfile the SIP profile to register with
    497      * @param listener to listen to the registration events
    498      * @throws SipException if calling the SIP service results in an error
    499      */
    500     public void unregister(SipProfile localProfile,
    501             SipRegistrationListener listener) throws SipException {
    502         try {
    503             ISipSession session = mSipService.createSession(localProfile,
    504                     createRelay(listener, localProfile.getUriString()));
    505             if (session == null) {
    506                 throw new SipException(
    507                         "SipService.createSession() returns null");
    508             }
    509             session.unregister();
    510         } catch (RemoteException e) {
    511             throw new SipException("unregister()", e);
    512         }
    513     }
    514 
    515     /**
    516      * Gets the {@link SipSession} that handles the incoming call. For audio
    517      * calls, consider to use {@link SipAudioCall} to handle the incoming call.
    518      * See {@link #takeAudioCall}. Note that the method may be called only once
    519      * for the same intent. For subsequent calls on the same intent, the method
    520      * returns null.
    521      *
    522      * @param incomingCallIntent the incoming call broadcast intent
    523      * @return the session object that handles the incoming call
    524      */
    525     public SipSession getSessionFor(Intent incomingCallIntent)
    526             throws SipException {
    527         try {
    528             String callId = getCallId(incomingCallIntent);
    529             ISipSession s = mSipService.getPendingSession(callId);
    530             return ((s == null) ? null : new SipSession(s));
    531         } catch (RemoteException e) {
    532             throw new SipException("getSessionFor()", e);
    533         }
    534     }
    535 
    536     private static ISipSessionListener createRelay(
    537             SipRegistrationListener listener, String uri) {
    538         return ((listener == null) ? null : new ListenerRelay(listener, uri));
    539     }
    540 
    541     /**
    542      * Creates a {@link SipSession} with the specified profile. Use other
    543      * methods, if applicable, instead of interacting with {@link SipSession}
    544      * directly.
    545      *
    546      * @param localProfile the SIP profile the session is associated with
    547      * @param listener to listen to SIP session events
    548      */
    549     public SipSession createSipSession(SipProfile localProfile,
    550             SipSession.Listener listener) throws SipException {
    551         try {
    552             ISipSession s = mSipService.createSession(localProfile, null);
    553             if (s == null) {
    554                 throw new SipException(
    555                         "Failed to create SipSession; network unavailable?");
    556             }
    557             return new SipSession(s, listener);
    558         } catch (RemoteException e) {
    559             throw new SipException("createSipSession()", e);
    560         }
    561     }
    562 
    563     /**
    564      * Gets the list of profiles hosted by the SIP service. The user information
    565      * (username, password and display name) are crossed out.
    566      * @hide
    567      */
    568     public SipProfile[] getListOfProfiles() {
    569         try {
    570             return mSipService.getListOfProfiles();
    571         } catch (RemoteException e) {
    572             return new SipProfile[0];
    573         }
    574     }
    575 
    576     private static class ListenerRelay extends SipSessionAdapter {
    577         private SipRegistrationListener mListener;
    578         private String mUri;
    579 
    580         // listener must not be null
    581         public ListenerRelay(SipRegistrationListener listener, String uri) {
    582             mListener = listener;
    583             mUri = uri;
    584         }
    585 
    586         private String getUri(ISipSession session) {
    587             try {
    588                 return ((session == null)
    589                         ? mUri
    590                         : session.getLocalProfile().getUriString());
    591             } catch (Throwable e) {
    592                 // SipService died? SIP stack died?
    593                 Rlog.e(TAG, "getUri(): ", e);
    594                 return null;
    595             }
    596         }
    597 
    598         @Override
    599         public void onRegistering(ISipSession session) {
    600             mListener.onRegistering(getUri(session));
    601         }
    602 
    603         @Override
    604         public void onRegistrationDone(ISipSession session, int duration) {
    605             long expiryTime = duration;
    606             if (duration > 0) expiryTime += System.currentTimeMillis();
    607             mListener.onRegistrationDone(getUri(session), expiryTime);
    608         }
    609 
    610         @Override
    611         public void onRegistrationFailed(ISipSession session, int errorCode,
    612                 String message) {
    613             mListener.onRegistrationFailed(getUri(session), errorCode, message);
    614         }
    615 
    616         @Override
    617         public void onRegistrationTimeout(ISipSession session) {
    618             mListener.onRegistrationFailed(getUri(session),
    619                     SipErrorCode.TIME_OUT, "registration timed out");
    620         }
    621     }
    622 }
    623