After this change, SipAudioCallImpl is the only place still using it. Change-Id: I5693bffa54f9e19cbfa70b45dfcf40fba04dedbb
510 lines
20 KiB
Java
510 lines
20 KiB
Java
/*
|
|
* Copyright (C) 2010 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.net.sip;
|
|
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageManager;
|
|
import android.os.IBinder;
|
|
import android.os.Looper;
|
|
import android.os.RemoteException;
|
|
import android.os.ServiceManager;
|
|
|
|
import java.text.ParseException;
|
|
import javax.sip.SipException;
|
|
|
|
/**
|
|
* The class provides API for various SIP related tasks. Specifically, the API
|
|
* allows the application to:
|
|
* <ul>
|
|
* <li>register a {@link SipProfile} to have the background SIP service listen
|
|
* to incoming calls and broadcast them with registered command string. See
|
|
* {@link #open(SipProfile, String, SipRegistrationListener)},
|
|
* {@link #open(SipProfile)}, {@link #close(String)},
|
|
* {@link #isOpened(String)} and {@link isRegistered(String)}. It also
|
|
* facilitates handling of the incoming call broadcast intent. See
|
|
* {@link #isIncomingCallIntent(Intent)}, {@link #getCallId(Intent)},
|
|
* {@link #getOfferSessionDescription(Intent)} and
|
|
* {@link #takeAudioCall(Context, Intent, SipAudioCall.Listener)}.</li>
|
|
* <li>make/take SIP-based audio calls. See
|
|
* {@link #makeAudioCall(Context, SipProfile, SipProfile, SipAudioCall.Listener)}
|
|
* and {@link #takeAudioCall(Context, Intent, SipAudioCall.Listener}.</li>
|
|
* <li>register/unregister with a SIP service provider. See
|
|
* {@link #register(SipProfile, int, ISipSessionListener)} and
|
|
* {@link #unregister(SipProfile, ISipSessionListener)}.</li>
|
|
* <li>process SIP events directly with a {@link ISipSession} created by
|
|
* {@link createSipSession(SipProfile, ISipSessionListener)}.</li>
|
|
* </ul>
|
|
* @hide
|
|
*/
|
|
public class SipManager {
|
|
/** @hide */
|
|
public static final String SIP_INCOMING_CALL_ACTION =
|
|
"com.android.phone.SIP_INCOMING_CALL";
|
|
/** @hide */
|
|
public static final String SIP_ADD_PHONE_ACTION =
|
|
"com.android.phone.SIP_ADD_PHONE";
|
|
/** @hide */
|
|
public static final String SIP_REMOVE_PHONE_ACTION =
|
|
"com.android.phone.SIP_REMOVE_PHONE";
|
|
/** @hide */
|
|
public static final String LOCAL_URI_KEY = "LOCAL SIPURI";
|
|
|
|
private static final String CALL_ID_KEY = "CallID";
|
|
private static final String OFFER_SD_KEY = "OfferSD";
|
|
|
|
private ISipService mSipService;
|
|
|
|
/**
|
|
* Gets a manager instance. Returns null if SIP API is not supported.
|
|
*
|
|
* @param context application context for checking if SIP API is supported
|
|
* @return the manager instance or null if SIP API is not supported
|
|
*/
|
|
public static SipManager getInstance(Context context) {
|
|
return (isApiSupported(context) ? new SipManager() : null);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the SIP API is supported by the system.
|
|
*/
|
|
public static boolean isApiSupported(Context context) {
|
|
return context.getPackageManager().hasSystemFeature(
|
|
PackageManager.FEATURE_SIP);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the system supports SIP-based VoIP.
|
|
*/
|
|
public static boolean isVoipSupported(Context context) {
|
|
return context.getPackageManager().hasSystemFeature(
|
|
PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context);
|
|
}
|
|
|
|
private SipManager() {
|
|
createSipService();
|
|
}
|
|
|
|
private void createSipService() {
|
|
if (mSipService != null) return;
|
|
IBinder b = ServiceManager.getService(Context.SIP_SERVICE);
|
|
mSipService = ISipService.Stub.asInterface(b);
|
|
}
|
|
|
|
/**
|
|
* Opens the profile for making calls and/or receiving calls. Subsequent
|
|
* SIP calls can be made through the default phone UI. The caller may also
|
|
* make subsequent calls through
|
|
* {@link #makeAudioCall(Context, String, String, SipAudioCall.Listener)}.
|
|
* If the receiving-call option is enabled in the profile, the SIP service
|
|
* will register the profile to the corresponding server periodically in
|
|
* order to receive calls from the server.
|
|
*
|
|
* @param localProfile the SIP profile to make calls from
|
|
* @throws SipException if the profile contains incorrect settings or
|
|
* calling the SIP service results in an error
|
|
*/
|
|
public void open(SipProfile localProfile) throws SipException {
|
|
try {
|
|
mSipService.open(localProfile);
|
|
} catch (RemoteException e) {
|
|
throw new SipException("open()", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Opens the profile for making calls and/or receiving calls. Subsequent
|
|
* SIP calls can be made through the default phone UI. The caller may also
|
|
* make subsequent calls through
|
|
* {@link #makeAudioCall(Context, String, String, SipAudioCall.Listener)}.
|
|
* If the receiving-call option is enabled in the profile, the SIP service
|
|
* will register the profile to the corresponding server periodically in
|
|
* order to receive calls from the server.
|
|
*
|
|
* @param localProfile the SIP profile to receive incoming calls for
|
|
* @param incomingCallBroadcastAction the action to be broadcast when an
|
|
* incoming call is received
|
|
* @param listener to listen to registration events; can be null
|
|
* @throws SipException if the profile contains incorrect settings or
|
|
* calling the SIP service results in an error
|
|
*/
|
|
public void open(SipProfile localProfile,
|
|
String incomingCallBroadcastAction,
|
|
SipRegistrationListener listener) throws SipException {
|
|
try {
|
|
mSipService.open3(localProfile, incomingCallBroadcastAction,
|
|
createRelay(listener));
|
|
} catch (RemoteException e) {
|
|
throw new SipException("open()", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the listener to listen to registration events. No effect if the
|
|
* profile has not been opened to receive calls
|
|
* (see {@link #open(SipProfile, String, SipRegistrationListener)} and
|
|
* {@link #open(SipProfile)}).
|
|
*
|
|
* @param localProfileUri the URI of the profile
|
|
* @param listener to listen to registration events; can be null
|
|
* @throws SipException if calling the SIP service results in an error
|
|
*/
|
|
public void setRegistrationListener(String localProfileUri,
|
|
SipRegistrationListener listener) throws SipException {
|
|
try {
|
|
mSipService.setRegistrationListener(
|
|
localProfileUri, createRelay(listener));
|
|
} catch (RemoteException e) {
|
|
throw new SipException("setRegistrationListener()", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Closes the specified profile to not make/receive calls. All the resources
|
|
* that were allocated to the profile are also released.
|
|
*
|
|
* @param localProfileUri the URI of the profile to close
|
|
* @throws SipException if calling the SIP service results in an error
|
|
*/
|
|
public void close(String localProfileUri) throws SipException {
|
|
try {
|
|
mSipService.close(localProfileUri);
|
|
} catch (RemoteException e) {
|
|
throw new SipException("close()", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if the specified profile is enabled to receive calls.
|
|
*
|
|
* @param localProfileUri the URI of the profile in question
|
|
* @return true if the profile is enabled to receive calls
|
|
* @throws SipException if calling the SIP service results in an error
|
|
*/
|
|
public boolean isOpened(String localProfileUri) throws SipException {
|
|
try {
|
|
return mSipService.isOpened(localProfileUri);
|
|
} catch (RemoteException e) {
|
|
throw new SipException("isOpened()", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if the specified profile is registered to the server for
|
|
* receiving calls.
|
|
*
|
|
* @param localProfileUri the URI of the profile in question
|
|
* @return true if the profile is registered to the server
|
|
* @throws SipException if calling the SIP service results in an error
|
|
*/
|
|
public boolean isRegistered(String localProfileUri) throws SipException {
|
|
try {
|
|
return mSipService.isRegistered(localProfileUri);
|
|
} catch (RemoteException e) {
|
|
throw new SipException("isRegistered()", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link SipAudioCall} to make a call.
|
|
*
|
|
* @param context context to create a {@link SipAudioCall} object
|
|
* @param localProfile the SIP profile to make the call from
|
|
* @param peerProfile the SIP profile to make the call to
|
|
* @param listener to listen to the call events from {@link SipAudioCall};
|
|
* can be null
|
|
* @return a {@link SipAudioCall} object
|
|
* @throws SipException if calling the SIP service results in an error
|
|
*/
|
|
public SipAudioCall makeAudioCall(Context context, SipProfile localProfile,
|
|
SipProfile peerProfile, SipAudioCall.Listener listener)
|
|
throws SipException {
|
|
SipAudioCall call = new SipAudioCallImpl(context, localProfile);
|
|
call.setListener(listener);
|
|
call.makeCall(peerProfile, this);
|
|
return call;
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link SipAudioCall} to make a call. To use this method, one
|
|
* must call {@link #open(SipProfile)} first.
|
|
*
|
|
* @param context context to create a {@link SipAudioCall} object
|
|
* @param localProfileUri URI of the SIP profile to make the call from
|
|
* @param peerProfileUri URI of the SIP profile to make the call to
|
|
* @param listener to listen to the call events from {@link SipAudioCall};
|
|
* can be null
|
|
* @return a {@link SipAudioCall} object
|
|
* @throws SipException if calling the SIP service results in an error
|
|
*/
|
|
public SipAudioCall makeAudioCall(Context context, String localProfileUri,
|
|
String peerProfileUri, SipAudioCall.Listener listener)
|
|
throws SipException {
|
|
try {
|
|
return makeAudioCall(context,
|
|
new SipProfile.Builder(localProfileUri).build(),
|
|
new SipProfile.Builder(peerProfileUri).build(), listener);
|
|
} catch (ParseException e) {
|
|
throw new SipException("build SipProfile", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The method calls {@code takeAudioCall(context, incomingCallIntent,
|
|
* listener, true}.
|
|
*
|
|
* @see #takeAudioCall(Context, Intent, SipAudioCall.Listener, boolean)
|
|
*/
|
|
public SipAudioCall takeAudioCall(Context context,
|
|
Intent incomingCallIntent, SipAudioCall.Listener listener)
|
|
throws SipException {
|
|
return takeAudioCall(context, incomingCallIntent, listener, true);
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link SipAudioCall} to take an incoming call. Before the call
|
|
* is returned, the listener will receive a
|
|
* {@link SipAudioCall#Listener.onRinging(SipAudioCall, SipProfile)}
|
|
* callback.
|
|
*
|
|
* @param context context to create a {@link SipAudioCall} object
|
|
* @param incomingCallIntent the incoming call broadcast intent
|
|
* @param listener to listen to the call events from {@link SipAudioCall};
|
|
* can be null
|
|
* @return a {@link SipAudioCall} object
|
|
* @throws SipException if calling the SIP service results in an error
|
|
*/
|
|
public SipAudioCall takeAudioCall(Context context,
|
|
Intent incomingCallIntent, SipAudioCall.Listener listener,
|
|
boolean ringtoneEnabled) throws SipException {
|
|
if (incomingCallIntent == null) return null;
|
|
|
|
String callId = getCallId(incomingCallIntent);
|
|
if (callId == null) {
|
|
throw new SipException("Call ID missing in incoming call intent");
|
|
}
|
|
|
|
String offerSd = getOfferSessionDescription(incomingCallIntent);
|
|
if (offerSd == null) {
|
|
throw new SipException("Session description missing in incoming "
|
|
+ "call intent");
|
|
}
|
|
|
|
try {
|
|
ISipSession session = mSipService.getPendingSession(callId);
|
|
if (session == null) return null;
|
|
SipAudioCall call = new SipAudioCallImpl(
|
|
context, session.getLocalProfile());
|
|
call.setRingtoneEnabled(ringtoneEnabled);
|
|
call.attachCall(session, offerSd);
|
|
call.setListener(listener);
|
|
return call;
|
|
} catch (Throwable t) {
|
|
throw new SipException("takeAudioCall()", t);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if the intent is an incoming call broadcast intent.
|
|
*
|
|
* @param intent the intent in question
|
|
* @return true if the intent is an incoming call broadcast intent
|
|
*/
|
|
public static boolean isIncomingCallIntent(Intent intent) {
|
|
if (intent == null) return false;
|
|
String callId = getCallId(intent);
|
|
String offerSd = getOfferSessionDescription(intent);
|
|
return ((callId != null) && (offerSd != null));
|
|
}
|
|
|
|
/**
|
|
* Gets the call ID from the specified incoming call broadcast intent.
|
|
*
|
|
* @param incomingCallIntent the incoming call broadcast intent
|
|
* @return the call ID or null if the intent does not contain it
|
|
*/
|
|
public static String getCallId(Intent incomingCallIntent) {
|
|
return incomingCallIntent.getStringExtra(CALL_ID_KEY);
|
|
}
|
|
|
|
/**
|
|
* Gets the offer session description from the specified incoming call
|
|
* broadcast intent.
|
|
*
|
|
* @param incomingCallIntent the incoming call broadcast intent
|
|
* @return the offer session description or null if the intent does not
|
|
* have it
|
|
*/
|
|
public static String getOfferSessionDescription(Intent incomingCallIntent) {
|
|
return incomingCallIntent.getStringExtra(OFFER_SD_KEY);
|
|
}
|
|
|
|
/**
|
|
* Creates an incoming call broadcast intent.
|
|
*
|
|
* @param action the action string to broadcast
|
|
* @param callId the call ID of the incoming call
|
|
* @param sessionDescription the session description of the incoming call
|
|
* @return the incoming call intent
|
|
* @hide
|
|
*/
|
|
public static Intent createIncomingCallBroadcast(String action,
|
|
String callId, String sessionDescription) {
|
|
Intent intent = new Intent(action);
|
|
intent.putExtra(CALL_ID_KEY, callId);
|
|
intent.putExtra(OFFER_SD_KEY, sessionDescription);
|
|
return intent;
|
|
}
|
|
|
|
/**
|
|
* Registers the profile to the corresponding server for receiving calls.
|
|
* {@link #open(SipProfile, String, SipRegistrationListener)} is still
|
|
* needed to be called at least once in order for the SIP service to
|
|
* broadcast an intent when an incoming call is received.
|
|
*
|
|
* @param localProfile the SIP profile to register with
|
|
* @param expiryTime registration expiration time (in second)
|
|
* @param listener to listen to the registration events
|
|
* @throws SipException if calling the SIP service results in an error
|
|
*/
|
|
public void register(SipProfile localProfile, int expiryTime,
|
|
SipRegistrationListener listener) throws SipException {
|
|
try {
|
|
ISipSession session = mSipService.createSession(
|
|
localProfile, createRelay(listener));
|
|
session.register(expiryTime);
|
|
} catch (RemoteException e) {
|
|
throw new SipException("register()", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unregisters the profile from the corresponding server for not receiving
|
|
* further calls.
|
|
*
|
|
* @param localProfile the SIP profile to register with
|
|
* @param listener to listen to the registration events
|
|
* @throws SipException if calling the SIP service results in an error
|
|
*/
|
|
public void unregister(SipProfile localProfile,
|
|
SipRegistrationListener listener) throws SipException {
|
|
try {
|
|
ISipSession session = mSipService.createSession(
|
|
localProfile, createRelay(listener));
|
|
session.unregister();
|
|
} catch (RemoteException e) {
|
|
throw new SipException("unregister()", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the {@link ISipSession} that handles the incoming call. For audio
|
|
* calls, consider to use {@link SipAudioCall} to handle the incoming call.
|
|
* See {@link #takeAudioCall(Context, Intent, SipAudioCall.Listener)}.
|
|
* Note that the method may be called only once for the same intent. For
|
|
* subsequent calls on the same intent, the method returns null.
|
|
*
|
|
* @param incomingCallIntent the incoming call broadcast intent
|
|
* @return the session object that handles the incoming call
|
|
*/
|
|
public ISipSession getSessionFor(Intent incomingCallIntent)
|
|
throws SipException {
|
|
try {
|
|
String callId = getCallId(incomingCallIntent);
|
|
return mSipService.getPendingSession(callId);
|
|
} catch (RemoteException e) {
|
|
throw new SipException("getSessionFor()", e);
|
|
}
|
|
}
|
|
|
|
private static ISipSessionListener createRelay(
|
|
SipRegistrationListener listener) {
|
|
return ((listener == null) ? null : new ListenerRelay(listener));
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link ISipSession} with the specified profile. Use other
|
|
* methods, if applicable, instead of interacting with {@link ISipSession}
|
|
* directly.
|
|
*
|
|
* @param localProfile the SIP profile the session is associated with
|
|
* @param listener to listen to SIP session events
|
|
*/
|
|
public ISipSession createSipSession(SipProfile localProfile,
|
|
ISipSessionListener listener) throws SipException {
|
|
try {
|
|
return mSipService.createSession(localProfile, listener);
|
|
} catch (RemoteException e) {
|
|
throw new SipException("createSipSession()", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the list of profiles hosted by the SIP service. The user information
|
|
* (username, password and display name) are crossed out.
|
|
* @hide
|
|
*/
|
|
public SipProfile[] getListOfProfiles() {
|
|
try {
|
|
return mSipService.getListOfProfiles();
|
|
} catch (RemoteException e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static class ListenerRelay extends SipSessionAdapter {
|
|
private SipRegistrationListener mListener;
|
|
|
|
// listener must not be null
|
|
public ListenerRelay(SipRegistrationListener listener) {
|
|
mListener = listener;
|
|
}
|
|
|
|
private String getUri(ISipSession session) {
|
|
try {
|
|
return session.getLocalProfile().getUriString();
|
|
} catch (RemoteException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onRegistering(ISipSession session) {
|
|
mListener.onRegistering(getUri(session));
|
|
}
|
|
|
|
@Override
|
|
public void onRegistrationDone(ISipSession session, int duration) {
|
|
long expiryTime = duration;
|
|
if (duration > 0) expiryTime += System.currentTimeMillis();
|
|
mListener.onRegistrationDone(getUri(session), expiryTime);
|
|
}
|
|
|
|
@Override
|
|
public void onRegistrationFailed(ISipSession session, String className,
|
|
String message) {
|
|
mListener.onRegistrationFailed(getUri(session), className, message);
|
|
}
|
|
|
|
@Override
|
|
public void onRegistrationTimeout(ISipSession session) {
|
|
mListener.onRegistrationFailed(getUri(session),
|
|
SipException.class.getName(), "registration timed out");
|
|
}
|
|
}
|
|
}
|