Merge commit 'bbd1a04c628f6756085797423794020ccde70c5c' * commit 'bbd1a04c628f6756085797423794020ccde70c5c': Move the sip related codes to framework.
This commit is contained in:
56
voip/java/android/net/rtp/AudioCodec.java
Normal file
56
voip/java/android/net/rtp/AudioCodec.java
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.rtp;
|
||||
|
||||
/** @hide */
|
||||
public class AudioCodec {
|
||||
public static final AudioCodec ULAW = new AudioCodec("PCMU", 8000, 160, 0);
|
||||
public static final AudioCodec ALAW = new AudioCodec("PCMA", 8000, 160, 8);
|
||||
|
||||
/**
|
||||
* Returns system supported codecs.
|
||||
*/
|
||||
public static AudioCodec[] getSystemSupportedCodecs() {
|
||||
return new AudioCodec[] {AudioCodec.ULAW, AudioCodec.ALAW};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codec instance if it is supported by the system.
|
||||
*
|
||||
* @param name name of the codec
|
||||
* @return the matched codec or null if the codec name is not supported by
|
||||
* the system
|
||||
*/
|
||||
public static AudioCodec getSystemSupportedCodec(String name) {
|
||||
for (AudioCodec codec : getSystemSupportedCodecs()) {
|
||||
if (codec.name.equals(name)) return codec;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public final String name;
|
||||
public final int sampleRate;
|
||||
public final int sampleCount;
|
||||
public final int defaultType;
|
||||
|
||||
private AudioCodec(String name, int sampleRate, int sampleCount, int defaultType) {
|
||||
this.name = name;
|
||||
this.sampleRate = sampleRate;
|
||||
this.sampleCount = sampleCount;
|
||||
this.defaultType = defaultType;
|
||||
}
|
||||
}
|
||||
91
voip/java/android/net/rtp/AudioGroup.java
Normal file
91
voip/java/android/net/rtp/AudioGroup.java
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.rtp;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*/
|
||||
/** @hide */
|
||||
public class AudioGroup {
|
||||
public static final int MODE_ON_HOLD = 0;
|
||||
public static final int MODE_MUTED = 1;
|
||||
public static final int MODE_NORMAL = 2;
|
||||
public static final int MODE_EC_ENABLED = 3;
|
||||
|
||||
private final Map<AudioStream, Integer> mStreams;
|
||||
private int mMode = MODE_ON_HOLD;
|
||||
|
||||
private int mNative;
|
||||
static {
|
||||
System.loadLibrary("rtp_jni");
|
||||
}
|
||||
|
||||
public AudioGroup() {
|
||||
mStreams = new HashMap<AudioStream, Integer>();
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return mMode;
|
||||
}
|
||||
|
||||
public synchronized native void setMode(int mode);
|
||||
|
||||
synchronized void add(AudioStream stream, AudioCodec codec, int codecType, int dtmfType) {
|
||||
if (!mStreams.containsKey(stream)) {
|
||||
try {
|
||||
int id = add(stream.getMode(), stream.dup(),
|
||||
stream.getRemoteAddress().getHostAddress(), stream.getRemotePort(),
|
||||
codec.name, codec.sampleRate, codec.sampleCount, codecType, dtmfType);
|
||||
mStreams.put(stream, id);
|
||||
} catch (NullPointerException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private native int add(int mode, int socket, String remoteAddress, int remotePort,
|
||||
String codecName, int sampleRate, int sampleCount, int codecType, int dtmfType);
|
||||
|
||||
synchronized void remove(AudioStream stream) {
|
||||
Integer id = mStreams.remove(stream);
|
||||
if (id != null) {
|
||||
remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
private native void remove(int id);
|
||||
|
||||
/**
|
||||
* Sends a DTMF digit to every {@link AudioStream} in this group. Currently
|
||||
* only event {@code 0} to {@code 15} are supported.
|
||||
*
|
||||
* @throws IllegalArgumentException if the event is invalid.
|
||||
*/
|
||||
public native synchronized void sendDtmf(int event);
|
||||
|
||||
public synchronized void reset() {
|
||||
remove(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
reset();
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
136
voip/java/android/net/rtp/AudioStream.java
Normal file
136
voip/java/android/net/rtp/AudioStream.java
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.rtp;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
|
||||
/**
|
||||
* AudioStream represents a RTP stream carrying audio payloads.
|
||||
*/
|
||||
/** @hide */
|
||||
public class AudioStream extends RtpStream {
|
||||
private AudioCodec mCodec;
|
||||
private int mCodecType = -1;
|
||||
private int mDtmfType = -1;
|
||||
private AudioGroup mGroup;
|
||||
|
||||
/**
|
||||
* Creates an AudioStream on the given local address. Note that the local
|
||||
* port is assigned automatically to conform with RFC 3550.
|
||||
*
|
||||
* @param address The network address of the local host to bind to.
|
||||
* @throws SocketException if the address cannot be bound or a problem
|
||||
* occurs during binding.
|
||||
*/
|
||||
public AudioStream(InetAddress address) throws SocketException {
|
||||
super(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the stream already joined an {@link AudioGroup}.
|
||||
*/
|
||||
@Override
|
||||
public final boolean isBusy() {
|
||||
return mGroup != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the joined {@link AudioGroup}.
|
||||
*/
|
||||
public AudioGroup getAudioGroup() {
|
||||
return mGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins an {@link AudioGroup}. Each stream can join only one group at a
|
||||
* time. The group can be changed by passing a different one or removed
|
||||
* by calling this method with {@code null}.
|
||||
*
|
||||
* @param group The AudioGroup to join or {@code null} to leave.
|
||||
* @throws IllegalStateException if the stream is not properly configured.
|
||||
* @see AudioGroup
|
||||
*/
|
||||
public void join(AudioGroup group) {
|
||||
if (mGroup == group) {
|
||||
return;
|
||||
}
|
||||
if (mGroup != null) {
|
||||
mGroup.remove(this);
|
||||
mGroup = null;
|
||||
}
|
||||
if (group != null) {
|
||||
group.add(this, mCodec, mCodecType, mDtmfType);
|
||||
mGroup = group;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AudioCodec} and its RTP payload type. According to RFC
|
||||
* 3551, the type must be in the range of 0 and 127, where 96 and above are
|
||||
* dynamic types. For codecs with static mappings (non-negative
|
||||
* {@link AudioCodec#defaultType}), assigning a different non-dynamic type
|
||||
* is disallowed.
|
||||
*
|
||||
* @param codec The AudioCodec to be used.
|
||||
* @param type The RTP payload type.
|
||||
* @throws IllegalArgumentException if the type is invalid or used by DTMF.
|
||||
* @throws IllegalStateException if the stream is busy.
|
||||
*/
|
||||
public void setCodec(AudioCodec codec, int type) {
|
||||
if (isBusy()) {
|
||||
throw new IllegalStateException("Busy");
|
||||
}
|
||||
if (type < 0 || type > 127 || (type != codec.defaultType && type < 96)) {
|
||||
throw new IllegalArgumentException("Invalid type");
|
||||
}
|
||||
if (type == mDtmfType) {
|
||||
throw new IllegalArgumentException("The type is used by DTMF");
|
||||
}
|
||||
mCodec = codec;
|
||||
mCodecType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the RTP payload type for dual-tone multi-frequency (DTMF) digits.
|
||||
* The primary usage is to send digits to the remote gateway to perform
|
||||
* certain tasks, such as second-stage dialing. According to RFC 2833, the
|
||||
* RTP payload type for DTMF is assigned dynamically, so it must be in the
|
||||
* range of 96 and 127. One can use {@code -1} to disable DTMF and free up
|
||||
* the previous assigned value. This method cannot be called when the stream
|
||||
* already joined an {@link AudioGroup}.
|
||||
*
|
||||
* @param type The RTP payload type to be used or {@code -1} to disable it.
|
||||
* @throws IllegalArgumentException if the type is invalid or used by codec.
|
||||
* @throws IllegalStateException if the stream is busy.
|
||||
* @see AudioGroup#sendDtmf(int)
|
||||
*/
|
||||
public void setDtmfType(int type) {
|
||||
if (isBusy()) {
|
||||
throw new IllegalStateException("Busy");
|
||||
}
|
||||
if (type != -1) {
|
||||
if (type < 96 || type > 127) {
|
||||
throw new IllegalArgumentException("Invalid type");
|
||||
}
|
||||
if (type == mCodecType) {
|
||||
throw new IllegalArgumentException("The type is used by codec");
|
||||
}
|
||||
}
|
||||
mDtmfType = type;
|
||||
}
|
||||
}
|
||||
173
voip/java/android/net/rtp/RtpStream.java
Normal file
173
voip/java/android/net/rtp/RtpStream.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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.rtp;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.SocketException;
|
||||
|
||||
/**
|
||||
* RtpStream represents a base class of media streams running over
|
||||
* Real-time Transport Protocol (RTP).
|
||||
*/
|
||||
/** @hide */
|
||||
public class RtpStream {
|
||||
public static final int MODE_NORMAL = 0;
|
||||
public static final int MODE_SEND_ONLY = 1;
|
||||
public static final int MODE_RECEIVE_ONLY = 2;
|
||||
|
||||
private final InetAddress mLocalAddress;
|
||||
private final int mLocalPort;
|
||||
|
||||
private InetAddress mRemoteAddress;
|
||||
private int mRemotePort = -1;
|
||||
private int mMode = MODE_NORMAL;
|
||||
|
||||
private int mNative;
|
||||
static {
|
||||
System.loadLibrary("rtp_jni");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a RtpStream on the given local address. Note that the local
|
||||
* port is assigned automatically to conform with RFC 3550.
|
||||
*
|
||||
* @param address The network address of the local host to bind to.
|
||||
* @throws SocketException if the address cannot be bound or a problem
|
||||
* occurs during binding.
|
||||
*/
|
||||
RtpStream(InetAddress address) throws SocketException {
|
||||
mLocalPort = create(address.getHostAddress());
|
||||
mLocalAddress = address;
|
||||
}
|
||||
|
||||
private native int create(String address) throws SocketException;
|
||||
|
||||
/**
|
||||
* Returns the network address of the local host.
|
||||
*/
|
||||
public InetAddress getLocalAddress() {
|
||||
return mLocalAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the network port of the local host.
|
||||
*/
|
||||
public int getLocalPort() {
|
||||
return mLocalPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the network address of the remote host or {@code null} if the
|
||||
* stream is not associated.
|
||||
*/
|
||||
public InetAddress getRemoteAddress() {
|
||||
return mRemoteAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the network port of the remote host or {@code -1} if the stream
|
||||
* is not associated.
|
||||
*/
|
||||
public int getRemotePort() {
|
||||
return mRemotePort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the stream is busy. This method is intended to be
|
||||
* overridden by subclasses.
|
||||
*/
|
||||
public boolean isBusy() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current mode. The initial mode is {@link #MODE_NORMAL}.
|
||||
*/
|
||||
public int getMode() {
|
||||
return mMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the current mode. It must be one of {@link #MODE_NORMAL},
|
||||
* {@link #MODE_SEND_ONLY}, and {@link #MODE_RECEIVE_ONLY}.
|
||||
*
|
||||
* @param mode The mode to change to.
|
||||
* @throws IllegalArgumentException if the mode is invalid.
|
||||
* @throws IllegalStateException if the stream is busy.
|
||||
* @see #isBusy()
|
||||
*/
|
||||
public void setMode(int mode) {
|
||||
if (isBusy()) {
|
||||
throw new IllegalStateException("Busy");
|
||||
}
|
||||
if (mode != MODE_NORMAL && mode != MODE_SEND_ONLY && mode != MODE_RECEIVE_ONLY) {
|
||||
throw new IllegalArgumentException("Invalid mode");
|
||||
}
|
||||
mMode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates with a remote host.
|
||||
*
|
||||
* @param address The network address of the remote host.
|
||||
* @param port The network port of the remote host.
|
||||
* @throws IllegalArgumentException if the address is not supported or the
|
||||
* port is invalid.
|
||||
* @throws IllegalStateException if the stream is busy.
|
||||
* @see #isBusy()
|
||||
*/
|
||||
public void associate(InetAddress address, int port) {
|
||||
if (isBusy()) {
|
||||
throw new IllegalStateException("Busy");
|
||||
}
|
||||
if (!(address instanceof Inet4Address && mLocalAddress instanceof Inet4Address) &&
|
||||
!(address instanceof Inet6Address && mLocalAddress instanceof Inet6Address)) {
|
||||
throw new IllegalArgumentException("Unsupported address");
|
||||
}
|
||||
if (port < 0 || port > 65535) {
|
||||
throw new IllegalArgumentException("Invalid port");
|
||||
}
|
||||
mRemoteAddress = address;
|
||||
mRemotePort = port;
|
||||
}
|
||||
|
||||
synchronized native int dup();
|
||||
|
||||
/**
|
||||
* Releases allocated resources. The stream becomes inoperable after calling
|
||||
* this method.
|
||||
*
|
||||
* @throws IllegalStateException if the stream is busy.
|
||||
* @see #isBusy()
|
||||
*/
|
||||
public void release() {
|
||||
if (isBusy()) {
|
||||
throw new IllegalStateException("Busy");
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
private synchronized native void close();
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
close();
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
79
voip/java/android/net/sip/BinderHelper.java
Normal file
79
voip/java/android/net/sip/BinderHelper.java
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.IBinder;
|
||||
import android.os.IInterface;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
// TODO: throw away this class after moving SIP classes to framework
|
||||
// This class helps to get IBinder instance of a service in a blocking call.
|
||||
// The method cannot be called in app's main thread as the ServiceConnection
|
||||
// callback will.
|
||||
class BinderHelper<T extends IInterface> {
|
||||
private Context mContext;
|
||||
private IBinder mBinder;
|
||||
private Class<T> mClass;
|
||||
|
||||
BinderHelper(Context context, Class<T> klass) {
|
||||
mContext = context;
|
||||
mClass = klass;
|
||||
}
|
||||
|
||||
void startService() {
|
||||
mContext.startService(new Intent(mClass.getName()));
|
||||
}
|
||||
|
||||
void stopService() {
|
||||
mContext.stopService(new Intent(mClass.getName()));
|
||||
}
|
||||
|
||||
IBinder getBinder() {
|
||||
// cannot call this method in app's main thread
|
||||
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
|
||||
throw new RuntimeException(
|
||||
"This method cannot be called in app's main thread");
|
||||
}
|
||||
|
||||
final ConditionVariable cv = new ConditionVariable();
|
||||
cv.close();
|
||||
ServiceConnection c = new ServiceConnection() {
|
||||
public synchronized void onServiceConnected(
|
||||
ComponentName className, IBinder binder) {
|
||||
Log.v("BinderHelper", "service connected!");
|
||||
mBinder = binder;
|
||||
cv.open();
|
||||
mContext.unbindService(this);
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
cv.open();
|
||||
mContext.unbindService(this);
|
||||
}
|
||||
};
|
||||
if (mContext.bindService(new Intent(mClass.getName()), c, 0)) {
|
||||
cv.block(4500);
|
||||
}
|
||||
return mBinder;
|
||||
}
|
||||
}
|
||||
42
voip/java/android/net/sip/ISipService.aidl
Normal file
42
voip/java/android/net/sip/ISipService.aidl
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.net.sip.ISipSession;
|
||||
import android.net.sip.ISipSessionListener;
|
||||
import android.net.sip.SipProfile;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
interface ISipService {
|
||||
void open(in SipProfile localProfile);
|
||||
void open3(in SipProfile localProfile,
|
||||
String incomingCallBroadcastAction,
|
||||
in ISipSessionListener listener);
|
||||
void close(in String localProfileUri);
|
||||
boolean isOpened(String localProfileUri);
|
||||
boolean isRegistered(String localProfileUri);
|
||||
void setRegistrationListener(String localProfileUri,
|
||||
ISipSessionListener listener);
|
||||
|
||||
ISipSession createSession(in SipProfile localProfile,
|
||||
in ISipSessionListener listener);
|
||||
ISipSession getPendingSession(String callId);
|
||||
|
||||
SipProfile[] getListOfProfiles();
|
||||
}
|
||||
147
voip/java/android/net/sip/ISipSession.aidl
Normal file
147
voip/java/android/net/sip/ISipSession.aidl
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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.net.sip.ISipSessionListener;
|
||||
import android.net.sip.SessionDescription;
|
||||
import android.net.sip.SipProfile;
|
||||
|
||||
/**
|
||||
* A SIP session that is associated with a SIP dialog or a transaction
|
||||
* (e.g., registration) not within a dialog.
|
||||
* @hide
|
||||
*/
|
||||
interface ISipSession {
|
||||
/**
|
||||
* Gets the IP address of the local host on which this SIP session runs.
|
||||
*
|
||||
* @return the IP address of the local host
|
||||
*/
|
||||
String getLocalIp();
|
||||
|
||||
/**
|
||||
* Gets the SIP profile that this session is associated with.
|
||||
*
|
||||
* @return the SIP profile that this session is associated with
|
||||
*/
|
||||
SipProfile getLocalProfile();
|
||||
|
||||
/**
|
||||
* Gets the SIP profile that this session is connected to. Only available
|
||||
* when the session is associated with a SIP dialog.
|
||||
*
|
||||
* @return the SIP profile that this session is connected to
|
||||
*/
|
||||
SipProfile getPeerProfile();
|
||||
|
||||
/**
|
||||
* Gets the session state. The value returned must be one of the states in
|
||||
* {@link SipSessionState}. One may convert it to {@link SipSessionState} by
|
||||
* <code>
|
||||
* Enum.valueOf(SipSessionState.class, session.getState());
|
||||
* </code>
|
||||
*
|
||||
* @return the session state
|
||||
*/
|
||||
String getState();
|
||||
|
||||
/**
|
||||
* Checks if the session is in a call.
|
||||
*
|
||||
* @return true if the session is in a call
|
||||
*/
|
||||
boolean isInCall();
|
||||
|
||||
/**
|
||||
* Gets the call ID of the session.
|
||||
*
|
||||
* @return the call ID
|
||||
*/
|
||||
String getCallId();
|
||||
|
||||
|
||||
/**
|
||||
* Sets the listener to listen to the session events. A {@link ISipSession}
|
||||
* can only hold one listener at a time. Subsequent calls to this method
|
||||
* override the previous listener.
|
||||
*
|
||||
* @param listener to listen to the session events of this object
|
||||
*/
|
||||
void setListener(in ISipSessionListener listener);
|
||||
|
||||
|
||||
/**
|
||||
* Performs registration to the server specified by the associated local
|
||||
* profile. The session listener is called back upon success or failure of
|
||||
* registration. The method is only valid to call when the session state is
|
||||
* in {@link SipSessionState#READY_TO_CALL}.
|
||||
*
|
||||
* @param duration duration in second before the registration expires
|
||||
* @see ISipSessionListener
|
||||
*/
|
||||
void register(int duration);
|
||||
|
||||
/**
|
||||
* Performs unregistration to the server specified by the associated local
|
||||
* profile. Unregistration is technically the same as registration with zero
|
||||
* expiration duration. The session listener is called back upon success or
|
||||
* failure of unregistration. The method is only valid to call when the
|
||||
* session state is in {@link SipSessionState#READY_TO_CALL}.
|
||||
*
|
||||
* @see ISipSessionListener
|
||||
*/
|
||||
void unregister();
|
||||
|
||||
/**
|
||||
* Initiates a call to the specified profile. The session listener is called
|
||||
* back upon defined session events. The method is only valid to call when
|
||||
* the session state is in {@link SipSessionState#READY_TO_CALL}.
|
||||
*
|
||||
* @param callee the SIP profile to make the call to
|
||||
* @param sessionDescription the session description of this call
|
||||
* @see ISipSessionListener
|
||||
*/
|
||||
void makeCall(in SipProfile callee,
|
||||
in SessionDescription sessionDescription);
|
||||
|
||||
/**
|
||||
* Answers an incoming call with the specified session description. The
|
||||
* method is only valid to call when the session state is in
|
||||
* {@link SipSessionState#INCOMING_CALL}.
|
||||
*
|
||||
* @param sessionDescription the session description to answer this call
|
||||
*/
|
||||
void answerCall(in SessionDescription sessionDescription);
|
||||
|
||||
/**
|
||||
* Ends an established call, terminates an outgoing call or rejects an
|
||||
* incoming call. The method is only valid to call when the session state is
|
||||
* in {@link SipSessionState#IN_CALL},
|
||||
* {@link SipSessionState#INCOMING_CALL},
|
||||
* {@link SipSessionState#OUTGOING_CALL} or
|
||||
* {@link SipSessionState#OUTGOING_CALL_RING_BACK}.
|
||||
*/
|
||||
void endCall();
|
||||
|
||||
/**
|
||||
* Changes the session description during a call. The method is only valid
|
||||
* to call when the session state is in {@link SipSessionState#IN_CALL}.
|
||||
*
|
||||
* @param sessionDescription the new session description
|
||||
*/
|
||||
void changeCall(in SessionDescription sessionDescription);
|
||||
}
|
||||
126
voip/java/android/net/sip/ISipSessionListener.aidl
Normal file
126
voip/java/android/net/sip/ISipSessionListener.aidl
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.net.sip.ISipSession;
|
||||
import android.net.sip.SipProfile;
|
||||
|
||||
/**
|
||||
* Listener class to listen to SIP session events.
|
||||
* @hide
|
||||
*/
|
||||
interface ISipSessionListener {
|
||||
/**
|
||||
* Called when an INVITE request is sent to initiate a new call.
|
||||
*
|
||||
* @param session the session object that carries out the transaction
|
||||
*/
|
||||
void onCalling(in ISipSession session);
|
||||
|
||||
/**
|
||||
* Called when an INVITE request is received.
|
||||
*
|
||||
* @param session the session object that carries out the transaction
|
||||
* @param caller the SIP profile of the caller
|
||||
* @param sessionDescription the caller's session description
|
||||
*/
|
||||
void onRinging(in ISipSession session, in SipProfile caller,
|
||||
in byte[] sessionDescription);
|
||||
|
||||
/**
|
||||
* Called when a RINGING response is received for the INVITE request sent
|
||||
*
|
||||
* @param session the session object that carries out the transaction
|
||||
*/
|
||||
void onRingingBack(in ISipSession session);
|
||||
|
||||
/**
|
||||
* Called when the session is established.
|
||||
*
|
||||
* @param session the session object that is associated with the dialog
|
||||
* @param sessionDescription the peer's session description
|
||||
*/
|
||||
void onCallEstablished(in ISipSession session,
|
||||
in byte[] sessionDescription);
|
||||
|
||||
/**
|
||||
* Called when the session is terminated.
|
||||
*
|
||||
* @param session the session object that is associated with the dialog
|
||||
*/
|
||||
void onCallEnded(in ISipSession session);
|
||||
|
||||
/**
|
||||
* Called when the peer is busy during session initialization.
|
||||
*
|
||||
* @param session the session object that carries out the transaction
|
||||
*/
|
||||
void onCallBusy(in ISipSession session);
|
||||
|
||||
/**
|
||||
* Called when an error occurs during session initialization and
|
||||
* termination.
|
||||
*
|
||||
* @param session the session object that carries out the transaction
|
||||
* @param errorClass name of the exception class
|
||||
* @param errorMessage error message
|
||||
*/
|
||||
void onError(in ISipSession session, String errorClass,
|
||||
String errorMessage);
|
||||
|
||||
/**
|
||||
* Called when an error occurs during session modification negotiation.
|
||||
*
|
||||
* @param session the session object that carries out the transaction
|
||||
* @param errorClass name of the exception class
|
||||
* @param errorMessage error message
|
||||
*/
|
||||
void onCallChangeFailed(in ISipSession session, String errorClass,
|
||||
String errorMessage);
|
||||
|
||||
/**
|
||||
* Called when a registration request is sent.
|
||||
*
|
||||
* @param session the session object that carries out the transaction
|
||||
*/
|
||||
void onRegistering(in ISipSession session);
|
||||
|
||||
/**
|
||||
* Called when registration is successfully done.
|
||||
*
|
||||
* @param session the session object that carries out the transaction
|
||||
* @param duration duration in second before the registration expires
|
||||
*/
|
||||
void onRegistrationDone(in ISipSession session, int duration);
|
||||
|
||||
/**
|
||||
* Called when the registration fails.
|
||||
*
|
||||
* @param session the session object that carries out the transaction
|
||||
* @param errorClass name of the exception class
|
||||
* @param errorMessage error message
|
||||
*/
|
||||
void onRegistrationFailed(in ISipSession session, String errorClass,
|
||||
String errorMessage);
|
||||
|
||||
/**
|
||||
* Called when the registration gets timed out.
|
||||
*
|
||||
* @param session the session object that carries out the transaction
|
||||
*/
|
||||
void onRegistrationTimeout(in ISipSession session);
|
||||
}
|
||||
428
voip/java/android/net/sip/SdpSessionDescription.java
Normal file
428
voip/java/android/net/sip/SdpSessionDescription.java
Normal file
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
* 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 gov.nist.javax.sdp.SessionDescriptionImpl;
|
||||
import gov.nist.javax.sdp.fields.AttributeField;
|
||||
import gov.nist.javax.sdp.fields.ConnectionField;
|
||||
import gov.nist.javax.sdp.fields.MediaField;
|
||||
import gov.nist.javax.sdp.fields.OriginField;
|
||||
import gov.nist.javax.sdp.fields.ProtoVersionField;
|
||||
import gov.nist.javax.sdp.fields.SessionNameField;
|
||||
import gov.nist.javax.sdp.fields.TimeField;
|
||||
import gov.nist.javax.sdp.parser.SDPAnnounceParser;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import javax.sdp.Connection;
|
||||
import javax.sdp.MediaDescription;
|
||||
import javax.sdp.SdpException;
|
||||
|
||||
/**
|
||||
* A session description that follows SDP (Session Description Protocol).
|
||||
* Refer to <a href="http://tools.ietf.org/html/rfc4566">RFC 4566</a>.
|
||||
* @hide
|
||||
*/
|
||||
public class SdpSessionDescription extends SessionDescription {
|
||||
private static final String TAG = "SDP";
|
||||
private static final String AUDIO = "audio";
|
||||
private static final String RTPMAP = "rtpmap";
|
||||
private static final String PTIME = "ptime";
|
||||
private static final String SENDONLY = "sendonly";
|
||||
private static final String RECVONLY = "recvonly";
|
||||
private static final String INACTIVE = "inactive";
|
||||
|
||||
private SessionDescriptionImpl mSessionDescription;
|
||||
|
||||
/**
|
||||
* The audio codec information parsed from "rtpmap".
|
||||
*/
|
||||
public static class AudioCodec {
|
||||
public final int payloadType;
|
||||
public final String name;
|
||||
public final int sampleRate;
|
||||
public final int sampleCount;
|
||||
|
||||
public AudioCodec(int payloadType, String name, int sampleRate,
|
||||
int sampleCount) {
|
||||
this.payloadType = payloadType;
|
||||
this.name = name;
|
||||
this.sampleRate = sampleRate;
|
||||
this.sampleCount = sampleCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The builder class used to create an {@link SdpSessionDescription} object.
|
||||
*/
|
||||
public static class Builder {
|
||||
private SdpSessionDescription mSdp = new SdpSessionDescription();
|
||||
private SessionDescriptionImpl mSessionDescription;
|
||||
|
||||
public Builder(String sessionName) throws SdpException {
|
||||
mSessionDescription = new SessionDescriptionImpl();
|
||||
mSdp.mSessionDescription = mSessionDescription;
|
||||
try {
|
||||
ProtoVersionField proto = new ProtoVersionField();
|
||||
proto.setVersion(0);
|
||||
mSessionDescription.addField(proto);
|
||||
|
||||
TimeField time = new TimeField();
|
||||
time.setZero();
|
||||
mSessionDescription.addField(time);
|
||||
|
||||
SessionNameField session = new SessionNameField();
|
||||
session.setValue(sessionName);
|
||||
mSessionDescription.addField(session);
|
||||
} catch (Exception e) {
|
||||
throwSdpException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder setConnectionInfo(String networkType, String addressType,
|
||||
String addr) throws SdpException {
|
||||
try {
|
||||
ConnectionField connection = new ConnectionField();
|
||||
connection.setNetworkType(networkType);
|
||||
connection.setAddressType(addressType);
|
||||
connection.setAddress(addr);
|
||||
mSessionDescription.addField(connection);
|
||||
} catch (Exception e) {
|
||||
throwSdpException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOrigin(SipProfile user, long sessionId,
|
||||
long sessionVersion, String networkType, String addressType,
|
||||
String address) throws SdpException {
|
||||
try {
|
||||
OriginField origin = new OriginField();
|
||||
origin.setUsername(user.getUserName());
|
||||
origin.setSessionId(sessionId);
|
||||
origin.setSessionVersion(sessionVersion);
|
||||
origin.setAddressType(addressType);
|
||||
origin.setNetworkType(networkType);
|
||||
origin.setAddress(address);
|
||||
mSessionDescription.addField(origin);
|
||||
} catch (Exception e) {
|
||||
throwSdpException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addMedia(String media, int port, int numPorts,
|
||||
String transport, Integer... types) throws SdpException {
|
||||
MediaField field = new MediaField();
|
||||
Vector<Integer> typeVector = new Vector<Integer>();
|
||||
Collections.addAll(typeVector, types);
|
||||
try {
|
||||
field.setMediaType(media);
|
||||
field.setMediaPort(port);
|
||||
field.setPortCount(numPorts);
|
||||
field.setProtocol(transport);
|
||||
field.setMediaFormats(typeVector);
|
||||
mSessionDescription.addField(field);
|
||||
} catch (Exception e) {
|
||||
throwSdpException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addMediaAttribute(String type, String name, String value)
|
||||
throws SdpException {
|
||||
try {
|
||||
MediaDescription md = mSdp.getMediaDescription(type);
|
||||
if (md == null) {
|
||||
throw new SdpException("Should add media first!");
|
||||
}
|
||||
AttributeField attribute = new AttributeField();
|
||||
attribute.setName(name);
|
||||
attribute.setValueAllowNull(value);
|
||||
mSessionDescription.addField(attribute);
|
||||
} catch (Exception e) {
|
||||
throwSdpException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addSessionAttribute(String name, String value)
|
||||
throws SdpException {
|
||||
try {
|
||||
AttributeField attribute = new AttributeField();
|
||||
attribute.setName(name);
|
||||
attribute.setValueAllowNull(value);
|
||||
mSessionDescription.addField(attribute);
|
||||
} catch (Exception e) {
|
||||
throwSdpException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void throwSdpException(Exception e) throws SdpException {
|
||||
if (e instanceof SdpException) {
|
||||
throw (SdpException) e;
|
||||
} else {
|
||||
throw new SdpException(e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public SdpSessionDescription build() {
|
||||
return mSdp;
|
||||
}
|
||||
}
|
||||
|
||||
private SdpSessionDescription() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param sdpString an SDP session description to parse
|
||||
*/
|
||||
public SdpSessionDescription(String sdpString) throws SdpException {
|
||||
try {
|
||||
mSessionDescription = new SDPAnnounceParser(sdpString).parse();
|
||||
} catch (ParseException e) {
|
||||
throw new SdpException(e.toString(), e);
|
||||
}
|
||||
verify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param content a raw SDP session description to parse
|
||||
*/
|
||||
public SdpSessionDescription(byte[] content) throws SdpException {
|
||||
this(new String(content));
|
||||
}
|
||||
|
||||
private void verify() throws SdpException {
|
||||
// make sure the syntax is correct over the fields we're interested in
|
||||
Vector<MediaDescription> descriptions = (Vector<MediaDescription>)
|
||||
mSessionDescription.getMediaDescriptions(false);
|
||||
for (MediaDescription md : descriptions) {
|
||||
md.getMedia().getMediaPort();
|
||||
Connection connection = md.getConnection();
|
||||
if (connection != null) connection.getAddress();
|
||||
md.getMedia().getFormats();
|
||||
}
|
||||
Connection connection = mSessionDescription.getConnection();
|
||||
if (connection != null) connection.getAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connection address of the media.
|
||||
*
|
||||
* @param type the media type; e.g., "AUDIO"
|
||||
* @return the media connection address of the peer
|
||||
*/
|
||||
public String getPeerMediaAddress(String type) {
|
||||
try {
|
||||
MediaDescription md = getMediaDescription(type);
|
||||
Connection connection = md.getConnection();
|
||||
if (connection == null) {
|
||||
connection = mSessionDescription.getConnection();
|
||||
}
|
||||
return ((connection == null) ? null : connection.getAddress());
|
||||
} catch (SdpException e) {
|
||||
// should not occur
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connection port number of the media.
|
||||
*
|
||||
* @param type the media type; e.g., "AUDIO"
|
||||
* @return the media connection port number of the peer
|
||||
*/
|
||||
public int getPeerMediaPort(String type) {
|
||||
try {
|
||||
MediaDescription md = getMediaDescription(type);
|
||||
return md.getMedia().getMediaPort();
|
||||
} catch (SdpException e) {
|
||||
// should not occur
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsAttribute(String type, String name) {
|
||||
if (name == null) return false;
|
||||
MediaDescription md = getMediaDescription(type);
|
||||
Vector<AttributeField> v = (Vector<AttributeField>)
|
||||
md.getAttributeFields();
|
||||
for (AttributeField field : v) {
|
||||
if (name.equals(field.getAttribute().getName())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the media is "sendonly".
|
||||
*
|
||||
* @param type the media type; e.g., "AUDIO"
|
||||
* @return true if the media is "sendonly"
|
||||
*/
|
||||
public boolean isSendOnly(String type) {
|
||||
boolean answer = containsAttribute(type, SENDONLY);
|
||||
Log.d(TAG, " sendonly? " + answer);
|
||||
return answer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the media is "recvonly".
|
||||
*
|
||||
* @param type the media type; e.g., "AUDIO"
|
||||
* @return true if the media is "recvonly"
|
||||
*/
|
||||
public boolean isReceiveOnly(String type) {
|
||||
boolean answer = containsAttribute(type, RECVONLY);
|
||||
Log.d(TAG, " recvonly? " + answer);
|
||||
return answer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the media is in sending; i.e., not "recvonly" and not
|
||||
* "inactive".
|
||||
*
|
||||
* @param type the media type; e.g., "AUDIO"
|
||||
* @return true if the media is sending
|
||||
*/
|
||||
public boolean isSending(String type) {
|
||||
boolean answer = !containsAttribute(type, RECVONLY)
|
||||
&& !containsAttribute(type, INACTIVE);
|
||||
|
||||
Log.d(TAG, " sending? " + answer);
|
||||
return answer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the media is in receiving; i.e., not "sendonly" and not
|
||||
* "inactive".
|
||||
*
|
||||
* @param type the media type; e.g., "AUDIO"
|
||||
* @return true if the media is receiving
|
||||
*/
|
||||
public boolean isReceiving(String type) {
|
||||
boolean answer = !containsAttribute(type, SENDONLY)
|
||||
&& !containsAttribute(type, INACTIVE);
|
||||
Log.d(TAG, " receiving? " + answer);
|
||||
return answer;
|
||||
}
|
||||
|
||||
private AudioCodec parseAudioCodec(String rtpmap, int ptime) {
|
||||
String[] ss = rtpmap.split(" ");
|
||||
int payloadType = Integer.parseInt(ss[0]);
|
||||
|
||||
ss = ss[1].split("/");
|
||||
String name = ss[0];
|
||||
int sampleRate = Integer.parseInt(ss[1]);
|
||||
int channelCount = 1;
|
||||
if (ss.length > 2) channelCount = Integer.parseInt(ss[2]);
|
||||
int sampleCount = sampleRate / (1000 / ptime) * channelCount;
|
||||
return new AudioCodec(payloadType, name, sampleRate, sampleCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of audio codecs in this session description.
|
||||
*
|
||||
* @return the list of audio codecs in this session description
|
||||
*/
|
||||
public List<AudioCodec> getAudioCodecs() {
|
||||
MediaDescription md = getMediaDescription(AUDIO);
|
||||
if (md == null) return new ArrayList<AudioCodec>();
|
||||
|
||||
// FIXME: what happens if ptime is missing
|
||||
int ptime = 20;
|
||||
try {
|
||||
String value = md.getAttribute(PTIME);
|
||||
if (value != null) ptime = Integer.parseInt(value);
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, "getCodecs(): ignored: " + t);
|
||||
}
|
||||
|
||||
List<AudioCodec> codecs = new ArrayList<AudioCodec>();
|
||||
Vector<AttributeField> v = (Vector<AttributeField>)
|
||||
md.getAttributeFields();
|
||||
for (AttributeField field : v) {
|
||||
try {
|
||||
if (RTPMAP.equals(field.getName())) {
|
||||
AudioCodec codec = parseAudioCodec(field.getValue(), ptime);
|
||||
if (codec != null) codecs.add(codec);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, "getCodecs(): ignored: " + t);
|
||||
}
|
||||
}
|
||||
return codecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the media description of the specified type.
|
||||
*
|
||||
* @param type the media type; e.g., "AUDIO"
|
||||
* @return the media description of the specified type
|
||||
*/
|
||||
public MediaDescription getMediaDescription(String type) {
|
||||
MediaDescription[] all = getMediaDescriptions();
|
||||
if ((all == null) || (all.length == 0)) return null;
|
||||
for (MediaDescription md : all) {
|
||||
String t = md.getMedia().getMedia();
|
||||
if (t.equalsIgnoreCase(type)) return md;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the media descriptions in this session description.
|
||||
*
|
||||
* @return all the media descriptions in this session description
|
||||
*/
|
||||
public MediaDescription[] getMediaDescriptions() {
|
||||
try {
|
||||
Vector<MediaDescription> descriptions = (Vector<MediaDescription>)
|
||||
mSessionDescription.getMediaDescriptions(false);
|
||||
MediaDescription[] all = new MediaDescription[descriptions.size()];
|
||||
return descriptions.toArray(all);
|
||||
} catch (SdpException e) {
|
||||
Log.e(TAG, "getMediaDescriptions", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return "sdp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent() {
|
||||
return mSessionDescription.toString().getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mSessionDescription.toString();
|
||||
}
|
||||
}
|
||||
19
voip/java/android/net/sip/SessionDescription.aidl
Normal file
19
voip/java/android/net/sip/SessionDescription.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
parcelable SessionDescription;
|
||||
83
voip/java/android/net/sip/SessionDescription.java
Normal file
83
voip/java/android/net/sip/SessionDescription.java
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Abstract class of a session description.
|
||||
* @hide
|
||||
*/
|
||||
public abstract class SessionDescription implements Parcelable {
|
||||
/** @hide */
|
||||
public static final Parcelable.Creator<SessionDescription> CREATOR =
|
||||
new Parcelable.Creator<SessionDescription>() {
|
||||
public SessionDescription createFromParcel(Parcel in) {
|
||||
return new SessionDescriptionImpl(in);
|
||||
}
|
||||
|
||||
public SessionDescription[] newArray(int size) {
|
||||
return new SessionDescriptionImpl[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the type of the session description; e.g., "SDP".
|
||||
*
|
||||
* @return the session description type
|
||||
*/
|
||||
public abstract String getType();
|
||||
|
||||
/**
|
||||
* Gets the raw content of the session description.
|
||||
*
|
||||
* @return the content of the session description
|
||||
*/
|
||||
public abstract byte[] getContent();
|
||||
|
||||
/** @hide */
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeString(getType());
|
||||
out.writeByteArray(getContent());
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static class SessionDescriptionImpl extends SessionDescription {
|
||||
private String mType;
|
||||
private byte[] mContent;
|
||||
|
||||
SessionDescriptionImpl(Parcel in) {
|
||||
mType = in.readString();
|
||||
mContent = in.createByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent() {
|
||||
return mContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
306
voip/java/android/net/sip/SipAudioCall.java
Normal file
306
voip/java/android/net/sip/SipAudioCall.java
Normal file
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* 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.net.rtp.AudioGroup;
|
||||
import android.net.rtp.AudioStream;
|
||||
import android.os.Message;
|
||||
|
||||
import javax.sip.SipException;
|
||||
|
||||
/**
|
||||
* Interface for making audio calls over SIP.
|
||||
* @hide
|
||||
*/
|
||||
public interface SipAudioCall {
|
||||
/** Listener class for all event callbacks. */
|
||||
public interface Listener {
|
||||
/**
|
||||
* Called when the call object is ready to make another call.
|
||||
*
|
||||
* @param call the call object that is ready to make another call
|
||||
*/
|
||||
void onReadyToCall(SipAudioCall call);
|
||||
|
||||
/**
|
||||
* Called when a request is sent out to initiate a new call.
|
||||
*
|
||||
* @param call the call object that carries out the audio call
|
||||
*/
|
||||
void onCalling(SipAudioCall call);
|
||||
|
||||
/**
|
||||
* Called when a new call comes in.
|
||||
*
|
||||
* @param call the call object that carries out the audio call
|
||||
* @param caller the SIP profile of the caller
|
||||
*/
|
||||
void onRinging(SipAudioCall call, SipProfile caller);
|
||||
|
||||
/**
|
||||
* Called when a RINGING response is received for the INVITE request sent
|
||||
*
|
||||
* @param call the call object that carries out the audio call
|
||||
*/
|
||||
void onRingingBack(SipAudioCall call);
|
||||
|
||||
/**
|
||||
* Called when the session is established.
|
||||
*
|
||||
* @param call the call object that carries out the audio call
|
||||
*/
|
||||
void onCallEstablished(SipAudioCall call);
|
||||
|
||||
/**
|
||||
* Called when the session is terminated.
|
||||
*
|
||||
* @param call the call object that carries out the audio call
|
||||
*/
|
||||
void onCallEnded(SipAudioCall call);
|
||||
|
||||
/**
|
||||
* Called when the peer is busy during session initialization.
|
||||
*
|
||||
* @param call the call object that carries out the audio call
|
||||
*/
|
||||
void onCallBusy(SipAudioCall call);
|
||||
|
||||
/**
|
||||
* Called when the call is on hold.
|
||||
*
|
||||
* @param call the call object that carries out the audio call
|
||||
*/
|
||||
void onCallHeld(SipAudioCall call);
|
||||
|
||||
/**
|
||||
* Called when an error occurs.
|
||||
*
|
||||
* @param call the call object that carries out the audio call
|
||||
* @param errorMessage error message
|
||||
*/
|
||||
void onError(SipAudioCall call, String errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* The adapter class for {@link SipAudioCall#Listener}. The default
|
||||
* implementation of all callback methods is no-op.
|
||||
*/
|
||||
public class Adapter implements Listener {
|
||||
protected void onChanged(SipAudioCall call) {
|
||||
}
|
||||
public void onReadyToCall(SipAudioCall call) {
|
||||
onChanged(call);
|
||||
}
|
||||
public void onCalling(SipAudioCall call) {
|
||||
onChanged(call);
|
||||
}
|
||||
public void onRinging(SipAudioCall call, SipProfile caller) {
|
||||
onChanged(call);
|
||||
}
|
||||
public void onRingingBack(SipAudioCall call) {
|
||||
onChanged(call);
|
||||
}
|
||||
public void onCallEstablished(SipAudioCall call) {
|
||||
onChanged(call);
|
||||
}
|
||||
public void onCallEnded(SipAudioCall call) {
|
||||
onChanged(call);
|
||||
}
|
||||
public void onCallBusy(SipAudioCall call) {
|
||||
onChanged(call);
|
||||
}
|
||||
public void onCallHeld(SipAudioCall call) {
|
||||
onChanged(call);
|
||||
}
|
||||
public void onError(SipAudioCall call, String errorMessage) {
|
||||
onChanged(call);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener to listen to the audio call events. The method calls
|
||||
* {@link #setListener(Listener, false)}.
|
||||
*
|
||||
* @param listener to listen to the audio call events of this object
|
||||
* @see #setListener(Listener, boolean)
|
||||
*/
|
||||
void setListener(Listener listener);
|
||||
|
||||
/**
|
||||
* Sets the listener to listen to the audio call events. A
|
||||
* {@link SipAudioCall} can only hold one listener at a time. Subsequent
|
||||
* calls to this method override the previous listener.
|
||||
*
|
||||
* @param listener to listen to the audio call events of this object
|
||||
* @param callbackImmediately set to true if the caller wants to be called
|
||||
* back immediately on the current state
|
||||
*/
|
||||
void setListener(Listener listener, boolean callbackImmediately);
|
||||
|
||||
/**
|
||||
* Closes this object. The object is not usable after being closed.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Initiates an audio call to the specified profile.
|
||||
*
|
||||
* @param callee the SIP profile to make the call to
|
||||
* @param sipManager the {@link SipManager} object to help make call with
|
||||
*/
|
||||
void makeCall(SipProfile callee, SipManager sipManager) throws SipException;
|
||||
|
||||
/**
|
||||
* Attaches an incoming call to this call object.
|
||||
*
|
||||
* @param session the session that receives the incoming call
|
||||
* @param sdp the session description of the incoming call
|
||||
*/
|
||||
void attachCall(ISipSession session, SdpSessionDescription sdp)
|
||||
throws SipException;
|
||||
|
||||
/** Ends a call. */
|
||||
void endCall() throws SipException;
|
||||
|
||||
/**
|
||||
* Puts a call on hold. When succeeds,
|
||||
* {@link #Listener#onCallHeld(SipAudioCall)} is called.
|
||||
*/
|
||||
void holdCall() throws SipException;
|
||||
|
||||
/** Answers a call. */
|
||||
void answerCall() throws SipException;
|
||||
|
||||
/**
|
||||
* Continues a call that's on hold. When succeeds,
|
||||
* {@link #Listener#onCallEstablished(SipAudioCall)} is called.
|
||||
*/
|
||||
void continueCall() throws SipException;
|
||||
|
||||
/** Puts the device to in-call mode. */
|
||||
void setInCallMode();
|
||||
|
||||
/** Puts the device to speaker mode. */
|
||||
void setSpeakerMode();
|
||||
|
||||
/** Toggles mute. */
|
||||
void toggleMute();
|
||||
|
||||
/**
|
||||
* Checks if the call is on hold.
|
||||
*
|
||||
* @return true if the call is on hold
|
||||
*/
|
||||
boolean isOnHold();
|
||||
|
||||
/**
|
||||
* Checks if the call is muted.
|
||||
*
|
||||
* @return true if the call is muted
|
||||
*/
|
||||
boolean isMuted();
|
||||
|
||||
/**
|
||||
* Sends a DTMF code.
|
||||
*
|
||||
* @param code the DTMF code to send
|
||||
*/
|
||||
void sendDtmf(int code);
|
||||
|
||||
/**
|
||||
* Sends a DTMF code.
|
||||
*
|
||||
* @param code the DTMF code to send
|
||||
* @param result the result message to send when done
|
||||
*/
|
||||
void sendDtmf(int code, Message result);
|
||||
|
||||
/**
|
||||
* Gets the {@link AudioStream} object used in this call. The object
|
||||
* represents the RTP stream that carries the audio data to and from the
|
||||
* peer. The object may not be created before the call is established. And
|
||||
* it is undefined after the call ends or the {@link #close} method is
|
||||
* called.
|
||||
*
|
||||
* @return the {@link AudioStream} object or null if the RTP stream has not
|
||||
* yet been set up
|
||||
*/
|
||||
AudioStream getAudioStream();
|
||||
|
||||
/**
|
||||
* Gets the {@link AudioGroup} object which the {@link AudioStream} object
|
||||
* joins. The group object may not exist before the call is established.
|
||||
* Also, the {@code AudioStream} may change its group during a call (e.g.,
|
||||
* after the call is held/un-held). Finally, the {@code AudioGroup} object
|
||||
* returned by this method is undefined after the call ends or the
|
||||
* {@link #close} method is called.
|
||||
*
|
||||
* @return the {@link AudioGroup} object or null if the RTP stream has not
|
||||
* yet been set up
|
||||
* @see #getAudioStream
|
||||
*/
|
||||
AudioGroup getAudioGroup();
|
||||
|
||||
/**
|
||||
* Checks if the call is established.
|
||||
*
|
||||
* @return true if the call is established
|
||||
*/
|
||||
boolean isInCall();
|
||||
|
||||
/**
|
||||
* Gets the local SIP profile.
|
||||
*
|
||||
* @return the local SIP profile
|
||||
*/
|
||||
SipProfile getLocalProfile();
|
||||
|
||||
/**
|
||||
* Gets the peer's SIP profile.
|
||||
*
|
||||
* @return the peer's SIP profile
|
||||
*/
|
||||
SipProfile getPeerProfile();
|
||||
|
||||
/**
|
||||
* Gets the state of the {@link ISipSession} that carries this call.
|
||||
*
|
||||
* @return the session state
|
||||
*/
|
||||
SipSessionState getState();
|
||||
|
||||
/**
|
||||
* Gets the {@link ISipSession} that carries this call.
|
||||
*
|
||||
* @return the session object that carries this call
|
||||
*/
|
||||
ISipSession getSipSession();
|
||||
|
||||
/**
|
||||
* Enables/disables the ring-back tone.
|
||||
*
|
||||
* @param enabled true to enable; false to disable
|
||||
*/
|
||||
void setRingbackToneEnabled(boolean enabled);
|
||||
|
||||
/**
|
||||
* Enables/disables the ring tone.
|
||||
*
|
||||
* @param enabled true to enable; false to disable
|
||||
*/
|
||||
void setRingtoneEnabled(boolean enabled);
|
||||
}
|
||||
701
voip/java/android/net/sip/SipAudioCallImpl.java
Normal file
701
voip/java/android/net/sip/SipAudioCallImpl.java
Normal file
@@ -0,0 +1,701 @@
|
||||
/*
|
||||
* 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 gov.nist.javax.sdp.fields.SDPKeywords;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.media.ToneGenerator;
|
||||
import android.net.Uri;
|
||||
import android.net.rtp.AudioCodec;
|
||||
import android.net.rtp.AudioGroup;
|
||||
import android.net.rtp.AudioStream;
|
||||
import android.net.rtp.RtpStream;
|
||||
import android.net.sip.ISipSession;
|
||||
import android.net.sip.SdpSessionDescription;
|
||||
import android.net.sip.SessionDescription;
|
||||
import android.net.sip.SipAudioCall;
|
||||
import android.net.sip.SipManager;
|
||||
import android.net.sip.SipProfile;
|
||||
import android.net.sip.SipSessionAdapter;
|
||||
import android.net.sip.SipSessionState;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.sdp.SdpException;
|
||||
import javax.sip.SipException;
|
||||
|
||||
/**
|
||||
* Class that handles an audio call over SIP.
|
||||
*/
|
||||
/** @hide */
|
||||
public class SipAudioCallImpl extends SipSessionAdapter
|
||||
implements SipAudioCall {
|
||||
private static final String TAG = SipAudioCallImpl.class.getSimpleName();
|
||||
private static final boolean RELEASE_SOCKET = true;
|
||||
private static final boolean DONT_RELEASE_SOCKET = false;
|
||||
private static final String AUDIO = "audio";
|
||||
private static final int DTMF = 101;
|
||||
|
||||
private Context mContext;
|
||||
private SipProfile mLocalProfile;
|
||||
private SipAudioCall.Listener mListener;
|
||||
private ISipSession mSipSession;
|
||||
private SdpSessionDescription mPeerSd;
|
||||
|
||||
private AudioStream mRtpSession;
|
||||
private SdpSessionDescription.AudioCodec mCodec;
|
||||
private long mSessionId = -1L; // SDP session ID
|
||||
private boolean mInCall = false;
|
||||
private boolean mMuted = false;
|
||||
private boolean mHold = false;
|
||||
|
||||
private boolean mRingbackToneEnabled = true;
|
||||
private boolean mRingtoneEnabled = true;
|
||||
private Ringtone mRingtone;
|
||||
private ToneGenerator mRingbackTone;
|
||||
|
||||
private SipProfile mPendingCallRequest;
|
||||
|
||||
public SipAudioCallImpl(Context context, SipProfile localProfile) {
|
||||
mContext = context;
|
||||
mLocalProfile = localProfile;
|
||||
}
|
||||
|
||||
public void setListener(SipAudioCall.Listener listener) {
|
||||
setListener(listener, false);
|
||||
}
|
||||
|
||||
public void setListener(SipAudioCall.Listener listener,
|
||||
boolean callbackImmediately) {
|
||||
mListener = listener;
|
||||
if ((listener == null) || !callbackImmediately) return;
|
||||
try {
|
||||
SipSessionState state = getState();
|
||||
switch (state) {
|
||||
case READY_TO_CALL:
|
||||
listener.onReadyToCall(this);
|
||||
break;
|
||||
case INCOMING_CALL:
|
||||
listener.onRinging(this, getPeerProfile(mSipSession));
|
||||
startRinging();
|
||||
break;
|
||||
case OUTGOING_CALL:
|
||||
listener.onCalling(this);
|
||||
break;
|
||||
default:
|
||||
listener.onError(this, "wrong state to attach call: " + state);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "setListener()", t);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean isInCall() {
|
||||
return mInCall;
|
||||
}
|
||||
|
||||
public synchronized boolean isOnHold() {
|
||||
return mHold;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
close(true);
|
||||
}
|
||||
|
||||
private synchronized void close(boolean closeRtp) {
|
||||
if (closeRtp) stopCall(RELEASE_SOCKET);
|
||||
stopRingbackTone();
|
||||
stopRinging();
|
||||
mSipSession = null;
|
||||
mInCall = false;
|
||||
mHold = false;
|
||||
mSessionId = -1L;
|
||||
}
|
||||
|
||||
public synchronized SipProfile getLocalProfile() {
|
||||
return mLocalProfile;
|
||||
}
|
||||
|
||||
public synchronized SipProfile getPeerProfile() {
|
||||
try {
|
||||
return (mSipSession == null) ? null : mSipSession.getPeerProfile();
|
||||
} catch (RemoteException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized SipSessionState getState() {
|
||||
if (mSipSession == null) return SipSessionState.READY_TO_CALL;
|
||||
try {
|
||||
return Enum.valueOf(SipSessionState.class, mSipSession.getState());
|
||||
} catch (RemoteException e) {
|
||||
return SipSessionState.REMOTE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public synchronized ISipSession getSipSession() {
|
||||
return mSipSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCalling(ISipSession session) {
|
||||
Log.d(TAG, "calling... " + session);
|
||||
Listener listener = mListener;
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.onCalling(SipAudioCallImpl.this);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "onCalling()", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRingingBack(ISipSession session) {
|
||||
Log.d(TAG, "sip call ringing back: " + session);
|
||||
if (!mInCall) startRingbackTone();
|
||||
Listener listener = mListener;
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.onRingingBack(SipAudioCallImpl.this);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "onRingingBack()", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onRinging(ISipSession session,
|
||||
SipProfile peerProfile, byte[] sessionDescription) {
|
||||
try {
|
||||
if ((mSipSession == null) || !mInCall
|
||||
|| !session.getCallId().equals(mSipSession.getCallId())) {
|
||||
// should not happen
|
||||
session.endCall();
|
||||
return;
|
||||
}
|
||||
|
||||
// session changing request
|
||||
try {
|
||||
mPeerSd = new SdpSessionDescription(sessionDescription);
|
||||
answerCall();
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "onRinging()", e);
|
||||
session.endCall();
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "onRinging()", e);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void establishCall(byte[] sessionDescription) {
|
||||
stopRingbackTone();
|
||||
stopRinging();
|
||||
try {
|
||||
SdpSessionDescription sd =
|
||||
new SdpSessionDescription(sessionDescription);
|
||||
Log.d(TAG, "sip call established: " + sd);
|
||||
startCall(sd);
|
||||
mInCall = true;
|
||||
} catch (SdpException e) {
|
||||
Log.e(TAG, "createSessionDescription()", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallEstablished(ISipSession session,
|
||||
byte[] sessionDescription) {
|
||||
establishCall(sessionDescription);
|
||||
Listener listener = mListener;
|
||||
if (listener != null) {
|
||||
try {
|
||||
if (mHold) {
|
||||
listener.onCallHeld(SipAudioCallImpl.this);
|
||||
} else {
|
||||
listener.onCallEstablished(SipAudioCallImpl.this);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "onCallEstablished()", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallEnded(ISipSession session) {
|
||||
Log.d(TAG, "sip call ended: " + session);
|
||||
close();
|
||||
Listener listener = mListener;
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.onCallEnded(SipAudioCallImpl.this);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "onCallEnded()", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallBusy(ISipSession session) {
|
||||
Log.d(TAG, "sip call busy: " + session);
|
||||
close(false);
|
||||
Listener listener = mListener;
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.onCallBusy(SipAudioCallImpl.this);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "onCallBusy()", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallChangeFailed(ISipSession session,
|
||||
String className, String message) {
|
||||
Log.d(TAG, "sip call change failed: " + message);
|
||||
Listener listener = mListener;
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.onError(SipAudioCallImpl.this,
|
||||
className + ": " + message);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "onCallBusy()", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ISipSession session, String className,
|
||||
String message) {
|
||||
Log.d(TAG, "sip session error: " + className + ": " + message);
|
||||
synchronized (this) {
|
||||
if (!isInCall()) close(true);
|
||||
}
|
||||
Listener listener = mListener;
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.onError(SipAudioCallImpl.this,
|
||||
className + ": " + message);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "onError()", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void attachCall(ISipSession session,
|
||||
SdpSessionDescription sdp) throws SipException {
|
||||
mSipSession = session;
|
||||
mPeerSd = sdp;
|
||||
try {
|
||||
session.setListener(this);
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "attachCall()", e);
|
||||
throwSipException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void makeCall(SipProfile peerProfile,
|
||||
SipManager sipManager) throws SipException {
|
||||
try {
|
||||
mSipSession = sipManager.createSipSession(mLocalProfile, this);
|
||||
if (mSipSession == null) {
|
||||
throw new SipException(
|
||||
"Failed to create SipSession; network available?");
|
||||
}
|
||||
mSipSession.makeCall(peerProfile, createOfferSessionDescription());
|
||||
} catch (Throwable e) {
|
||||
if (e instanceof SipException) {
|
||||
throw (SipException) e;
|
||||
} else {
|
||||
throwSipException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void endCall() throws SipException {
|
||||
try {
|
||||
stopRinging();
|
||||
if (mSipSession != null) mSipSession.endCall();
|
||||
stopCall(true);
|
||||
} catch (Throwable e) {
|
||||
throwSipException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void holdCall() throws SipException {
|
||||
if (mHold) return;
|
||||
try {
|
||||
mSipSession.changeCall(createHoldSessionDescription());
|
||||
mHold = true;
|
||||
} catch (Throwable e) {
|
||||
throwSipException(e);
|
||||
}
|
||||
|
||||
AudioGroup audioGroup = getAudioGroup();
|
||||
if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
|
||||
}
|
||||
|
||||
public synchronized void answerCall() throws SipException {
|
||||
try {
|
||||
stopRinging();
|
||||
mSipSession.answerCall(createAnswerSessionDescription());
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "answerCall()", e);
|
||||
throwSipException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void continueCall() throws SipException {
|
||||
if (!mHold) return;
|
||||
try {
|
||||
mHold = false;
|
||||
mSipSession.changeCall(createContinueSessionDescription());
|
||||
} catch (Throwable e) {
|
||||
throwSipException(e);
|
||||
}
|
||||
|
||||
AudioGroup audioGroup = getAudioGroup();
|
||||
if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL);
|
||||
}
|
||||
|
||||
private SessionDescription createOfferSessionDescription() {
|
||||
AudioCodec[] codecs = AudioCodec.getSystemSupportedCodecs();
|
||||
return createSdpBuilder(true, convert(codecs)).build();
|
||||
}
|
||||
|
||||
private SessionDescription createAnswerSessionDescription() {
|
||||
try {
|
||||
// choose an acceptable media from mPeerSd to answer
|
||||
SdpSessionDescription.AudioCodec codec = getCodec(mPeerSd);
|
||||
SdpSessionDescription.Builder sdpBuilder =
|
||||
createSdpBuilder(false, codec);
|
||||
if (mPeerSd.isSendOnly(AUDIO)) {
|
||||
sdpBuilder.addMediaAttribute(AUDIO, "recvonly", (String) null);
|
||||
} else if (mPeerSd.isReceiveOnly(AUDIO)) {
|
||||
sdpBuilder.addMediaAttribute(AUDIO, "sendonly", (String) null);
|
||||
}
|
||||
return sdpBuilder.build();
|
||||
} catch (SdpException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private SessionDescription createHoldSessionDescription() {
|
||||
try {
|
||||
return createSdpBuilder(false, mCodec)
|
||||
.addMediaAttribute(AUDIO, "sendonly", (String) null)
|
||||
.build();
|
||||
} catch (SdpException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private SessionDescription createContinueSessionDescription() {
|
||||
return createSdpBuilder(true, mCodec).build();
|
||||
}
|
||||
|
||||
private String getMediaDescription(SdpSessionDescription.AudioCodec codec) {
|
||||
return String.format("%d %s/%d", codec.payloadType, codec.name,
|
||||
codec.sampleRate);
|
||||
}
|
||||
|
||||
private long getSessionId() {
|
||||
if (mSessionId < 0) {
|
||||
mSessionId = System.currentTimeMillis();
|
||||
}
|
||||
return mSessionId;
|
||||
}
|
||||
|
||||
private SdpSessionDescription.Builder createSdpBuilder(
|
||||
boolean addTelephoneEvent,
|
||||
SdpSessionDescription.AudioCodec... codecs) {
|
||||
String localIp = getLocalIp();
|
||||
SdpSessionDescription.Builder sdpBuilder;
|
||||
try {
|
||||
long sessionVersion = System.currentTimeMillis();
|
||||
sdpBuilder = new SdpSessionDescription.Builder("SIP Call")
|
||||
.setOrigin(mLocalProfile, getSessionId(), sessionVersion,
|
||||
SDPKeywords.IN, SDPKeywords.IPV4, localIp)
|
||||
.setConnectionInfo(SDPKeywords.IN, SDPKeywords.IPV4,
|
||||
localIp);
|
||||
List<Integer> codecIds = new ArrayList<Integer>();
|
||||
for (SdpSessionDescription.AudioCodec codec : codecs) {
|
||||
codecIds.add(codec.payloadType);
|
||||
}
|
||||
if (addTelephoneEvent) codecIds.add(DTMF);
|
||||
sdpBuilder.addMedia(AUDIO, getLocalMediaPort(), 1, "RTP/AVP",
|
||||
codecIds.toArray(new Integer[codecIds.size()]));
|
||||
for (SdpSessionDescription.AudioCodec codec : codecs) {
|
||||
sdpBuilder.addMediaAttribute(AUDIO, "rtpmap",
|
||||
getMediaDescription(codec));
|
||||
}
|
||||
if (addTelephoneEvent) {
|
||||
sdpBuilder.addMediaAttribute(AUDIO, "rtpmap",
|
||||
DTMF + " telephone-event/8000");
|
||||
}
|
||||
// FIXME: deal with vbr codec
|
||||
sdpBuilder.addMediaAttribute(AUDIO, "ptime", "20");
|
||||
} catch (SdpException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return sdpBuilder;
|
||||
}
|
||||
|
||||
public synchronized void toggleMute() {
|
||||
AudioGroup audioGroup = getAudioGroup();
|
||||
if (audioGroup != null) {
|
||||
audioGroup.setMode(
|
||||
mMuted ? AudioGroup.MODE_NORMAL : AudioGroup.MODE_MUTED);
|
||||
mMuted = !mMuted;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean isMuted() {
|
||||
return mMuted;
|
||||
}
|
||||
|
||||
public synchronized void setInCallMode() {
|
||||
((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
|
||||
.setSpeakerphoneOn(false);
|
||||
((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
|
||||
.setMode(AudioManager.MODE_NORMAL);
|
||||
}
|
||||
|
||||
public synchronized void setSpeakerMode() {
|
||||
((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
|
||||
.setSpeakerphoneOn(true);
|
||||
}
|
||||
|
||||
public void sendDtmf(int code) {
|
||||
sendDtmf(code, null);
|
||||
}
|
||||
|
||||
public synchronized void sendDtmf(int code, Message result) {
|
||||
AudioGroup audioGroup = getAudioGroup();
|
||||
if ((audioGroup != null) && (mSipSession != null)
|
||||
&& (SipSessionState.IN_CALL == getState())) {
|
||||
Log.v(TAG, "send DTMF: " + code);
|
||||
audioGroup.sendDtmf(code);
|
||||
}
|
||||
if (result != null) result.sendToTarget();
|
||||
}
|
||||
|
||||
public synchronized AudioStream getAudioStream() {
|
||||
return mRtpSession;
|
||||
}
|
||||
|
||||
public synchronized AudioGroup getAudioGroup() {
|
||||
return ((mRtpSession == null) ? null : mRtpSession.getAudioGroup());
|
||||
}
|
||||
|
||||
private SdpSessionDescription.AudioCodec getCodec(SdpSessionDescription sd) {
|
||||
HashMap<String, AudioCodec> acceptableCodecs =
|
||||
new HashMap<String, AudioCodec>();
|
||||
for (AudioCodec codec : AudioCodec.getSystemSupportedCodecs()) {
|
||||
acceptableCodecs.put(codec.name, codec);
|
||||
}
|
||||
for (SdpSessionDescription.AudioCodec codec : sd.getAudioCodecs()) {
|
||||
AudioCodec matchedCodec = acceptableCodecs.get(codec.name);
|
||||
if (matchedCodec != null) return codec;
|
||||
}
|
||||
Log.w(TAG, "no common codec is found, use PCM/0");
|
||||
return convert(AudioCodec.ULAW);
|
||||
}
|
||||
|
||||
private AudioCodec convert(SdpSessionDescription.AudioCodec codec) {
|
||||
AudioCodec c = AudioCodec.getSystemSupportedCodec(codec.name);
|
||||
return ((c == null) ? AudioCodec.ULAW : c);
|
||||
}
|
||||
|
||||
private SdpSessionDescription.AudioCodec convert(AudioCodec codec) {
|
||||
return new SdpSessionDescription.AudioCodec(codec.defaultType,
|
||||
codec.name, codec.sampleRate, codec.sampleCount);
|
||||
}
|
||||
|
||||
private SdpSessionDescription.AudioCodec[] convert(AudioCodec[] codecs) {
|
||||
SdpSessionDescription.AudioCodec[] copies =
|
||||
new SdpSessionDescription.AudioCodec[codecs.length];
|
||||
for (int i = 0, len = codecs.length; i < len; i++) {
|
||||
copies[i] = convert(codecs[i]);
|
||||
}
|
||||
return copies;
|
||||
}
|
||||
|
||||
private void startCall(SdpSessionDescription peerSd) {
|
||||
stopCall(DONT_RELEASE_SOCKET);
|
||||
|
||||
mPeerSd = peerSd;
|
||||
String peerMediaAddress = peerSd.getPeerMediaAddress(AUDIO);
|
||||
// TODO: handle multiple media fields
|
||||
int peerMediaPort = peerSd.getPeerMediaPort(AUDIO);
|
||||
Log.i(TAG, "start audiocall " + peerMediaAddress + ":" + peerMediaPort);
|
||||
|
||||
int localPort = getLocalMediaPort();
|
||||
int sampleRate = 8000;
|
||||
int frameSize = sampleRate / 50; // 160
|
||||
try {
|
||||
// TODO: get sample rate from sdp
|
||||
mCodec = getCodec(peerSd);
|
||||
|
||||
AudioStream audioStream = mRtpSession;
|
||||
audioStream.associate(InetAddress.getByName(peerMediaAddress),
|
||||
peerMediaPort);
|
||||
audioStream.setCodec(convert(mCodec), mCodec.payloadType);
|
||||
audioStream.setDtmfType(DTMF);
|
||||
Log.d(TAG, "start media: localPort=" + localPort + ", peer="
|
||||
+ peerMediaAddress + ":" + peerMediaPort);
|
||||
|
||||
audioStream.setMode(RtpStream.MODE_NORMAL);
|
||||
if (!mHold) {
|
||||
// FIXME: won't work if peer is not sending nor receiving
|
||||
if (!peerSd.isSending(AUDIO)) {
|
||||
Log.d(TAG, " not receiving");
|
||||
audioStream.setMode(RtpStream.MODE_SEND_ONLY);
|
||||
}
|
||||
if (!peerSd.isReceiving(AUDIO)) {
|
||||
Log.d(TAG, " not sending");
|
||||
audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY);
|
||||
}
|
||||
}
|
||||
setInCallMode();
|
||||
|
||||
AudioGroup audioGroup = new AudioGroup();
|
||||
audioStream.join(audioGroup);
|
||||
if (mHold) {
|
||||
audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
|
||||
} else if (mMuted) {
|
||||
audioGroup.setMode(AudioGroup.MODE_MUTED);
|
||||
} else {
|
||||
audioGroup.setMode(AudioGroup.MODE_NORMAL);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "call()", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopCall(boolean releaseSocket) {
|
||||
Log.d(TAG, "stop audiocall");
|
||||
if (mRtpSession != null) {
|
||||
mRtpSession.join(null);
|
||||
|
||||
if (releaseSocket) {
|
||||
mRtpSession.release();
|
||||
mRtpSession = null;
|
||||
}
|
||||
}
|
||||
setInCallMode();
|
||||
}
|
||||
|
||||
private int getLocalMediaPort() {
|
||||
if (mRtpSession != null) return mRtpSession.getLocalPort();
|
||||
try {
|
||||
AudioStream s = mRtpSession =
|
||||
new AudioStream(InetAddress.getByName(getLocalIp()));
|
||||
return s.getLocalPort();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "getLocalMediaPort(): " + e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getLocalIp() {
|
||||
try {
|
||||
return mSipSession.getLocalIp();
|
||||
} catch (RemoteException e) {
|
||||
// FIXME
|
||||
return "127.0.0.1";
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setRingbackToneEnabled(boolean enabled) {
|
||||
mRingbackToneEnabled = enabled;
|
||||
}
|
||||
|
||||
public synchronized void setRingtoneEnabled(boolean enabled) {
|
||||
mRingtoneEnabled = enabled;
|
||||
}
|
||||
|
||||
private void startRingbackTone() {
|
||||
if (!mRingbackToneEnabled) return;
|
||||
if (mRingbackTone == null) {
|
||||
// The volume relative to other sounds in the stream
|
||||
int toneVolume = 80;
|
||||
mRingbackTone = new ToneGenerator(
|
||||
AudioManager.STREAM_VOICE_CALL, toneVolume);
|
||||
}
|
||||
mRingbackTone.startTone(ToneGenerator.TONE_CDMA_LOW_PBX_L);
|
||||
}
|
||||
|
||||
private void stopRingbackTone() {
|
||||
if (mRingbackTone != null) {
|
||||
mRingbackTone.stopTone();
|
||||
mRingbackTone.release();
|
||||
mRingbackTone = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void startRinging() {
|
||||
if (!mRingtoneEnabled) return;
|
||||
((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
|
||||
.vibrate(new long[] {0, 1000, 1000}, 1);
|
||||
AudioManager am = (AudioManager)
|
||||
mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
if (am.getStreamVolume(AudioManager.STREAM_RING) > 0) {
|
||||
String ringtoneUri =
|
||||
Settings.System.DEFAULT_RINGTONE_URI.toString();
|
||||
mRingtone = RingtoneManager.getRingtone(mContext,
|
||||
Uri.parse(ringtoneUri));
|
||||
mRingtone.play();
|
||||
}
|
||||
}
|
||||
|
||||
private void stopRinging() {
|
||||
((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
|
||||
.cancel();
|
||||
if (mRingtone != null) mRingtone.stop();
|
||||
}
|
||||
|
||||
private void throwSipException(Throwable throwable) throws SipException {
|
||||
if (throwable instanceof SipException) {
|
||||
throw (SipException) throwable;
|
||||
} else {
|
||||
throw new SipException("", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private SipProfile getPeerProfile(ISipSession session) {
|
||||
try {
|
||||
return session.getPeerProfile();
|
||||
} catch (RemoteException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
499
voip/java/android/net/sip/SipManager.java
Normal file
499
voip/java/android/net/sip/SipManager.java
Normal file
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
* 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.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;
|
||||
|
||||
// Will be removed once the SIP service is integrated into framework
|
||||
private BinderHelper<ISipService> mBinderHelper;
|
||||
|
||||
/**
|
||||
* Creates a manager instance and initializes the background SIP service.
|
||||
* Will be removed once the SIP service is integrated into framework.
|
||||
*
|
||||
* @param context context to start the SIP service
|
||||
* @return the manager instance
|
||||
*/
|
||||
public static SipManager getInstance(final Context context) {
|
||||
final SipManager manager = new SipManager();
|
||||
manager.createSipService(context);
|
||||
return manager;
|
||||
}
|
||||
|
||||
private SipManager() {
|
||||
}
|
||||
|
||||
private void createSipService(Context context) {
|
||||
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");
|
||||
}
|
||||
|
||||
byte[] offerSd = getOfferSessionDescription(incomingCallIntent);
|
||||
if (offerSd == null) {
|
||||
throw new SipException("Session description missing in incoming "
|
||||
+ "call intent");
|
||||
}
|
||||
|
||||
try {
|
||||
SdpSessionDescription sdp = new SdpSessionDescription(offerSd);
|
||||
|
||||
ISipSession session = mSipService.getPendingSession(callId);
|
||||
if (session == null) return null;
|
||||
SipAudioCall call = new SipAudioCallImpl(
|
||||
context, session.getLocalProfile());
|
||||
call.setRingtoneEnabled(ringtoneEnabled);
|
||||
call.attachCall(session, sdp);
|
||||
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);
|
||||
byte[] 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 byte[] getOfferSessionDescription(Intent incomingCallIntent) {
|
||||
return incomingCallIntent.getByteArrayExtra(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, byte[] 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
19
voip/java/android/net/sip/SipProfile.aidl
Normal file
19
voip/java/android/net/sip/SipProfile.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
parcelable SipProfile;
|
||||
401
voip/java/android/net/sip/SipProfile.java
Normal file
401
voip/java/android/net/sip/SipProfile.java
Normal file
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* 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.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.text.ParseException;
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.ListeningPoint;
|
||||
import javax.sip.PeerUnavailableException;
|
||||
import javax.sip.SipFactory;
|
||||
import javax.sip.address.Address;
|
||||
import javax.sip.address.AddressFactory;
|
||||
import javax.sip.address.SipURI;
|
||||
import javax.sip.address.URI;
|
||||
|
||||
/**
|
||||
* Class containing a SIP account, domain and server information.
|
||||
* @hide
|
||||
*/
|
||||
public class SipProfile implements Parcelable, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final int DEFAULT_PORT = 5060;
|
||||
private Address mAddress;
|
||||
private String mProxyAddress;
|
||||
private String mPassword;
|
||||
private String mDomain;
|
||||
private String mProtocol = ListeningPoint.UDP;
|
||||
private String mProfileName;
|
||||
private boolean mSendKeepAlive = false;
|
||||
private boolean mAutoRegistration = true;
|
||||
|
||||
/** @hide */
|
||||
public static final Parcelable.Creator<SipProfile> CREATOR =
|
||||
new Parcelable.Creator<SipProfile>() {
|
||||
public SipProfile createFromParcel(Parcel in) {
|
||||
return new SipProfile(in);
|
||||
}
|
||||
|
||||
public SipProfile[] newArray(int size) {
|
||||
return new SipProfile[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class to help create a {@link SipProfile}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private AddressFactory mAddressFactory;
|
||||
private SipProfile mProfile = new SipProfile();
|
||||
private SipURI mUri;
|
||||
private String mDisplayName;
|
||||
private String mProxyAddress;
|
||||
|
||||
{
|
||||
try {
|
||||
mAddressFactory =
|
||||
SipFactory.getInstance().createAddressFactory();
|
||||
} catch (PeerUnavailableException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param uriString the URI string as "sip:<user_name>@<domain>"
|
||||
* @throws ParseException if the string is not a valid URI
|
||||
*/
|
||||
public Builder(String uriString) throws ParseException {
|
||||
if (uriString == null) {
|
||||
throw new NullPointerException("uriString cannot be null");
|
||||
}
|
||||
URI uri = mAddressFactory.createURI(fix(uriString));
|
||||
if (uri instanceof SipURI) {
|
||||
mUri = (SipURI) uri;
|
||||
} else {
|
||||
throw new ParseException(uriString + " is not a SIP URI", 0);
|
||||
}
|
||||
mProfile.mDomain = mUri.getHost();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param username username of the SIP account
|
||||
* @param serverDomain the SIP server domain; if the network address
|
||||
* is different from the domain, use
|
||||
* {@link #setOutboundProxy(String)} to set server address
|
||||
* @throws ParseException if the parameters are not valid
|
||||
*/
|
||||
public Builder(String username, String serverDomain)
|
||||
throws ParseException {
|
||||
if ((username == null) || (serverDomain == null)) {
|
||||
throw new NullPointerException(
|
||||
"username and serverDomain cannot be null");
|
||||
}
|
||||
mUri = mAddressFactory.createSipURI(username, serverDomain);
|
||||
mProfile.mDomain = serverDomain;
|
||||
}
|
||||
|
||||
private String fix(String uriString) {
|
||||
return (uriString.trim().toLowerCase().startsWith("sip:")
|
||||
? uriString
|
||||
: "sip:" + uriString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the profile. This name is given by user.
|
||||
*
|
||||
* @param name name of the profile
|
||||
* @return this builder object
|
||||
*/
|
||||
public Builder setProfileName(String name) {
|
||||
mProfile.mProfileName = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password of the SIP account
|
||||
*
|
||||
* @param password password of the SIP account
|
||||
* @return this builder object
|
||||
*/
|
||||
public Builder setPassword(String password) {
|
||||
mUri.setUserPassword(password);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the port number of the server. By default, it is 5060.
|
||||
*
|
||||
* @param port port number of the server
|
||||
* @return this builder object
|
||||
* @throws InvalidArgumentException if the port number is out of range
|
||||
*/
|
||||
public Builder setPort(int port) throws InvalidArgumentException {
|
||||
mUri.setPort(port);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the protocol used to connect to the SIP server. Currently,
|
||||
* only "UDP" and "TCP" are supported.
|
||||
*
|
||||
* @param protocol the protocol string
|
||||
* @return this builder object
|
||||
* @throws InvalidArgumentException if the protocol is not recognized
|
||||
*/
|
||||
public Builder setProtocol(String protocol)
|
||||
throws InvalidArgumentException {
|
||||
if (protocol == null) {
|
||||
throw new NullPointerException("protocol cannot be null");
|
||||
}
|
||||
protocol = protocol.toUpperCase();
|
||||
if (!protocol.equals("UDP") && !protocol.equals("TCP")) {
|
||||
throw new InvalidArgumentException(
|
||||
"unsupported protocol: " + protocol);
|
||||
}
|
||||
mProfile.mProtocol = protocol;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the outbound proxy of the SIP server.
|
||||
*
|
||||
* @param outboundProxy the network address of the outbound proxy
|
||||
* @return this builder object
|
||||
*/
|
||||
public Builder setOutboundProxy(String outboundProxy) {
|
||||
mProxyAddress = outboundProxy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the display name of the user.
|
||||
*
|
||||
* @param displayName display name of the user
|
||||
* @return this builder object
|
||||
*/
|
||||
public Builder setDisplayName(String displayName) {
|
||||
mDisplayName = displayName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the send keep-alive flag.
|
||||
*
|
||||
* @param flag true if sending keep-alive message is required,
|
||||
* false otherwise
|
||||
* @return this builder object
|
||||
*/
|
||||
public Builder setSendKeepAlive(boolean flag) {
|
||||
mProfile.mSendKeepAlive = flag;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the auto. registration flag.
|
||||
*
|
||||
* @param flag true if the profile will be registered automatically,
|
||||
* false otherwise
|
||||
* @return this builder object
|
||||
*/
|
||||
public Builder setAutoRegistration(boolean flag) {
|
||||
mProfile.mAutoRegistration = flag;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns the SIP profile object.
|
||||
*
|
||||
* @return the profile object created
|
||||
*/
|
||||
public SipProfile build() {
|
||||
// remove password from URI
|
||||
mProfile.mPassword = mUri.getUserPassword();
|
||||
mUri.setUserPassword(null);
|
||||
try {
|
||||
mProfile.mAddress = mAddressFactory.createAddress(
|
||||
mDisplayName, mUri);
|
||||
if (!TextUtils.isEmpty(mProxyAddress)) {
|
||||
SipURI uri = (SipURI)
|
||||
mAddressFactory.createURI(fix(mProxyAddress));
|
||||
mProfile.mProxyAddress = uri.getHost();
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
// must not occur
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return mProfile;
|
||||
}
|
||||
}
|
||||
|
||||
private SipProfile() {
|
||||
}
|
||||
|
||||
private SipProfile(Parcel in) {
|
||||
mAddress = (Address) in.readSerializable();
|
||||
mProxyAddress = in.readString();
|
||||
mPassword = in.readString();
|
||||
mDomain = in.readString();
|
||||
mProtocol = in.readString();
|
||||
mProfileName = in.readString();
|
||||
mSendKeepAlive = (in.readInt() == 0) ? false : true;
|
||||
mAutoRegistration = (in.readInt() == 0) ? false : true;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeSerializable(mAddress);
|
||||
out.writeString(mProxyAddress);
|
||||
out.writeString(mPassword);
|
||||
out.writeString(mDomain);
|
||||
out.writeString(mProtocol);
|
||||
out.writeString(mProfileName);
|
||||
out.writeInt(mSendKeepAlive ? 1 : 0);
|
||||
out.writeInt(mAutoRegistration ? 1 : 0);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SIP URI of this profile.
|
||||
*
|
||||
* @return the SIP URI of this profile
|
||||
*/
|
||||
public SipURI getUri() {
|
||||
return (SipURI) mAddress.getURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SIP URI string of this profile.
|
||||
*
|
||||
* @return the SIP URI string of this profile
|
||||
*/
|
||||
public String getUriString() {
|
||||
return mAddress.getURI().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SIP address of this profile.
|
||||
*
|
||||
* @return the SIP address of this profile
|
||||
*/
|
||||
public Address getSipAddress() {
|
||||
return mAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the display name of the user.
|
||||
*
|
||||
* @return the display name of the user
|
||||
*/
|
||||
public String getDisplayName() {
|
||||
return mAddress.getDisplayName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the username.
|
||||
*
|
||||
* @return the username
|
||||
*/
|
||||
public String getUserName() {
|
||||
return getUri().getUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the password.
|
||||
*
|
||||
* @return the password
|
||||
*/
|
||||
public String getPassword() {
|
||||
return mPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SIP domain.
|
||||
*
|
||||
* @return the SIP domain
|
||||
*/
|
||||
public String getSipDomain() {
|
||||
return mDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the port number of the SIP server.
|
||||
*
|
||||
* @return the port number of the SIP server
|
||||
*/
|
||||
public int getPort() {
|
||||
int port = getUri().getPort();
|
||||
return (port == -1) ? DEFAULT_PORT : port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the protocol used to connect to the server.
|
||||
*
|
||||
* @return the protocol
|
||||
*/
|
||||
public String getProtocol() {
|
||||
return mProtocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the network address of the server outbound proxy.
|
||||
*
|
||||
* @return the network address of the server outbound proxy
|
||||
*/
|
||||
public String getProxyAddress() {
|
||||
return mProxyAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (user-defined) name of the profile.
|
||||
*
|
||||
* @return name of the profile
|
||||
*/
|
||||
public String getProfileName() {
|
||||
return mProfileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flag of 'Sending keep-alive'.
|
||||
*
|
||||
* @return the flag of sending SIP keep-alive messages.
|
||||
*/
|
||||
public boolean getSendKeepAlive() {
|
||||
return mSendKeepAlive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flag of 'Auto Registration'.
|
||||
*
|
||||
* @return the flag of registering the profile automatically.
|
||||
*/
|
||||
public boolean getAutoRegistration() {
|
||||
return mAutoRegistration;
|
||||
}
|
||||
}
|
||||
48
voip/java/android/net/sip/SipRegistrationListener.java
Normal file
48
voip/java/android/net/sip/SipRegistrationListener.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Listener class to listen to SIP registration events.
|
||||
* @hide
|
||||
*/
|
||||
public interface SipRegistrationListener {
|
||||
/**
|
||||
* Called when a registration request is sent.
|
||||
*
|
||||
* @param localProfileUri the URI string of the SIP profile to register with
|
||||
*/
|
||||
void onRegistering(String localProfileUri);
|
||||
|
||||
/**
|
||||
* Called when registration is successfully done.
|
||||
*
|
||||
* @param localProfileUri the URI string of the SIP profile to register with
|
||||
* @param expiryTime duration in second before the registration expires
|
||||
*/
|
||||
void onRegistrationDone(String localProfileUri, long expiryTime);
|
||||
|
||||
/**
|
||||
* Called when the registration fails.
|
||||
*
|
||||
* @param localProfileUri the URI string of the SIP profile to register with
|
||||
* @param errorClass name of the exception class
|
||||
* @param errorMessage error message
|
||||
*/
|
||||
void onRegistrationFailed(String localProfileUri, String errorClass,
|
||||
String errorMessage);
|
||||
}
|
||||
67
voip/java/android/net/sip/SipSessionAdapter.java
Normal file
67
voip/java/android/net/sip/SipSessionAdapter.java
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Adapter class for {@link ISipSessionListener}. Default implementation of all
|
||||
* callback methods is no-op.
|
||||
* @hide
|
||||
*/
|
||||
public class SipSessionAdapter extends ISipSessionListener.Stub {
|
||||
public void onCalling(ISipSession session) {
|
||||
}
|
||||
|
||||
public void onRinging(ISipSession session, SipProfile caller,
|
||||
byte[] sessionDescription) {
|
||||
}
|
||||
|
||||
public void onRingingBack(ISipSession session) {
|
||||
}
|
||||
|
||||
public void onCallEstablished(
|
||||
ISipSession session, byte[] sessionDescription) {
|
||||
}
|
||||
|
||||
public void onCallEnded(ISipSession session) {
|
||||
}
|
||||
|
||||
public void onCallBusy(ISipSession session) {
|
||||
}
|
||||
|
||||
public void onCallChanged(ISipSession session, byte[] sessionDescription) {
|
||||
}
|
||||
|
||||
public void onCallChangeFailed(ISipSession session, String className,
|
||||
String message) {
|
||||
}
|
||||
|
||||
public void onError(ISipSession session, String className, String message) {
|
||||
}
|
||||
|
||||
public void onRegistering(ISipSession session) {
|
||||
}
|
||||
|
||||
public void onRegistrationDone(ISipSession session, int duration) {
|
||||
}
|
||||
|
||||
public void onRegistrationFailed(ISipSession session, String className,
|
||||
String message) {
|
||||
}
|
||||
|
||||
public void onRegistrationTimeout(ISipSession session) {
|
||||
}
|
||||
}
|
||||
66
voip/java/android/net/sip/SipSessionState.java
Normal file
66
voip/java/android/net/sip/SipSessionState.java
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Defines {@link ISipSession} states.
|
||||
* @hide
|
||||
*/
|
||||
public enum SipSessionState {
|
||||
/** When session is ready to initiate a call or transaction. */
|
||||
READY_TO_CALL,
|
||||
|
||||
/** When the registration request is sent out. */
|
||||
REGISTERING,
|
||||
|
||||
/** When the unregistration request is sent out. */
|
||||
DEREGISTERING,
|
||||
|
||||
/** When an INVITE request is received. */
|
||||
INCOMING_CALL,
|
||||
|
||||
/** When an OK response is sent for the INVITE request received. */
|
||||
INCOMING_CALL_ANSWERING,
|
||||
|
||||
/** When an INVITE request is sent. */
|
||||
OUTGOING_CALL,
|
||||
|
||||
/** When a RINGING response is received for the INVITE request sent. */
|
||||
OUTGOING_CALL_RING_BACK,
|
||||
|
||||
/** When a CANCEL request is sent for the INVITE request sent. */
|
||||
OUTGOING_CALL_CANCELING,
|
||||
|
||||
/** When a call is established. */
|
||||
IN_CALL,
|
||||
|
||||
/** Some error occurs when making a remote call to {@link ISipSession}. */
|
||||
REMOTE_ERROR,
|
||||
|
||||
/** When an OPTIONS request is sent. */
|
||||
PINGING;
|
||||
|
||||
/**
|
||||
* Checks if the specified string represents the same state as this object.
|
||||
*
|
||||
* @return true if the specified string represents the same state as this
|
||||
* object
|
||||
*/
|
||||
public boolean equals(String state) {
|
||||
return toString().equals(state);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user