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